vintage-frames 0.14.0 → 0.14.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 +16 -0
- package/custom-elements.json +177 -13
- package/dist/components/vf-container.d.ts +3 -1
- package/dist/components/vf-container.js +34 -16
- package/dist/components/vf-desktop.d.ts +114 -12
- package/dist/components/vf-desktop.js +119 -58
- package/dist/components/vf-icon-field.d.ts +1 -1
- package/dist/components/vf-icon.js +68 -61
- package/dist/components/vf-menu-bar.d.ts +5 -0
- package/dist/components/vf-menu-bar.js +67 -43
- package/dist/components/vf-window.d.ts +13 -0
- package/dist/components/vf-window.js +10 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +44 -43
- package/dist/pattern-fill.d.ts +11 -8
- package/dist/pattern-fill.js +2 -4
- package/dist/position.d.ts +25 -7
- package/dist/position.js +6 -3
- package/dist/shell/alert.d.ts +32 -0
- package/dist/shell/alert.js +44 -0
- package/dist/shell/catalog.d.ts +168 -0
- package/dist/shell/catalog.js +278 -0
- package/dist/shell/clock.d.ts +16 -0
- package/dist/shell/clock.js +36 -0
- package/dist/shell/filing.d.ts +39 -0
- package/dist/shell/filing.js +68 -0
- package/dist/shell/finder.d.ts +116 -0
- package/dist/shell/finder.js +564 -0
- package/dist/shell/geometry.d.ts +196 -0
- package/dist/shell/geometry.js +241 -0
- package/dist/shell/index.d.ts +29 -0
- package/dist/shell/index.js +11 -0
- package/dist/shell/shell.d.ts +150 -0
- package/dist/shell/shell.js +213 -0
- package/dist/shell/state.d.ts +88 -0
- package/dist/shell/state.js +103 -0
- package/dist/shell/storage.d.ts +14 -0
- package/dist/shell/storage.js +41 -0
- package/dist/shell/windows.d.ts +215 -0
- package/dist/shell/windows.js +343 -0
- package/dist/window-chrome.d.ts +43 -0
- package/dist/window-chrome.js +13 -0
- package/docs/SPEC.md +12 -11
- package/editor/vscode.html-custom-data.json +9 -4
- package/editor/web-types.json +45 -9
- package/package.json +10 -2
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell's geometry, pure and DOM-free: boxes in whole system px, the
|
|
3
|
+
* window cascade, the nine-slice pin that carries windows and icons through
|
|
4
|
+
* a raster resize, and the icon lattices that new items and Clean Up place
|
|
5
|
+
* on. Every function takes the area it works in — a desktop's work area, a
|
|
6
|
+
* folder's plane — so nothing here knows a menu bar's height.
|
|
7
|
+
*/
|
|
8
|
+
/** A point in whole system px. */
|
|
9
|
+
export interface Point {
|
|
10
|
+
left: number;
|
|
11
|
+
top: number;
|
|
12
|
+
}
|
|
13
|
+
/** A size in whole system px. */
|
|
14
|
+
export interface Size {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
}
|
|
18
|
+
/** A box in whole system px. */
|
|
19
|
+
export interface Box extends Point, Size {
|
|
20
|
+
}
|
|
21
|
+
/** How far each cascaded window steps down and right, system px. */
|
|
22
|
+
export declare const CASCADE_STEP = 24;
|
|
23
|
+
/** How many slots a cascade has before it wraps. */
|
|
24
|
+
export declare const CASCADE_SLOTS = 5;
|
|
25
|
+
/** A cascade's step and slot count. */
|
|
26
|
+
export interface CascadeOptions {
|
|
27
|
+
step?: number;
|
|
28
|
+
slots?: number;
|
|
29
|
+
}
|
|
30
|
+
/** Where a desktop's first window opens: this far in from its window area's corner. */
|
|
31
|
+
export declare const WINDOW_ORIGIN: Point;
|
|
32
|
+
/** The first cascade slot in a window area: {@link WINDOW_ORIGIN} in from its corner. */
|
|
33
|
+
export declare function windowOrigin(area: Box): Point;
|
|
34
|
+
/** Cascade slot `i` from `base`, wrapping past the slot count. */
|
|
35
|
+
export declare function cascadeSlot(base: Point, i: number, { step, slots }?: CascadeOptions): Point & {
|
|
36
|
+
slot: number;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The first cascade slot from `base` that no window in `occupied` (their
|
|
40
|
+
* top-lefts) holds. A window within half a step of a slot holds it, which
|
|
41
|
+
* absorbs snapping and clamping. Every slot held wraps rather than walking
|
|
42
|
+
* off the screen.
|
|
43
|
+
*/
|
|
44
|
+
export declare function cascadeFrom(base: Point, occupied: readonly Point[], options?: CascadeOptions): Point & {
|
|
45
|
+
slot: number;
|
|
46
|
+
};
|
|
47
|
+
/** A box of `size` centered in `area`, its top-left held at the area's. */
|
|
48
|
+
export declare function centeredBox(area: Box, size: Size): Box;
|
|
49
|
+
/**
|
|
50
|
+
* A box of `size` on cascade slot `n` from `origin`, pulled in to fit `area`
|
|
51
|
+
* but never past its top-left.
|
|
52
|
+
*/
|
|
53
|
+
export declare function cascadedBox(area: Box, size: Size, origin: Point, n?: number, options?: CascadeOptions): Box;
|
|
54
|
+
/** {@link nearBox}'s tolerance: more than a lattice snap, less than a real move. */
|
|
55
|
+
export declare const NEAR = 10;
|
|
56
|
+
/** Whether every edge of `box` is within `tol` of `target`'s. */
|
|
57
|
+
export declare function nearBox(box: Box, target: Box, tol?: number): boolean;
|
|
58
|
+
/** The least thickness of an outer slice, system px. The middle is the remainder. */
|
|
59
|
+
export declare const BAND = 100;
|
|
60
|
+
/** Each outer slice's thickness, system px. */
|
|
61
|
+
export interface Bands {
|
|
62
|
+
left: number;
|
|
63
|
+
top: number;
|
|
64
|
+
right: number;
|
|
65
|
+
bottom: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The frame a pin is read in: `reserve` is the band above the open area — a
|
|
69
|
+
* desktop's work area top, the pin's y = 0 line — and `bands` each outer
|
|
70
|
+
* slice's thickness.
|
|
71
|
+
*/
|
|
72
|
+
export interface Frame {
|
|
73
|
+
reserve: number;
|
|
74
|
+
bands: Bands;
|
|
75
|
+
}
|
|
76
|
+
/** A frame below `reserve`, its bands at least {@link BAND}, any of them wider. */
|
|
77
|
+
export declare function frameOf(reserve?: number, bands?: Partial<Bands>): Frame;
|
|
78
|
+
/**
|
|
79
|
+
* A resize policy per axis ({@link pinTo}): `size` fixes an axis at a size,
|
|
80
|
+
* `min` floors a resizable one.
|
|
81
|
+
*/
|
|
82
|
+
export interface Policy {
|
|
83
|
+
size?: {
|
|
84
|
+
width?: number;
|
|
85
|
+
height?: number;
|
|
86
|
+
};
|
|
87
|
+
min?: {
|
|
88
|
+
width?: number;
|
|
89
|
+
height?: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* One edge's pin. `near`: the offset from the span's start; `far`: from its
|
|
94
|
+
* end; `spring`: the unrounded fraction of the middle.
|
|
95
|
+
*/
|
|
96
|
+
export interface EdgePin {
|
|
97
|
+
kind: 'near' | 'far' | 'spring';
|
|
98
|
+
v: number;
|
|
99
|
+
}
|
|
100
|
+
/** A box's pin: per axis, the near edge (left or top), then the far edge. */
|
|
101
|
+
export interface Pin {
|
|
102
|
+
x: [EdgePin, EdgePin];
|
|
103
|
+
y: [EdgePin, EdgePin];
|
|
104
|
+
}
|
|
105
|
+
/** Whether `p` has a pin's shape — a stored or garbled record reads as none, so pinTo never throws. */
|
|
106
|
+
export declare function isPin(p: unknown): p is Pin;
|
|
107
|
+
/** A box's pin on `raster` in `frame`: each edge a strut or a spring. */
|
|
108
|
+
export declare function pinOf(box: Box, raster: Size, frame: Frame): Pin;
|
|
109
|
+
/**
|
|
110
|
+
* A pin back on `raster` as a box, in the frame it was read in, rounded to
|
|
111
|
+
* whole system px. The policy applies per axis: `size` fixes it, `min`
|
|
112
|
+
* floors it. Callers snap to their lattice and cap an oversize box.
|
|
113
|
+
*/
|
|
114
|
+
export declare function pinTo(pin: Pin, raster: Size, frame: Frame, { size, min }?: Policy): Box;
|
|
115
|
+
/** An icon's cell, system px: the 64px plate, name included. Two icons collide where their cells overlap. */
|
|
116
|
+
export declare const ICON_CELL = 64;
|
|
117
|
+
/** The lattice's pitches and its inset from the container, system px. */
|
|
118
|
+
export declare const ICON_ROW = 72;
|
|
119
|
+
export declare const ICON_COLUMN = 80;
|
|
120
|
+
export declare const ICON_INSET = 16;
|
|
121
|
+
/**
|
|
122
|
+
* A container's icon grid. `left`/`top` locate cell (0,0); `dx` is negative
|
|
123
|
+
* where columns run left; `cols` or `rows` is `Infinity` when unbounded;
|
|
124
|
+
* `down` fills a column at a time, otherwise a row at a time.
|
|
125
|
+
*/
|
|
126
|
+
export interface Lattice {
|
|
127
|
+
left: number;
|
|
128
|
+
top: number;
|
|
129
|
+
dx: number;
|
|
130
|
+
dy: number;
|
|
131
|
+
cols: number;
|
|
132
|
+
rows: number;
|
|
133
|
+
down: boolean;
|
|
134
|
+
}
|
|
135
|
+
/** A lattice's cell, pitches and inset from its container's edges, system px. */
|
|
136
|
+
export interface LatticeOptions {
|
|
137
|
+
cell?: number;
|
|
138
|
+
column?: number;
|
|
139
|
+
row?: number;
|
|
140
|
+
inset?: Partial<{
|
|
141
|
+
top: number;
|
|
142
|
+
right: number;
|
|
143
|
+
bottom: number;
|
|
144
|
+
left: number;
|
|
145
|
+
}>;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The desktop's lattice in its work area: cell (0,0) at the top right, rows
|
|
149
|
+
* running down to the bottom inset, columns running left. At least one row
|
|
150
|
+
* and one column. Insets default to 16 on the top, right and bottom.
|
|
151
|
+
*/
|
|
152
|
+
export declare function desktopLattice(area: Box, options?: LatticeOptions): Lattice;
|
|
153
|
+
/**
|
|
154
|
+
* The Trash's place in a desktop's work area: the bottom-right cell, inset
|
|
155
|
+
* as the desktop's lattice is, held inside the area's top-left.
|
|
156
|
+
*/
|
|
157
|
+
export declare function trashCell(area: Box, options?: LatticeOptions): Point;
|
|
158
|
+
/**
|
|
159
|
+
* A folder plane's lattice for a plane `width` wide: rows from the top left,
|
|
160
|
+
* wrapping at the width, unbounded downward. At least one column. Insets
|
|
161
|
+
* default to 16 on the top and left.
|
|
162
|
+
*/
|
|
163
|
+
export declare function folderLattice(width: number, options?: LatticeOptions): Lattice;
|
|
164
|
+
/** The top-left of cell (`col`, `row`), whether or not the lattice reaches it. */
|
|
165
|
+
export declare function latticeCell(grid: Lattice, col: number, row: number): Point;
|
|
166
|
+
/** Cell number `slot` in the lattice's fill order. */
|
|
167
|
+
export declare function latticeSlot(grid: Lattice, slot: number): Point;
|
|
168
|
+
/**
|
|
169
|
+
* Two positions in the lattice's fill order, negative when `a` comes first.
|
|
170
|
+
* Each rounds to its nearest cell, so a pixel of drift still sorts right.
|
|
171
|
+
*/
|
|
172
|
+
export declare function fillOrder(grid: Lattice, a: Point, b: Point): number;
|
|
173
|
+
/**
|
|
174
|
+
* Clean Up: each position's cell on the lattice. Each icon takes the free
|
|
175
|
+
* cell nearest it, searching square rings outward from its own rounded,
|
|
176
|
+
* clamped cell; icons past the last free cell share their own. One position
|
|
177
|
+
* per input, in input order.
|
|
178
|
+
*/
|
|
179
|
+
export declare function cleanUp(grid: Lattice, positions: readonly Point[]): Point[];
|
|
180
|
+
/**
|
|
181
|
+
* The first cell, in fill order, that no icon at `taken` overlaps — an icon
|
|
182
|
+
* off the lattice blocks every cell its own cell touches. The home cell when
|
|
183
|
+
* a bounded lattice is full.
|
|
184
|
+
*/
|
|
185
|
+
export declare function nextFreeCell(grid: Lattice, taken: readonly Point[], cell?: number): Point;
|
|
186
|
+
/**
|
|
187
|
+
* A folder's field: at least its viewport, grown to hold every icon at
|
|
188
|
+
* `positions` (their top-lefts) plus the inset past it. The field's size is
|
|
189
|
+
* the window's scroll range.
|
|
190
|
+
*/
|
|
191
|
+
export declare function fieldExtent(positions: readonly Point[], viewport: Size, { cell, inset }?: {
|
|
192
|
+
cell?: number;
|
|
193
|
+
inset?: number;
|
|
194
|
+
}): Size;
|
|
195
|
+
/** The pairs of `boxes` that overlap, as `"i,j"` with i < j. Boxes sharing only an edge do not. */
|
|
196
|
+
export declare function collisions(boxes: readonly Box[]): Set<string>;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
//#region src/shell/geometry.ts
|
|
2
|
+
var e = 24, t = 5, n = {
|
|
3
|
+
left: 40,
|
|
4
|
+
top: 20
|
|
5
|
+
};
|
|
6
|
+
function r(e) {
|
|
7
|
+
return {
|
|
8
|
+
left: e.left + n.left,
|
|
9
|
+
top: e.top + n.top
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function i(e, t, { step: n = 24, slots: r = 5 } = {}) {
|
|
13
|
+
let i = (t % r + r) % r;
|
|
14
|
+
return {
|
|
15
|
+
left: e.left + n * i,
|
|
16
|
+
top: e.top + n * i,
|
|
17
|
+
slot: i
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function a(e, t, n = {}) {
|
|
21
|
+
let { step: r = 24, slots: a = 5 } = n, o = r / 2;
|
|
22
|
+
for (let r = 0; r < a; r++) {
|
|
23
|
+
let a = i(e, r, n);
|
|
24
|
+
if (!t.some((e) => Math.abs(e.left - a.left) < o && Math.abs(e.top - a.top) < o)) return a;
|
|
25
|
+
}
|
|
26
|
+
return i(e, t.length % a, n);
|
|
27
|
+
}
|
|
28
|
+
function o(e, t) {
|
|
29
|
+
return {
|
|
30
|
+
left: Math.max(e.left, e.left + Math.floor((e.width - t.width) / 2)),
|
|
31
|
+
top: Math.max(e.top, e.top + Math.floor((e.height - t.height) / 2)),
|
|
32
|
+
width: t.width,
|
|
33
|
+
height: t.height
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function s(e, t, n, r = 0, a = {}) {
|
|
37
|
+
let o = i(n, r, a);
|
|
38
|
+
return {
|
|
39
|
+
left: Math.max(e.left, Math.min(o.left, e.left + e.width - t.width)),
|
|
40
|
+
top: Math.max(e.top, Math.min(o.top, e.top + e.height - t.height)),
|
|
41
|
+
width: t.width,
|
|
42
|
+
height: t.height
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
var c = 10;
|
|
46
|
+
function l(e, t, n = 10) {
|
|
47
|
+
return Math.abs(e.left - t.left) <= n && Math.abs(e.top - t.top) <= n && Math.abs(e.left + e.width - (t.left + t.width)) <= n && Math.abs(e.top + e.height - (t.top + t.height)) <= n;
|
|
48
|
+
}
|
|
49
|
+
var u = 100;
|
|
50
|
+
function d(e = 0, t = {}) {
|
|
51
|
+
let n = (e) => Math.max(100, e ?? 100);
|
|
52
|
+
return {
|
|
53
|
+
reserve: e,
|
|
54
|
+
bands: {
|
|
55
|
+
left: n(t.left),
|
|
56
|
+
top: n(t.top),
|
|
57
|
+
right: n(t.right),
|
|
58
|
+
bottom: n(t.bottom)
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function f(e) {
|
|
63
|
+
let t = (e) => {
|
|
64
|
+
let t = e;
|
|
65
|
+
return !!t && (t.kind === "near" || t.kind === "far" || t.kind === "spring") && Number.isFinite(t.v);
|
|
66
|
+
}, n = (e) => Array.isArray(e) && e.length === 2 && t(e[0]) && t(e[1]), r = e;
|
|
67
|
+
return !!r && typeof r == "object" && n(r.x) && n(r.y);
|
|
68
|
+
}
|
|
69
|
+
function p(e, t, n, r) {
|
|
70
|
+
return e < n ? {
|
|
71
|
+
kind: "near",
|
|
72
|
+
v: e
|
|
73
|
+
} : e >= t - r ? {
|
|
74
|
+
kind: "far",
|
|
75
|
+
v: t - e
|
|
76
|
+
} : {
|
|
77
|
+
kind: "spring",
|
|
78
|
+
v: (e - n) / Math.max(1, t - n - r)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function m(e, t, n, r) {
|
|
82
|
+
return e.kind === "near" ? e.v : e.kind === "far" ? t - e.v : n + e.v * Math.max(0, t - n - r);
|
|
83
|
+
}
|
|
84
|
+
function h(e, t, n, r, { size: i, min: a = 0 }) {
|
|
85
|
+
let o = m(e[0], t, n, r), s = m(e[1], t, n, r), c = i;
|
|
86
|
+
if (c == null) {
|
|
87
|
+
if (s - o >= a) return {
|
|
88
|
+
a: o,
|
|
89
|
+
size: s - o
|
|
90
|
+
};
|
|
91
|
+
c = a;
|
|
92
|
+
}
|
|
93
|
+
return e[0].kind === "spring" ? e[1].kind === "spring" ? {
|
|
94
|
+
a: (o + s) / 2 - c / 2,
|
|
95
|
+
size: c
|
|
96
|
+
} : {
|
|
97
|
+
a: s - c,
|
|
98
|
+
size: c
|
|
99
|
+
} : {
|
|
100
|
+
a: o,
|
|
101
|
+
size: c
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function g(e, t, n) {
|
|
105
|
+
let { reserve: r, bands: i } = n, a = t.width, o = t.height - r, s = e.top - r;
|
|
106
|
+
return {
|
|
107
|
+
x: [p(e.left, a, i.left, i.right), p(e.left + e.width, a, i.left, i.right)],
|
|
108
|
+
y: [p(s, o, i.top, i.bottom), p(s + e.height, o, i.top, i.bottom)]
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function _(e, t, n, { size: r, min: i } = {}) {
|
|
112
|
+
let { reserve: a, bands: o } = n, s = h(e.x, t.width, o.left, o.right, {
|
|
113
|
+
size: r?.width,
|
|
114
|
+
min: i?.width
|
|
115
|
+
}), c = h(e.y, t.height - a, o.top, o.bottom, {
|
|
116
|
+
size: r?.height,
|
|
117
|
+
min: i?.height
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
left: Math.round(s.a),
|
|
121
|
+
top: Math.round(a + c.a),
|
|
122
|
+
width: Math.round(s.size),
|
|
123
|
+
height: Math.round(c.size)
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
var v = 64, y = 72, b = 80, x = 16;
|
|
127
|
+
function S(e, t = {}) {
|
|
128
|
+
let { cell: n = 64, column: r = 80, row: i = 72 } = t, a = {
|
|
129
|
+
top: 16,
|
|
130
|
+
right: 16,
|
|
131
|
+
bottom: 16,
|
|
132
|
+
left: 0,
|
|
133
|
+
...t.inset
|
|
134
|
+
}, o = e.top + e.height, s = Math.max(e.left + a.left, e.left + e.width - a.right - n), c = e.top + a.top;
|
|
135
|
+
return {
|
|
136
|
+
left: s,
|
|
137
|
+
top: c,
|
|
138
|
+
dx: -r,
|
|
139
|
+
dy: i,
|
|
140
|
+
cols: Math.floor((s - e.left - a.left) / r) + 1,
|
|
141
|
+
rows: Math.max(1, Math.floor((o - a.bottom - n - c) / i) + 1),
|
|
142
|
+
down: !0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function C(e, t = {}) {
|
|
146
|
+
let { cell: n = 64 } = t, r = {
|
|
147
|
+
right: 16,
|
|
148
|
+
bottom: 16,
|
|
149
|
+
left: 0,
|
|
150
|
+
...t.inset
|
|
151
|
+
};
|
|
152
|
+
return {
|
|
153
|
+
left: Math.max(e.left + r.left, e.left + e.width - r.right - n),
|
|
154
|
+
top: Math.max(e.top, e.top + e.height - r.bottom - n)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function w(e, t = {}) {
|
|
158
|
+
let { cell: n = 64, column: r = 80, row: i = 72 } = t, a = {
|
|
159
|
+
top: 16,
|
|
160
|
+
right: 0,
|
|
161
|
+
bottom: 0,
|
|
162
|
+
left: 16,
|
|
163
|
+
...t.inset
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
left: a.left,
|
|
167
|
+
top: a.top,
|
|
168
|
+
dx: r,
|
|
169
|
+
dy: i,
|
|
170
|
+
cols: Math.max(1, Math.floor((e - a.left - a.right - n) / r) + 1),
|
|
171
|
+
rows: Infinity,
|
|
172
|
+
down: !1
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function T(e, t, n) {
|
|
176
|
+
return {
|
|
177
|
+
left: e.left + t * e.dx,
|
|
178
|
+
top: e.top + n * e.dy
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
function E(e, t) {
|
|
182
|
+
let n = e.down ? e.rows : e.cols, r = Math.floor(t / n), i = t % n;
|
|
183
|
+
return e.down ? T(e, r, i) : T(e, i, r);
|
|
184
|
+
}
|
|
185
|
+
function D(e, t, n) {
|
|
186
|
+
let r = (t) => Math.round((t.left - e.left) / e.dx), i = (t) => Math.round((t.top - e.top) / e.dy);
|
|
187
|
+
return e.down ? r(t) - r(n) || i(t) - i(n) : i(t) - i(n) || r(t) - r(n);
|
|
188
|
+
}
|
|
189
|
+
var O = (e, t) => Math.min(Math.max(Math.round(e), 0), t - 1);
|
|
190
|
+
function k(e, t) {
|
|
191
|
+
let { cols: n, rows: r } = e, i = t.map((t) => ({
|
|
192
|
+
col: O((t.left - e.left) / e.dx, n),
|
|
193
|
+
row: O((t.top - e.top) / e.dy, r)
|
|
194
|
+
})), a = (e) => i[e], o = t.map((e, t) => t).sort((e, t) => a(e).row - a(t).row || a(e).col - a(t).col || e - t), s = /* @__PURE__ */ new Set(), c = Math.min(n, t.length + 1) + Math.min(r, t.length + 1), l = [];
|
|
195
|
+
for (let i of o) {
|
|
196
|
+
let o = a(i), u = t[i], d = o;
|
|
197
|
+
for (let t = 0; t <= c; t++) {
|
|
198
|
+
let i = null, a = Infinity;
|
|
199
|
+
for (let c = o.col - t; c <= o.col + t; c++) for (let l = o.row - t; l <= o.row + t; l++) {
|
|
200
|
+
if (Math.max(Math.abs(c - o.col), Math.abs(l - o.row)) !== t || c < 0 || l < 0 || c >= n || l >= r || s.has(`${c},${l}`)) continue;
|
|
201
|
+
let d = T(e, c, l), f = (d.left - u.left) ** 2 + (d.top - u.top) ** 2;
|
|
202
|
+
f < a && (a = f, i = {
|
|
203
|
+
col: c,
|
|
204
|
+
row: l
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (i) {
|
|
208
|
+
d = i;
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
s.add(`${d.col},${d.row}`), l[i] = T(e, d.col, d.row);
|
|
213
|
+
}
|
|
214
|
+
return l;
|
|
215
|
+
}
|
|
216
|
+
function A(e, t, n = 64) {
|
|
217
|
+
let r = e.cols * e.rows, i = Number.isFinite(r) ? r : 4 * t.length + 1;
|
|
218
|
+
for (let r = 0; r < i; r++) {
|
|
219
|
+
let i = E(e, r);
|
|
220
|
+
if (!t.some((e) => Math.abs(e.left - i.left) < n && Math.abs(e.top - i.top) < n)) return i;
|
|
221
|
+
}
|
|
222
|
+
return E(e, 0);
|
|
223
|
+
}
|
|
224
|
+
function j(e, t, { cell: n = 64, inset: r = 16 } = {}) {
|
|
225
|
+
let i = t.width, a = t.height;
|
|
226
|
+
for (let t of e) i = Math.max(i, t.left + n + r), a = Math.max(a, t.top + n + r);
|
|
227
|
+
return {
|
|
228
|
+
width: i,
|
|
229
|
+
height: a
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
function M(e) {
|
|
233
|
+
let t = /* @__PURE__ */ new Set();
|
|
234
|
+
for (let n = 0; n < e.length; n++) for (let r = n + 1; r < e.length; r++) {
|
|
235
|
+
let i = e[n], a = e[r];
|
|
236
|
+
i.left < a.left + a.width && a.left < i.left + i.width && i.top < a.top + a.height && a.top < i.top + i.height && t.add(`${n},${r}`);
|
|
237
|
+
}
|
|
238
|
+
return t;
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
export { u as BAND, t as CASCADE_SLOTS, e as CASCADE_STEP, v as ICON_CELL, b as ICON_COLUMN, x as ICON_INSET, y as ICON_ROW, c as NEAR, n as WINDOW_ORIGIN, a as cascadeFrom, i as cascadeSlot, s as cascadedBox, o as centeredBox, k as cleanUp, M as collisions, S as desktopLattice, j as fieldExtent, D as fillOrder, w as folderLattice, d as frameOf, f as isPin, T as latticeCell, E as latticeSlot, l as nearBox, A as nextFreeCell, g as pinOf, _ as pinTo, C as trashCell, r as windowOrigin };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `vintage-frames/shell` — experimental. Applications over one desktop: a
|
|
3
|
+
* window manager, a menu bar that shows the front application's menus, a
|
|
4
|
+
* catalog of what is where, and the stock Finder that shows it. Plain
|
|
5
|
+
* modules over the kit's elements: it registers no elements, writes no
|
|
6
|
+
* styles and ships no art, and it reaches the kit only through the
|
|
7
|
+
* package's own exports, so the page imports `vintage-frames` first.
|
|
8
|
+
*
|
|
9
|
+
* Its API may change in any minor release while it is experimental.
|
|
10
|
+
* docs/SHELL.md is the guide.
|
|
11
|
+
*/
|
|
12
|
+
export { createShell, defineApp, appIdOf, APP_KIND } from './shell.js';
|
|
13
|
+
export type { AppContext, AppDefinition, CatalogSetup, KindDefinition, Shell, ShellOptions } from './shell.js';
|
|
14
|
+
export { createWindowManager, focusInto } from './windows.js';
|
|
15
|
+
export type { AdoptOptions, ArrangeGroup, BoxInput, FocusHome, HomeBox, OpenOptions, PlacePolicy, Placed, WindowManager, WindowManagerOptions, } from './windows.js';
|
|
16
|
+
export { finder, FINDER } from './finder.js';
|
|
17
|
+
export type { FinderApi, FinderArt, FinderCommand, FinderCommandSpec, FinderMenu, FinderOpen, FinderOptions, } from './finder.js';
|
|
18
|
+
export { fileByDrag } from './filing.js';
|
|
19
|
+
export type { FilingOptions } from './filing.js';
|
|
20
|
+
export { createCatalog, memoryStorage, childrenOf, containerOf, copyName, descendantsOf, enclosingFolders, isContainerKind, isInside, isTrashed, isVolume, itemCount, itemOf, nextFolderName, DISK, FOLDER, TRASH, UNTITLED_FOLDER, } from './catalog.js';
|
|
21
|
+
export type { Catalog, CatalogArchive, CatalogOptions, CatalogState, CatalogStorage, Item, KindHooks, NewItem, Volumes, } from './catalog.js';
|
|
22
|
+
export { indexedDbStorage } from './storage.js';
|
|
23
|
+
export { localStorageState, mergeSession, openWindowsOf, readSession } from './state.js';
|
|
24
|
+
export type { LocalStorageStateOptions, SavedSession, SavedWindow, SessionSnapshot, ShellState } from './state.js';
|
|
25
|
+
export { BAND, CASCADE_SLOTS, CASCADE_STEP, ICON_CELL, ICON_COLUMN, ICON_INSET, ICON_ROW, NEAR, WINDOW_ORIGIN, cascadedBox, cascadeFrom, cascadeSlot, centeredBox, cleanUp, collisions, desktopLattice, fieldExtent, fillOrder, folderLattice, frameOf, isPin, latticeCell, latticeSlot, nearBox, nextFreeCell, pinOf, pinTo, trashCell, windowOrigin, } from './geometry.js';
|
|
26
|
+
export type { Bands, Box, CascadeOptions, EdgePin, Frame, Lattice, LatticeOptions, Pin, Point, Policy, Size, } from './geometry.js';
|
|
27
|
+
export { startClock, formatDate, formatTime, DATE_HOLD_MS } from './clock.js';
|
|
28
|
+
export { showAlert } from './alert.js';
|
|
29
|
+
export type { AlertButton, AlertOptions } from './alert.js';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DISK as e, FOLDER as t, TRASH as n, UNTITLED_FOLDER as r, childrenOf as i, containerOf as a, copyName as o, createCatalog as s, descendantsOf as c, enclosingFolders as l, isContainerKind as u, isInside as d, isTrashed as f, isVolume as p, itemCount as m, itemOf as h, memoryStorage as g, nextFolderName as _ } from "./catalog.js";
|
|
2
|
+
import { BAND as v, CASCADE_SLOTS as y, CASCADE_STEP as b, ICON_CELL as x, ICON_COLUMN as S, ICON_INSET as C, ICON_ROW as w, NEAR as T, WINDOW_ORIGIN as E, cascadeFrom as D, cascadeSlot as O, cascadedBox as k, centeredBox as A, cleanUp as j, collisions as M, desktopLattice as N, fieldExtent as P, fillOrder as F, folderLattice as I, frameOf as L, isPin as R, latticeCell as z, latticeSlot as B, nearBox as V, nextFreeCell as H, pinOf as U, pinTo as W, trashCell as G, windowOrigin as K } from "./geometry.js";
|
|
3
|
+
import { createWindowManager as q, focusInto as J } from "./windows.js";
|
|
4
|
+
import { localStorageState as Y, mergeSession as X, openWindowsOf as Z, readSession as Q } from "./state.js";
|
|
5
|
+
import { DATE_HOLD_MS as $, formatDate as ee, formatTime as te, startClock as ne } from "./clock.js";
|
|
6
|
+
import { showAlert as re } from "./alert.js";
|
|
7
|
+
import { APP_KIND as ie, appIdOf as ae, createShell as oe, defineApp as se } from "./shell.js";
|
|
8
|
+
import { fileByDrag as ce } from "./filing.js";
|
|
9
|
+
import { FINDER as le, finder as ue } from "./finder.js";
|
|
10
|
+
import { indexedDbStorage as de } from "./storage.js";
|
|
11
|
+
export { ie as APP_KIND, v as BAND, y as CASCADE_SLOTS, b as CASCADE_STEP, $ as DATE_HOLD_MS, e as DISK, le as FINDER, t as FOLDER, x as ICON_CELL, S as ICON_COLUMN, C as ICON_INSET, w as ICON_ROW, T as NEAR, n as TRASH, r as UNTITLED_FOLDER, E as WINDOW_ORIGIN, ae as appIdOf, D as cascadeFrom, O as cascadeSlot, k as cascadedBox, A as centeredBox, i as childrenOf, j as cleanUp, M as collisions, a as containerOf, o as copyName, s as createCatalog, oe as createShell, q as createWindowManager, se as defineApp, c as descendantsOf, N as desktopLattice, l as enclosingFolders, P as fieldExtent, ce as fileByDrag, F as fillOrder, ue as finder, J as focusInto, I as folderLattice, ee as formatDate, te as formatTime, L as frameOf, de as indexedDbStorage, u as isContainerKind, d as isInside, R as isPin, f as isTrashed, p as isVolume, m as itemCount, h as itemOf, z as latticeCell, B as latticeSlot, Y as localStorageState, g as memoryStorage, X as mergeSession, V as nearBox, _ as nextFolderName, H as nextFreeCell, Z as openWindowsOf, U as pinOf, W as pinTo, Q as readSession, re as showAlert, ne as startClock, G as trashCell, K as windowOrigin };
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shell: applications over one desktop.
|
|
3
|
+
*
|
|
4
|
+
* - The menu bar holds the system menu — the page's own first menu, the
|
|
5
|
+
* Apple menu — then the front application's menus, then the clock in the
|
|
6
|
+
* bar's `end` slot. Each application's menus are parsed once and the same
|
|
7
|
+
* nodes come back each time it is front, so items keep their state. A menu
|
|
8
|
+
* off the bar has no key equivalents, so only the front application's fire.
|
|
9
|
+
* The bar's label is the front application's name.
|
|
10
|
+
* - Each application's `init(ctx)` gets the desktop, the window manager, its
|
|
11
|
+
* own menus, the system menu, the other applications' actions (read at the
|
|
12
|
+
* call), the catalog when a Finder runs, the site's services, `alert()`,
|
|
13
|
+
* `gate()`, `onFront()` and `onDispose()`. Everything it sets up comes down
|
|
14
|
+
* through `onDispose()`, so a hot reload starts clean.
|
|
15
|
+
* - A window showing a catalog item follows it: its title follows a rename,
|
|
16
|
+
* and it closes when the item is removed.
|
|
17
|
+
* - The boot reads the catalog, seeds it once, reopens the last session's
|
|
18
|
+
* windows deepest first and the active one last, then starts saving.
|
|
19
|
+
*/
|
|
20
|
+
import type { VfDesktop, VfMenu, VfMenuItem, VfViewportBox } from '../index.js';
|
|
21
|
+
import type { Catalog, CatalogStorage, Item, Volumes } from './catalog.js';
|
|
22
|
+
import type { WindowManager } from './windows.js';
|
|
23
|
+
import type { ShellState } from './state.js';
|
|
24
|
+
import type { AlertOptions } from './alert.js';
|
|
25
|
+
/** A kind of catalog item an application registers: how it looks and opens. */
|
|
26
|
+
export interface KindDefinition {
|
|
27
|
+
/** Its 32×32 art: one URL, or one per item. */
|
|
28
|
+
art: string | ((item: Item) => string);
|
|
29
|
+
/** Open the item, its window out of `from` (its icon's box). */
|
|
30
|
+
open(item: Item, from: VfViewportBox | null): void;
|
|
31
|
+
/** Renameable in place. Default true. */
|
|
32
|
+
editable?: boolean;
|
|
33
|
+
/** Bytes, for Empty Trash's alert. */
|
|
34
|
+
size?(item: Item): number;
|
|
35
|
+
/** Copy `from`'s payload to `to` when the Finder copies the item. */
|
|
36
|
+
copy?(from: Item, to: Item): void | Promise<void>;
|
|
37
|
+
/** Empty Trash removed the item: drop its payload. */
|
|
38
|
+
onRemove?(item: Item): void | Promise<void>;
|
|
39
|
+
/** Make an item of a pasted or dropped file in `parent`; resolves whether it did. */
|
|
40
|
+
claim?(file: Blob, parent: string | null): Promise<boolean>;
|
|
41
|
+
/** The item on the system clipboard, as a file. */
|
|
42
|
+
export?(item: Item): Promise<Blob | null>;
|
|
43
|
+
}
|
|
44
|
+
/** The catalog a Finder brings: where it is stored, its volumes and its seed. */
|
|
45
|
+
export interface CatalogSetup {
|
|
46
|
+
storage: CatalogStorage | null;
|
|
47
|
+
volumes?: Volumes;
|
|
48
|
+
/** Run once per storage, to store the site's defaults. */
|
|
49
|
+
seed?: (catalog: Catalog) => void | Promise<void>;
|
|
50
|
+
}
|
|
51
|
+
/** One application. */
|
|
52
|
+
export interface AppDefinition<Actions = unknown> {
|
|
53
|
+
id: string;
|
|
54
|
+
/** The bar's accessible name while it is front. */
|
|
55
|
+
name: string;
|
|
56
|
+
/** Its 32×32 art, for the Finder's icon of it. */
|
|
57
|
+
icon?: string;
|
|
58
|
+
/** A fragment of `vf-menu` elements, each with a `data-menu` name. */
|
|
59
|
+
menus?: string;
|
|
60
|
+
/** The catalog kinds it opens. */
|
|
61
|
+
kinds?: Record<string, KindDefinition>;
|
|
62
|
+
/** The Finder's: the catalog every application shares. */
|
|
63
|
+
catalog?: CatalogSetup;
|
|
64
|
+
/**
|
|
65
|
+
* Set everything up and return what other applications may call. An
|
|
66
|
+
* application that reopens windows across a reload — or opens from an
|
|
67
|
+
* icon — provides `open({ item, from })`.
|
|
68
|
+
*/
|
|
69
|
+
init(ctx: AppContext): Actions | void;
|
|
70
|
+
}
|
|
71
|
+
/** What an application's `init` gets. */
|
|
72
|
+
export interface AppContext {
|
|
73
|
+
readonly desktop: VfDesktop;
|
|
74
|
+
readonly windows: WindowManager;
|
|
75
|
+
/** Its own menus, parsed from `menus`. */
|
|
76
|
+
readonly menus: VfMenu[];
|
|
77
|
+
/** The page's system menu, for the items applications install. */
|
|
78
|
+
readonly systemMenu: VfMenu;
|
|
79
|
+
/** Every application's actions by id, read at the call. */
|
|
80
|
+
readonly apps: Record<string, any>;
|
|
81
|
+
/** The catalog, when a Finder runs; read before the boot has refreshed it, it lists the volumes alone. */
|
|
82
|
+
readonly catalog: Catalog | null;
|
|
83
|
+
/** The site's own services. */
|
|
84
|
+
readonly services: Record<string, any>;
|
|
85
|
+
/** The saved session, or null. */
|
|
86
|
+
readonly state: ShellState | null;
|
|
87
|
+
/** Every kind the applications register, the shell's `app` kind included, in registration order. */
|
|
88
|
+
readonly kinds: ReadonlyMap<string, KindDefinition>;
|
|
89
|
+
/** A kind's definition, or undefined. */
|
|
90
|
+
kind(kind: string): KindDefinition | undefined;
|
|
91
|
+
/** The application definition with `id`. */
|
|
92
|
+
app(id: string): AppDefinition | undefined;
|
|
93
|
+
/** One of its menus by `data-menu`; throws when the markup drifts. */
|
|
94
|
+
menu(name: string): VfMenu;
|
|
95
|
+
/** One of its menu items by `value`; throws when the markup drifts. */
|
|
96
|
+
item(value: string): VfMenuItem;
|
|
97
|
+
/** Every pick from its menus — never while a modal is open. */
|
|
98
|
+
onMenu(fn: (value: string, item: VfMenuItem) => void): void;
|
|
99
|
+
/** Put an item in the system menu, which runs `fn` when picked. */
|
|
100
|
+
systemItem(value: string, label: string, fn: () => void): VfMenuItem;
|
|
101
|
+
/** Show an alert with the site's caution art; resolves the button's value. */
|
|
102
|
+
alert(message: string, options?: AlertOptions): Promise<string | null>;
|
|
103
|
+
/** Keep `item` disabled while `test` is false: re-run on every press, key, selection change and activation. */
|
|
104
|
+
gate(item: VfMenuItem, test: () => boolean): void;
|
|
105
|
+
/** Whether a text field has keyboard focus, read through shadow roots at the call. */
|
|
106
|
+
typing(): boolean;
|
|
107
|
+
/** Whether a modal dialog is open. */
|
|
108
|
+
modalOpen(): boolean;
|
|
109
|
+
/** Follow whether this application is front; called at once with the current state. */
|
|
110
|
+
onFront(fn: (front: boolean) => void): void;
|
|
111
|
+
/** A listener torn down with the application. */
|
|
112
|
+
on(target: EventTarget, type: string, fn: (e: Event) => void, options?: boolean | AddEventListenerOptions): void;
|
|
113
|
+
/** Run `fn` when the shell is disposed. */
|
|
114
|
+
onDispose(fn: () => void): void;
|
|
115
|
+
}
|
|
116
|
+
/** Declare an application, with its actions' type. */
|
|
117
|
+
export declare function defineApp<Actions>(definition: AppDefinition<Actions>): AppDefinition<Actions>;
|
|
118
|
+
export interface ShellOptions {
|
|
119
|
+
/** The applications; the first with a `catalog` is the Finder. */
|
|
120
|
+
apps: AppDefinition<any>[];
|
|
121
|
+
/** The application front while no window is active. Default the Finder, else the first. */
|
|
122
|
+
defaultApp?: string;
|
|
123
|
+
/** `'viewport'` sizes the desktop to the viewport on every resize and scale change. */
|
|
124
|
+
fit?: 'viewport' | false;
|
|
125
|
+
/** Where the session is saved; null keeps nothing, so a reload resets. */
|
|
126
|
+
state?: ShellState | null;
|
|
127
|
+
/** The site's own services, handed to every application. */
|
|
128
|
+
services?: Record<string, unknown>;
|
|
129
|
+
/** The clock in the menu bar's `end` slot. Default true. */
|
|
130
|
+
clock?: boolean;
|
|
131
|
+
/** The 32×32 caution art the alerts carry. */
|
|
132
|
+
caution?: string | null;
|
|
133
|
+
}
|
|
134
|
+
/** A running shell. */
|
|
135
|
+
export interface Shell {
|
|
136
|
+
readonly desktop: VfDesktop;
|
|
137
|
+
readonly windows: WindowManager;
|
|
138
|
+
readonly catalog: Catalog | null;
|
|
139
|
+
readonly apps: Record<string, any>;
|
|
140
|
+
/** Settles once the boot has read the catalog and reopened the session. */
|
|
141
|
+
readonly ready: Promise<void>;
|
|
142
|
+
alert(message: string, options?: AlertOptions): Promise<string | null>;
|
|
143
|
+
dispose(): void;
|
|
144
|
+
}
|
|
145
|
+
/** The kind the shell registers: an application's icon, which opens it. */
|
|
146
|
+
export declare const APP_KIND = "app";
|
|
147
|
+
/** Start the shell over `desktop`. The page has imported `vintage-frames` first. */
|
|
148
|
+
export declare function createShell(desktop: VfDesktop, options: ShellOptions): Shell;
|
|
149
|
+
/** The application an `app` item stands for. */
|
|
150
|
+
export declare function appIdOf(item: Item): string | null;
|