Skip to content

Commit

Permalink
Merge branch 'main' of github.com:a-type/biscuits
Browse files Browse the repository at this point in the history
  • Loading branch information
a-type committed Jun 9, 2024
2 parents 8ff74ca + a3e5ab7 commit c8ed0b8
Show file tree
Hide file tree
Showing 21 changed files with 379 additions and 119 deletions.
2 changes: 1 addition & 1 deletion apps/gnocchi/hub/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"typecheck": "tsc --build tsconfig.json"
},
"dependencies": {
"@a-type/ui": "^0.8.18",
"@a-type/ui": "^0.8.19",
"@a-type/utils": "^1.0.8",
"@tiptap/core": "^2.2.4",
"@tiptap/extension-document": "^2.2.4",
Expand Down
2 changes: 1 addition & 1 deletion apps/gnocchi/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"typecheck": "tsc --build tsconfig.json"
},
"dependencies": {
"@a-type/ui": "^0.8.18",
"@a-type/ui": "^0.8.19",
"@a-type/utils": "^1.0.8",
"@biscuits/client": "workspace:*",
"@biscuits/error": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion apps/marginalia/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@a-type/ui": "0.8.18",
"@a-type/ui": "0.8.19",
"@a-type/utils": "1.1.0",
"@biscuits/client": "workspace:*",
"@marginalia.biscuits/verdant": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion apps/shopping/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@a-type/ui": "^0.8.18",
"@a-type/ui": "^0.8.19",
"@a-type/utils": "^1.0.8",
"@biscuits/client": "workspace:*",
"@react-spring/web": "^9.7.3",
Expand Down
2 changes: 1 addition & 1 deletion apps/star-chart/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"@a-type/ui": "0.8.18",
"@a-type/ui": "0.8.19",
"@a-type/utils": "1.1.2",
"@biscuits/client": "workspace:*",
"@react-spring/web": "^9.7.3",
Expand Down
22 changes: 11 additions & 11 deletions apps/star-chart/web/src/components/canvas/BoxRegion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { CanvasGestureInfo } from './Canvas.js';
import { useCanvas } from './CanvasProvider.jsx';

export interface BoxRegionProps {
onPending?: (objectIds: string[], info: CanvasGestureInfo) => void;
onEnd?: (objectIds: string[], info: CanvasGestureInfo) => void;
onPending?: (objectIds: Set<string>, info: CanvasGestureInfo) => void;
onEnd?: (objectIds: Set<string>, info: CanvasGestureInfo) => void;
tolerance?: number;
className?: string;
}
Expand All @@ -26,13 +26,13 @@ export function BoxRegion({
}));
const originRef = useRef<Vector2>({ x: 0, y: 0 });

const previousPending = useRef<string[]>([]);
const previousPending = useRef<Set<string>>(new Set<string>());

const canvas = useCanvas();

useCanvasGestures({
onDragStart: (info) => {
previousPending.current = [];
previousPending.current = new Set<string>();
originRef.current = info.worldPosition;
spring.set({
x: info.worldPosition.x,
Expand All @@ -52,15 +52,15 @@ export function BoxRegion({
const objectIds = canvas.bounds.getIntersections(rect, tolerance);

// this is all just logic to diff as much as possible...
if (objectIds.length !== previousPending.current.length) {
if (objectIds.size !== previousPending.current.size) {
onPending?.(objectIds, info);
} else if (objectIds.length === 0) {
if (previousPending.current.length !== 0) {
onPending?.([], info);
} else if (objectIds.size === 0) {
if (previousPending.current.size !== 0) {
onPending?.(objectIds, info);
}
} else {
for (let i = 0; i < objectIds.length; i++) {
if (objectIds[i] !== previousPending.current[i]) {
for (const entry of objectIds) {
if (!previousPending.current.has(entry)) {
onPending?.(objectIds, info);
break;
}
Expand All @@ -80,7 +80,7 @@ export function BoxRegion({
tolerance,
);

onPending?.([], info);
onPending?.(new Set(), info);
onCommit?.(objectIds, info);

spring.set({ x: 0, y: 0, width: 0, height: 0 });
Expand Down
2 changes: 1 addition & 1 deletion apps/star-chart/web/src/components/canvas/BoxSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Vector2 } from './types.js';

export interface BoxSelectProps {
className?: string;
onCommit?: (objectIds: string[], endPosition: Vector2) => void;
onCommit?: (objectIds: Set<string>, endPosition: Vector2) => void;
}

export function BoxSelect({ className, onCommit }: BoxSelectProps) {
Expand Down
13 changes: 12 additions & 1 deletion apps/star-chart/web/src/components/canvas/Canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { clampVector, snap } from './math.js';
import { ObjectBounds } from './ObjectBounds.js';
import { Selections } from './Selections.js';
import { RectLimits, Vector2 } from './types.js';
import { Viewport, ViewportConfig } from './Viewport.js';
import { Viewport, ViewportConfig, ViewportEventOrigin } from './Viewport.js';
import { proxy } from 'valtio';

export interface CanvasOptions {
Expand Down Expand Up @@ -222,5 +222,16 @@ export class Canvas extends EventSubscriber<CanvasEvents> {
}
};

zoomToFit = (
options: { origin?: ViewportEventOrigin; margin?: number } = {},
) => {
const bounds = this.bounds.getCurrentContainer();
if (bounds) {
this.viewport.fitOnScreen(bounds, options);
} else {
this.viewport.doMove(this.center, 1, options);
}
};

dispose = () => {};
}
2 changes: 2 additions & 0 deletions apps/star-chart/web/src/components/canvas/CanvasObject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,8 @@ export function useCanvasObject({
// block gestures internal to the drag handle for a bit even
// after releasing
setTimeout(setIsDragging, 100, false);
// update the spatial hash now that the object is settled
canvas.bounds.updateHash(objectId);
},
});

Expand Down
107 changes: 104 additions & 3 deletions apps/star-chart/web/src/components/canvas/ObjectBounds.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventSubscriber } from '@a-type/utils';
import { SpringValue } from '@react-spring/web';
import { Box, LiveVector2, Vector2 } from './types.js';
import { SpatialHash } from './SpatialHash.js';

export interface Bounds {
width: SpringValue<number>;
Expand All @@ -15,12 +16,44 @@ export class ObjectBounds extends EventSubscriber<{
private origins: Map<string, LiveVector2> = new Map();
private sizes: Map<string, Bounds> = new Map();
private sizeObserver;
private spatialHash = new SpatialHash<string>(100);
private spatialHashRecomputeTimers = new Map<string, any>();

constructor() {
super();
this.sizeObserver = new ResizeObserver(this.handleChanges);
}

updateHash = (objectId: string) => {
const origin = this.getOrigin(objectId);
const size = this.getSize(objectId);

if (!origin || !size) {
console.log('no origin or size', objectId, {
origin,
size,
});
return;
}

const x = origin.x.get();
const y = origin.y.get();
const width = size.width.get();
const height = size.height.get();

this.spatialHash.replace(objectId, { x, y, width, height });
};

private debouncedUpdateHash = (objectId: string) => {
clearTimeout(this.spatialHashRecomputeTimers.get(objectId));
this.spatialHashRecomputeTimers.set(
objectId,
setTimeout(() => {
this.updateHash(objectId);
}, 500),
);
};

private updateSize = (
objectId: string,
changes: Partial<{ width: number; height: number }>,
Expand All @@ -39,6 +72,8 @@ export class ObjectBounds extends EventSubscriber<{
if (changes.height) {
bounds.height.set(changes.height);
}

this.debouncedUpdateHash(objectId);
};

observe = (objectId: string, element: Element | null) => {
Expand Down Expand Up @@ -105,9 +140,14 @@ export class ObjectBounds extends EventSubscriber<{
}

getIntersections = (box: Box, threshold: number) => {
return this.ids.filter((objectId) =>
this.intersects(objectId, box, threshold),
);
const nearby = this.spatialHash.queryByRect(box);
const intersections = new Set<string>();
for (const id of nearby) {
if (this.intersects(id, box, threshold)) {
intersections.add(id);
}
}
return intersections;
};

hitTest = (point: Vector2) => {
Expand Down Expand Up @@ -196,4 +236,65 @@ export class ObjectBounds extends EventSubscriber<{

return intersectionArea / testArea > threshold;
};

/**
* Get the instantaenous bounding box of an object.
*/
getCurrentBounds = (objectId: string): Box | null => {
const origin = this.getOrigin(objectId);
const size = this.getSize(objectId);

if (!origin && !size) {
return null;
}

const bounds: Box = {
x: 0,
y: 0,
width: 0,
height: 0,
};

if (origin) {
bounds.x = origin.x.get();
bounds.y = origin.y.get();
}
if (size) {
bounds.width = size.width.get();
bounds.height = size.height.get();
}

return bounds;
};

/**
* Gets the instantaneous rectangle describing the outer
* limits of all tracked objects
*/
getCurrentContainer = () => {
const ids = this.ids;
let container = this.getCurrentBounds(ids[0]);
if (!container) {
return null;
}

for (let i = 1; i < ids.length; i++) {
const bounds = this.getCurrentBounds(ids[i]);
if (!bounds) {
continue;
}

container = {
x: Math.min(container.x, bounds.x),
y: Math.min(container.y, bounds.y),
width: Math.max(container.width, bounds.x - container.x + bounds.width),
height: Math.max(
container.height,
bounds.y - container.y + bounds.height,
),
};
}

return container;
};
}
22 changes: 11 additions & 11 deletions apps/star-chart/web/src/components/canvas/Selections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Selections extends EventSubscriber<{
this.emit('change', Array.from(this.selectedIds));
};

addAll = (objectIds: string[]) => {
addAll = (objectIds: Iterable<string>) => {
for (const objectId of objectIds) {
this.add(objectId);
}
Expand Down Expand Up @@ -66,31 +66,31 @@ export class Selections extends EventSubscriber<{
}
};

set = (objectIds: string[]) => {
const selectedIds = new Set(objectIds);
set = (objectIds: Set<string> | Array<string>) => {
const selectedIds = Array.isArray(objectIds)
? new Set(objectIds)
: objectIds;
for (const objectId of this.selectedIds) {
if (!selectedIds.has(objectId)) {
this.remove(objectId);
}
}
for (const objectId of objectIds) {
if (!this.selectedIds.has(objectId)) {
this.add(objectId);
}
this.add(objectId);
}
};

setPending = (objectIds: string[]) => {
const pendingIds = new Set(objectIds);
setPending = (objectIds: Set<string> | Array<string>) => {
const pendingIds = Array.isArray(objectIds)
? new Set(objectIds)
: objectIds;
for (const objectId of this.pendingIds) {
if (!pendingIds.has(objectId)) {
this.removePending(objectId);
}
}
for (const objectId of objectIds) {
if (!this.pendingIds.has(objectId)) {
this.addPending(objectId);
}
this.addPending(objectId);
}
};
}
Loading

0 comments on commit c8ed0b8

Please sign in to comment.