pi-weave 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -16
- package/package.json +1 -2
- package/src/core/frontmatter.ts +3 -3
- package/src/core/graph/model.ts +2 -2
- package/src/core/graph/wikilinks.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/pi/viewer/tui/surface/detail.ts +1 -1
- package/src/web/client/dist/app.js +210 -178
- package/src/web/client/graph/Graph.tsx +18 -0
- package/src/web/client/graph/column.model.ts +19 -2
- package/src/web/client/graph/graph.model.ts +1 -1
- package/src/web/client/graph/positions.ts +1 -1
- package/src/web/client/graph/renderer.ts +9 -1
- package/src/web/client/note/Note.tsx +66 -91
- package/src/web/client/note/drafts.ts +172 -0
- package/src/web/client/note/note.model.ts +2 -34
- package/src/web/client/search/SearchPalette.tsx +9 -8
- package/src/web/client/shell/ContextRail.tsx +8 -9
- package/src/web/client/shell/Shell.tsx +371 -254
- package/src/web/client/shell/StatusBar.tsx +1 -1
- package/src/web/client/shell/focus.model.ts +2 -2
- package/src/web/client/shell/keys.model.ts +26 -31
- package/src/web/client/shell/keys.ts +1 -0
- package/src/web/client/shell/shell.model.ts +31 -107
- package/src/web/client/shell/theme.model.ts +4 -4
- package/src/web/client/shell/theme.ts +12 -93
- package/src/web/client/shell/workspace.css.ts +117 -0
- package/src/web/client/tree/Tree.tsx +43 -14
- package/src/web/client/tree/tree.model.ts +19 -0
- package/src/web/client/workspace.ts +19 -1
- package/src/web/server/routes.ts +34 -3
- package/src/web/server/server.ts +5 -0
- package/src/web/server/workspace-state.ts +121 -0
- package/src/web/shared/workspace.ts +270 -0
- package/src/web/client/selection.storage.ts +0 -69
- package/src/web/client/shell/Columns.tsx +0 -139
- package/src/web/client/shell/Divider.tsx +0 -15
- package/src/web/client/shell/Header.tsx +0 -119
- package/src/web/client/shell/drag.model.ts +0 -35
- package/src/web/client/shell/layout.model.ts +0 -132
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/** Pure model for the resizable workspace columns. */
|
|
2
|
-
|
|
3
|
-
export type ColumnId = "tree" | "note" | "graph";
|
|
4
|
-
export const COLUMNS: readonly ColumnId[] = ["tree", "note", "graph"];
|
|
5
|
-
export type Columns<T> = { readonly [K in ColumnId]: T };
|
|
6
|
-
export const MIN_WIDTHS: Columns<number> = { tree: 180, note: 320, graph: 260 };
|
|
7
|
-
export const DEFAULT_FRACTIONS: Columns<number> = { tree: 0.22, note: 0.46, graph: 0.32 };
|
|
8
|
-
|
|
9
|
-
export type Breakpoint = "wide" | "medium" | "narrow";
|
|
10
|
-
export const BREAKPOINT_MEDIUM = 1100;
|
|
11
|
-
export const BREAKPOINT_NARROW = 800;
|
|
12
|
-
|
|
13
|
-
export function breakpointFor(width: number): Breakpoint {
|
|
14
|
-
if (!Number.isFinite(width) || width < BREAKPOINT_NARROW) return "narrow";
|
|
15
|
-
return width < BREAKPOINT_MEDIUM ? "medium" : "wide";
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function columnsAt(breakpoint: Breakpoint): readonly ColumnId[] {
|
|
19
|
-
return breakpoint === "wide" ? COLUMNS : breakpoint === "medium" ? ["tree", "note"] : ["note"];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function isCollapsed(breakpoint: Breakpoint, column: ColumnId): boolean {
|
|
23
|
-
return !columnsAt(breakpoint).includes(column);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface LayoutState { readonly fractions: Columns<number> }
|
|
27
|
-
|
|
28
|
-
function normalizeOver(ids: readonly ColumnId[], fractions: Columns<number>, floors: Columns<number>): Columns<number> {
|
|
29
|
-
const clean: Record<ColumnId, number> = { tree: 0, note: 0, graph: 0 };
|
|
30
|
-
let total = 0;
|
|
31
|
-
for (const id of ids) {
|
|
32
|
-
const value = fractions[id];
|
|
33
|
-
clean[id] = Number.isFinite(value) && value > 0 ? value : DEFAULT_FRACTIONS[id];
|
|
34
|
-
total += clean[id];
|
|
35
|
-
}
|
|
36
|
-
for (const id of ids) clean[id] /= total;
|
|
37
|
-
let deficit = 0;
|
|
38
|
-
let slack = 0;
|
|
39
|
-
const atFloor: Record<ColumnId, boolean> = { tree: false, note: false, graph: false };
|
|
40
|
-
for (const id of ids) {
|
|
41
|
-
const floor = floors[id];
|
|
42
|
-
if (clean[id] < floor) {
|
|
43
|
-
deficit += floor - clean[id];
|
|
44
|
-
clean[id] = floor;
|
|
45
|
-
atFloor[id] = true;
|
|
46
|
-
} else slack += clean[id] - floor;
|
|
47
|
-
}
|
|
48
|
-
if (deficit > 0 && slack > 0) {
|
|
49
|
-
const rate = Math.min(deficit, slack) / slack;
|
|
50
|
-
for (const id of ids) if (!atFloor[id]) clean[id] -= (clean[id] - floors[id]) * rate;
|
|
51
|
-
}
|
|
52
|
-
return { tree: clean.tree, note: clean.note, graph: clean.graph };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function normalizeFractions(fractions: Columns<number>, minShare: Columns<number>): Columns<number> {
|
|
56
|
-
return normalizeOver(COLUMNS, fractions, minShare);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function minShares(available: number): Columns<number> {
|
|
60
|
-
const width = Number.isFinite(available) && available > 0 ? available : 1;
|
|
61
|
-
const raw = { tree: MIN_WIDTHS.tree / width, note: MIN_WIDTHS.note / width, graph: MIN_WIDTHS.graph / width };
|
|
62
|
-
const total = raw.tree + raw.note + raw.graph;
|
|
63
|
-
if (total <= 0.9) return raw;
|
|
64
|
-
const scale = 0.9 / total;
|
|
65
|
-
return { tree: raw.tree * scale, note: raw.note * scale, graph: raw.graph * scale };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export function makeLayout(fractions: Columns<number>, available: number): LayoutState {
|
|
69
|
-
return { fractions: normalizeFractions(fractions, minShares(available)) };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function defaultLayout(available: number): LayoutState {
|
|
73
|
-
return makeLayout(DEFAULT_FRACTIONS, available);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface ResolvedColumn { readonly id: ColumnId; readonly width: number }
|
|
77
|
-
|
|
78
|
-
export function resolveColumns(state: LayoutState, available: number, breakpoint: Breakpoint): readonly ResolvedColumn[] {
|
|
79
|
-
const width = Number.isFinite(available) && available > 0 ? available : 0;
|
|
80
|
-
const shares = normalizeOver(columnsAt(breakpoint), state.fractions, minShares(width));
|
|
81
|
-
return columnsAt(breakpoint).map((id) => ({ id, width: shares[id] * width }));
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export type DividerId = "tree" | "note";
|
|
85
|
-
export const DIVIDERS: readonly DividerId[] = ["tree", "note"];
|
|
86
|
-
export function dividerPair(divider: DividerId): readonly [ColumnId, ColumnId] {
|
|
87
|
-
return divider === "tree" ? ["tree", "note"] : ["note", "graph"];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function resizeAt(state: LayoutState, divider: DividerId, deltaPx: number, available: number): LayoutState {
|
|
91
|
-
const width = Number.isFinite(available) && available > 0 ? available : 0;
|
|
92
|
-
if (width === 0 || !Number.isFinite(deltaPx) || deltaPx === 0) return state;
|
|
93
|
-
const [left, right] = dividerPair(divider);
|
|
94
|
-
const floors = minShares(width);
|
|
95
|
-
const delta = Math.max(-(Math.max(0, state.fractions[left] - floors[left])), Math.min(Math.max(0, state.fractions[right] - floors[right]), deltaPx / width));
|
|
96
|
-
if (delta === 0) return state;
|
|
97
|
-
return { fractions: normalizeFractions({ ...state.fractions, [left]: state.fractions[left] + delta, [right]: state.fractions[right] - delta }, floors) };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface LayoutStorage { getItem(key: string): string | null; setItem(key: string, value: string): void }
|
|
101
|
-
export const LAYOUT_STORAGE_KEY = "pi-weave.layout.v1";
|
|
102
|
-
const round4 = (value: number): number => Math.round(value * 10000) / 10000;
|
|
103
|
-
|
|
104
|
-
export function serializeLayout(state: LayoutState): string {
|
|
105
|
-
return JSON.stringify({ v: 1, tree: round4(state.fractions.tree), note: round4(state.fractions.note), graph: round4(state.fractions.graph) });
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export function deserializeLayout(raw: string | null, available: number): LayoutState | null {
|
|
109
|
-
if (raw === null) return null;
|
|
110
|
-
let parsed: unknown;
|
|
111
|
-
try { parsed = JSON.parse(raw); } catch { return null; }
|
|
112
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return null;
|
|
113
|
-
const record = parsed as Record<string, unknown>;
|
|
114
|
-
if (record["v"] !== 1) return null;
|
|
115
|
-
const fractions = {} as Record<ColumnId, number>;
|
|
116
|
-
for (const id of COLUMNS) {
|
|
117
|
-
const value = record[id];
|
|
118
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) return null;
|
|
119
|
-
fractions[id] = value;
|
|
120
|
-
}
|
|
121
|
-
return makeLayout(fractions, available);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function loadLayout(storage: LayoutStorage, available: number): LayoutState {
|
|
125
|
-
try { return deserializeLayout(storage.getItem(LAYOUT_STORAGE_KEY), available) ?? defaultLayout(available); }
|
|
126
|
-
catch { return defaultLayout(available); }
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function saveLayout(storage: LayoutStorage, state: LayoutState): boolean {
|
|
130
|
-
try { storage.setItem(LAYOUT_STORAGE_KEY, serializeLayout(state)); return true; }
|
|
131
|
-
catch { return false; }
|
|
132
|
-
}
|