feat: F5.4 presence: who else is in the project
Over Yjs awareness each editor tells who it is (F names the person in the access message), the device it has chosen and the windows it has open, and nothing else: no pointer, nothing that changes without a cause, sent only when it changed. Others' states are read with care. A frame in their colour round a device they chose, a dot on a window they have open too, a circle with initials each beside the mode buttons; the bridge tells the dashboard who is there. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,8 @@ export interface CanvasProps {
|
||||
* documentation item is chosen or changed, and nothing has a menu.
|
||||
*/
|
||||
readonly locked?: boolean;
|
||||
/** The devices others in the project have chosen, with their colours (plan §7.4): a frame each. */
|
||||
readonly chosenBy?: ReadonlyMap<string, readonly string[]>;
|
||||
/** A documentation item moved or resized: its new frame. */
|
||||
readonly onDocFrame?: (id: string, frame: Frame) => void;
|
||||
/** The field a focused text is typed into, over the surface at its frame. */
|
||||
@@ -97,6 +99,8 @@ export interface CanvasProps {
|
||||
}
|
||||
|
||||
const NO_LINKS: ReadonlySet<string> = new Set();
|
||||
const NO_CHOICES: ReadonlyMap<string, readonly string[]> = new Map();
|
||||
const NO_COLORS: readonly string[] = [];
|
||||
|
||||
/** The label's width in Java's font, measured once per text. */
|
||||
function useTextWidth(): (text: string) => number {
|
||||
@@ -170,7 +174,22 @@ function Documentation({ item, documenting, focused }: { readonly item: Document
|
||||
);
|
||||
}
|
||||
|
||||
function Device({ node, box, kindName, selected, pending }: { readonly node: NodeView; readonly box: DeviceBox; readonly kindName: string; readonly selected: boolean; readonly pending: boolean }) {
|
||||
function Device({
|
||||
node,
|
||||
box,
|
||||
kindName,
|
||||
selected,
|
||||
pending,
|
||||
chosenBy,
|
||||
}: {
|
||||
readonly node: NodeView;
|
||||
readonly box: DeviceBox;
|
||||
readonly kindName: string;
|
||||
readonly selected: boolean;
|
||||
readonly pending: boolean;
|
||||
/** The colours of the others who have chosen it, each a frame further out. */
|
||||
readonly chosenBy: readonly string[];
|
||||
}) {
|
||||
return (
|
||||
<g
|
||||
className="device"
|
||||
@@ -183,6 +202,22 @@ function Device({ node, box, kindName, selected, pending }: { readonly node: Nod
|
||||
>
|
||||
{/* The whole button takes the pointer, as Java's label does. */}
|
||||
<rect x={box.x} y={box.y} width={box.width} height={box.height} fill="transparent" />
|
||||
{chosenBy.map((color, index) => (
|
||||
<rect
|
||||
key={color}
|
||||
className="chosen-by"
|
||||
data-color={color}
|
||||
x={box.x - 5 - 3 * index}
|
||||
y={box.y - 5 - 3 * index}
|
||||
width={box.width + 10 + 6 * index}
|
||||
height={box.height + 10 + 6 * index}
|
||||
rx={8}
|
||||
fill="none"
|
||||
stroke={color}
|
||||
strokeWidth={2}
|
||||
pointerEvents="none"
|
||||
/>
|
||||
))}
|
||||
{selected || pending ? (
|
||||
<rect
|
||||
x={box.x - 2}
|
||||
@@ -235,6 +270,7 @@ export const Canvas = forwardRef<CanvasHandle, CanvasProps>(function Canvas(
|
||||
highlightedLink = null,
|
||||
mode = 'design',
|
||||
locked = false,
|
||||
chosenBy = NO_CHOICES,
|
||||
activeLinks = NO_LINKS,
|
||||
onActivate,
|
||||
onDocFrame,
|
||||
@@ -631,6 +667,7 @@ export const Canvas = forwardRef<CanvasHandle, CanvasProps>(function Canvas(
|
||||
box={boxes.get(node.id)!}
|
||||
kindName={t(`device.${node.kind}`)}
|
||||
selected={selection?.kind === 'device' && selection.id === node.id}
|
||||
chosenBy={chosenBy.get(node.id) ?? NO_COLORS}
|
||||
pending={cableFrom?.id === node.id}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user