flexdesk 0.17.18 → 0.18.0
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/CHANGELOG.md +53 -0
- package/README.md +1 -0
- package/css/flexdesk.css +7544 -5916
- package/css/reset.css +1 -1
- package/css/tokens.css +23 -1
- package/dist/charts.js +10 -6
- package/dist/charts.js.map +1 -1
- package/dist/chunk-5W4EEKSK.js +778 -0
- package/dist/chunk-5W4EEKSK.js.map +7 -0
- package/dist/chunk-DNJ3N4T4.js +82 -0
- package/dist/chunk-DNJ3N4T4.js.map +7 -0
- package/dist/{chunk-REJ5HLIM.js → chunk-HYJYYEPK.js} +2 -2
- package/dist/{chunk-7U4IB65H.js → chunk-LLBJ2SJH.js} +5 -3
- package/dist/{chunk-7U4IB65H.js.map → chunk-LLBJ2SJH.js.map} +1 -1
- package/dist/{chunk-7OL6F2AM.js → chunk-P74F6I6P.js} +6 -4
- package/dist/{chunk-7OL6F2AM.js.map → chunk-P74F6I6P.js.map} +1 -1
- package/dist/chunk-PAI2ZUWV.js +260 -0
- package/dist/chunk-PAI2ZUWV.js.map +7 -0
- package/dist/chunk-Q7BALEPX.js +527 -0
- package/dist/chunk-Q7BALEPX.js.map +7 -0
- package/dist/chunk-QXAOTGCC.js +40 -0
- package/dist/chunk-QXAOTGCC.js.map +7 -0
- package/dist/{chunk-XJNADRGR.js → chunk-RKVYIXDC.js} +664 -91
- package/dist/chunk-RKVYIXDC.js.map +7 -0
- package/dist/{chunk-2Z2ENHR3.js → chunk-U5GKJUOC.js} +3 -3
- package/dist/{chunk-2Z2ENHR3.js.map → chunk-U5GKJUOC.js.map} +1 -1
- package/dist/chunk-UKP22RUG.js +219 -0
- package/dist/chunk-UKP22RUG.js.map +7 -0
- package/dist/chunk-VLOAI3HS.js +43 -0
- package/dist/chunk-VLOAI3HS.js.map +7 -0
- package/dist/{chunk-WUFLGETN.js → chunk-WVFGV5FT.js} +2 -39
- package/dist/{chunk-WUFLGETN.js.map → chunk-WVFGV5FT.js.map} +3 -3
- package/dist/{chunk-PGZ4EHSE.js → chunk-ZL3VKINP.js} +41 -390
- package/dist/chunk-ZL3VKINP.js.map +7 -0
- package/dist/editor.js +3 -2
- package/dist/editor.js.map +1 -1
- package/dist/flexdesk.css +7544 -5916
- package/dist/flow.js +29 -97
- package/dist/flow.js.map +3 -3
- package/dist/git.js +1356 -0
- package/dist/git.js.map +7 -0
- package/dist/reset.css +1 -1
- package/dist/tiles.js +20 -15
- package/dist/tiles.js.map +2 -2
- package/dist/tokens.css +23 -1
- package/dist/widgets.js +3967 -1188
- package/dist/widgets.js.map +4 -4
- package/dist/wm.js +120 -32
- package/dist/wm.js.map +2 -2
- package/docs/README.md +9 -4
- package/docs/best-practices.md +2 -2
- package/docs/components/chat-shell.md +198 -0
- package/docs/components/conversation.md +10 -0
- package/docs/components/filter-builder.md +246 -0
- package/docs/components/forms-and-dialogs.md +22 -0
- package/docs/components/git.md +355 -0
- package/docs/components/input-fields.md +167 -2
- package/docs/components/keymap.md +8 -0
- package/docs/components/menus.md +82 -2
- package/docs/components/panels-and-desktops.md +16 -5
- package/docs/components/shell.md +104 -1
- package/docs/components/workspace-picker.md +149 -0
- package/docs/concepts/building-and-packaging.md +2 -2
- package/docs/concepts/packages-and-entry-points.md +14 -13
- package/docs/concepts/state-guard.md +3 -3
- package/docs/quickstart.md +1 -1
- package/git.js +13 -0
- package/package.json +6 -1
- package/src/flow/kit/dom.js +28 -119
- package/src/flow/kit/step_picker.js +1 -1
- package/src/flow/kit/value_picker.js +1 -1
- package/src/flow/outline/menu.js +1 -1
- package/src/git/diff_viewer.js +212 -0
- package/src/git/diff_window.js +51 -0
- package/src/git/git_graph.js +401 -0
- package/src/git/git_ops.js +61 -0
- package/src/git/git_panel.js +755 -0
- package/src/git/index.js +9 -0
- package/src/git/line_diff.js +202 -0
- package/src/tiles/tile_base.js +5 -4
- package/src/tiling/desktops.js +10 -8
- package/src/tiling/keymap.js +6 -5
- package/src/tiling/kind_taxonomy.js +15 -2
- package/src/tiling/shell.js +124 -17
- package/src/tiling/tile_renderer.js +2 -3
- package/src/ui/components/action_dropdown.js +4 -78
- package/src/ui/components/anchored_layer.js +255 -0
- package/src/ui/components/chat_shell.js +920 -0
- package/src/ui/components/chat_state_machine.js +223 -0
- package/src/ui/components/choice_controls.js +45 -2
- package/src/ui/components/color_field.js +3 -2
- package/src/ui/components/color_list_field.js +3 -2
- package/src/ui/components/combobox.js +422 -0
- package/src/ui/components/conversation_view.js +17 -2
- package/src/ui/components/data_table.js +2 -2
- package/src/ui/components/filter_builder.js +766 -0
- package/src/ui/components/filter_model.js +614 -0
- package/src/ui/components/managed_window.js +24 -47
- package/src/ui/components/modal.js +79 -109
- package/src/ui/components/option_list.js +160 -0
- package/src/ui/components/popover.js +94 -0
- package/src/ui/components/recent_items.js +169 -0
- package/src/ui/components/select.js +322 -0
- package/src/ui/components/tag_input.js +77 -8
- package/src/ui/components/workspace_picker.js +1155 -0
- package/src/ui/utils/fire_event.js +17 -0
- package/src/ui/utils/focusable.js +25 -0
- package/src/ui/utils/modal_host.js +27 -0
- package/widgets.js +12 -2
- package/css/base.css +0 -10547
- package/css/overrides.css +0 -2596
- package/dist/chunk-7JI7HHSY.js +0 -107
- package/dist/chunk-7JI7HHSY.js.map +0 -7
- package/dist/chunk-MIW7D6WC.js +0 -526
- package/dist/chunk-MIW7D6WC.js.map +0 -7
- package/dist/chunk-PGZ4EHSE.js.map +0 -7
- package/dist/chunk-XJNADRGR.js.map +0 -7
- /package/dist/{chunk-REJ5HLIM.js.map → chunk-HYJYYEPK.js.map} +0 -0
|
@@ -202,7 +202,7 @@ export function openStepPicker({ anchor, entries = [], categories = [], where =
|
|
|
202
202
|
anchor, content: root, label: title ?? say(S, 'addStep'), className: 'twm-flow-popover--picker',
|
|
203
203
|
focus: search, onClose: () => settle(result),
|
|
204
204
|
});
|
|
205
|
-
if (handle.
|
|
205
|
+
if (!handle.el) settle(null);
|
|
206
206
|
else filter();
|
|
207
207
|
done.close = () => handle.close('closed');
|
|
208
208
|
return done;
|
|
@@ -231,7 +231,7 @@ export function openValuePicker({ anchor, groups = [], note = null, onPick = nul
|
|
|
231
231
|
anchor, content: root, label: title ?? say(S, 'insertValue'), className: 'twm-flow-popover--values',
|
|
232
232
|
focus: search, onClose: () => settle(result),
|
|
233
233
|
});
|
|
234
|
-
if (handle.
|
|
234
|
+
if (!handle.el) settle(null);
|
|
235
235
|
else paint();
|
|
236
236
|
done.close = () => handle.close('closed');
|
|
237
237
|
return done;
|
package/src/flow/outline/menu.js
CHANGED
|
@@ -150,5 +150,5 @@ export function openStepMenu({ anchor, label, items, strings = null, onClose = n
|
|
|
150
150
|
anchor, content: root, label: label || '', className: 'twm-flow-popover--menu',
|
|
151
151
|
focus: enabled(root)[0] || root, onClose: () => onClose?.(),
|
|
152
152
|
});
|
|
153
|
-
return { el: root, close: () => handle.close('closed'), toggled: handle.toggled };
|
|
153
|
+
return { el: handle.el ? root : null, close: () => handle.close('closed'), toggled: handle.toggled };
|
|
154
154
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff_viewer.js — what changed, drawn three ways. Usable alone: it needs no
|
|
3
|
+
* adapter and knows nothing of git beyond reading a unified diff.
|
|
4
|
+
*
|
|
5
|
+
* FIELDS named values, `Priority low → high`. The consumer parses them;
|
|
6
|
+
* they arrive as `{ name, from, to }`
|
|
7
|
+
* TEXT line by line, with old and new line numbers and no diff headers,
|
|
8
|
+
* from `before`/`after` (compared here) or else from `diff` (read)
|
|
9
|
+
* RAW the `diff` text as it came
|
|
10
|
+
*
|
|
11
|
+
* const viewer = createDiffViewer(el, { before, after, diff, fields });
|
|
12
|
+
* viewer.update({ before, after });
|
|
13
|
+
* viewer.setView('raw');
|
|
14
|
+
* viewer.destroy();
|
|
15
|
+
*
|
|
16
|
+
* A view is offered only when its data is there, and the buttons that switch
|
|
17
|
+
* between views only when there are two or more. Which view shows: the one set
|
|
18
|
+
* by `view`, `setView` or a click while it stays on offer, else Fields when a
|
|
19
|
+
* field changed, else Text, else Raw. `setView(null)` hands the choice back.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { computeLineDiff, parseUnifiedDiff } from './line_diff.js';
|
|
23
|
+
|
|
24
|
+
const DEFAULT_LABELS = Object.freeze({
|
|
25
|
+
fields: 'Fields',
|
|
26
|
+
text: 'Text',
|
|
27
|
+
raw: 'Raw',
|
|
28
|
+
noChanges: 'No differences.',
|
|
29
|
+
noFields: 'No field changes.',
|
|
30
|
+
tooLong: 'Too long to compare here.',
|
|
31
|
+
gap: (hidden) => `${hidden} unchanged line${hidden === 1 ? '' : 's'}`,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
function el(tag, className, text) {
|
|
35
|
+
const n = document.createElement(tag);
|
|
36
|
+
if (className) n.className = className;
|
|
37
|
+
if (text !== undefined) n.textContent = text;
|
|
38
|
+
return n;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A field's value as drawn: an absent or empty one reads as a dash. */
|
|
42
|
+
const shownField = (v) => {
|
|
43
|
+
const s = v === null || v === undefined ? '' : String(v);
|
|
44
|
+
return s === '' ? '—' : s;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function lineRow(line, labels) {
|
|
48
|
+
const row = el('div', `twm-diff__line twm-diff__line--${line.kind}`);
|
|
49
|
+
if (line.kind === 'gap' || line.kind === 'hunk' || line.kind === 'meta' || line.kind === 'file') {
|
|
50
|
+
row.textContent = line.kind === 'gap' ? labels.gap(line.hidden) : line.text;
|
|
51
|
+
return row;
|
|
52
|
+
}
|
|
53
|
+
row.append(
|
|
54
|
+
el('span', 'twm-diff__n', line.oldLine === null ? '' : String(line.oldLine)),
|
|
55
|
+
el('span', 'twm-diff__n', line.newLine === null ? '' : String(line.newLine)),
|
|
56
|
+
el('span', 'twm-diff__sign', line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ''),
|
|
57
|
+
el('span', 'twm-diff__text', line.text),
|
|
58
|
+
);
|
|
59
|
+
return row;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* @param {HTMLElement} host
|
|
64
|
+
* @param {Object} [options]
|
|
65
|
+
* @param {string} [options.before] - The old text. With `after`, drawn by comparing the two.
|
|
66
|
+
* @param {string} [options.after] - The new text.
|
|
67
|
+
* @param {string} [options.diff] - A unified diff: the Raw view, and the Text view when there is no `before`/`after`.
|
|
68
|
+
* @param {Array<{name: string, from?: *, to?: *}>} [options.fields] - The Fields view's rows.
|
|
69
|
+
* @param {number} [options.beforeStart=1] - The line number of `before`'s first line.
|
|
70
|
+
* @param {number} [options.afterStart=1] - The line number of `after`'s first line.
|
|
71
|
+
* @param {number} [options.context=3] - Unchanged lines kept around each change.
|
|
72
|
+
* @param {number} [options.maxLines] - Past this many differing lines a side, the texts are not compared.
|
|
73
|
+
* @param {'fields'|'text'|'raw'} [options.view] - The view to show while it is on offer.
|
|
74
|
+
* @param {Object} [options.labels] - Words to replace: `fields`, `text`, `raw`, `noChanges`, `noFields`, `tooLong`, `gap(hidden)`.
|
|
75
|
+
* @param {(view: string) => void} [options.onViewChange] - Told when a click changes the view.
|
|
76
|
+
* @returns {{ el: HTMLElement, update(data: Object): void, setView(view: string|null): void,
|
|
77
|
+
* view(): string|null, showMessage(text: string): void, destroy(): void }}
|
|
78
|
+
*/
|
|
79
|
+
export function createDiffViewer(host, options = {}) {
|
|
80
|
+
const { context = 3, maxLines, onViewChange = null } = options;
|
|
81
|
+
const labels = { ...DEFAULT_LABELS, ...options.labels };
|
|
82
|
+
|
|
83
|
+
const root = el('div', 'twm-diff');
|
|
84
|
+
const switcher = el('div', 'twm-diff__views');
|
|
85
|
+
switcher.setAttribute('role', 'group');
|
|
86
|
+
switcher.setAttribute('aria-label', 'Diff view');
|
|
87
|
+
const body = el('div', 'twm-diff__body');
|
|
88
|
+
root.append(switcher, body);
|
|
89
|
+
host.appendChild(root);
|
|
90
|
+
|
|
91
|
+
let data = {};
|
|
92
|
+
let chosen = options.view ?? null;
|
|
93
|
+
let textLines; // this data's Text view; undefined until first drawn
|
|
94
|
+
let current = null;
|
|
95
|
+
|
|
96
|
+
const hasPair = () => typeof data.before === 'string' || typeof data.after === 'string';
|
|
97
|
+
|
|
98
|
+
const linesOfText = () => {
|
|
99
|
+
if (textLines !== undefined) return textLines;
|
|
100
|
+
textLines = hasPair()
|
|
101
|
+
? computeLineDiff(data.before, data.after, {
|
|
102
|
+
context,
|
|
103
|
+
...(maxLines === undefined ? {} : { maxLines }),
|
|
104
|
+
beforeStart: data.beforeStart ?? 1,
|
|
105
|
+
afterStart: data.afterStart ?? 1,
|
|
106
|
+
})
|
|
107
|
+
: parseUnifiedDiff(data.diff);
|
|
108
|
+
return textLines;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const offered = () => {
|
|
112
|
+
const out = [];
|
|
113
|
+
if (Array.isArray(data.fields)) out.push('fields');
|
|
114
|
+
if (hasPair() || (typeof data.diff === 'string' && linesOfText().length > 0)) out.push('text');
|
|
115
|
+
if (typeof data.diff === 'string') out.push('raw');
|
|
116
|
+
return out;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const pick = (views) => {
|
|
120
|
+
if (chosen && views.includes(chosen)) return chosen;
|
|
121
|
+
if (views.includes('fields') && data.fields.length > 0) return 'fields';
|
|
122
|
+
if (views.includes('text')) return 'text';
|
|
123
|
+
return views[0] ?? null;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const message = (text) => {
|
|
127
|
+
body.replaceChildren(el('div', 'twm-diff__message', text));
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const drawFields = () => {
|
|
131
|
+
if (data.fields.length === 0) { message(labels.noFields); return; }
|
|
132
|
+
const grid = el('div', 'twm-diff__fields');
|
|
133
|
+
for (const field of data.fields) {
|
|
134
|
+
const values = el('span', 'twm-diff__field-values');
|
|
135
|
+
const arrow = el('span', 'material-symbols-outlined twm-diff__field-arrow', 'arrow_forward');
|
|
136
|
+
arrow.setAttribute('aria-hidden', 'true');
|
|
137
|
+
values.append(
|
|
138
|
+
el('span', 'twm-diff__field-from', shownField(field.from)),
|
|
139
|
+
arrow,
|
|
140
|
+
el('span', 'twm-diff__field-to', shownField(field.to)),
|
|
141
|
+
);
|
|
142
|
+
grid.append(el('span', 'twm-diff__field-name', String(field.name)), values);
|
|
143
|
+
}
|
|
144
|
+
body.replaceChildren(grid);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const drawText = () => {
|
|
148
|
+
const lines = linesOfText();
|
|
149
|
+
if (lines === null) {
|
|
150
|
+
message(typeof data.diff === 'string' ? `${labels.tooLong} See ${labels.raw}.` : labels.tooLong);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (lines.length === 0) { message(labels.noChanges); return; }
|
|
154
|
+
const list = el('div', 'twm-diff__lines');
|
|
155
|
+
list.append(...lines.map((line) => lineRow(line, labels)));
|
|
156
|
+
body.replaceChildren(list);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const drawRaw = () => {
|
|
160
|
+
if (data.diff === '') { message(labels.noChanges); return; }
|
|
161
|
+
body.replaceChildren(el('pre', 'twm-diff__raw', data.diff));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const draw = () => {
|
|
165
|
+
const views = offered();
|
|
166
|
+
current = pick(views);
|
|
167
|
+
switcher.replaceChildren(...(views.length > 1 ? views.map((v) => {
|
|
168
|
+
const b = el('button', 'twm-diff__view', labels[v]);
|
|
169
|
+
b.type = 'button';
|
|
170
|
+
b.setAttribute('aria-pressed', String(v === current));
|
|
171
|
+
b.addEventListener('click', () => {
|
|
172
|
+
chosen = v;
|
|
173
|
+
draw();
|
|
174
|
+
onViewChange?.(v);
|
|
175
|
+
});
|
|
176
|
+
return b;
|
|
177
|
+
}) : []));
|
|
178
|
+
switcher.hidden = views.length <= 1;
|
|
179
|
+
if (current === 'fields') drawFields();
|
|
180
|
+
else if (current === 'text') drawText();
|
|
181
|
+
else if (current === 'raw') drawRaw();
|
|
182
|
+
else message(labels.noChanges);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
const update = (next) => {
|
|
186
|
+
data = next ?? {};
|
|
187
|
+
textLines = undefined;
|
|
188
|
+
draw();
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
update(options);
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
el: root,
|
|
195
|
+
update,
|
|
196
|
+
setView(view) {
|
|
197
|
+
chosen = view;
|
|
198
|
+
draw();
|
|
199
|
+
},
|
|
200
|
+
view: () => current,
|
|
201
|
+
/** Words in place of the diff, while it loads or when it could not be read. The next update replaces them. */
|
|
202
|
+
showMessage(text) {
|
|
203
|
+
switcher.replaceChildren();
|
|
204
|
+
switcher.hidden = true;
|
|
205
|
+
current = null;
|
|
206
|
+
message(text);
|
|
207
|
+
},
|
|
208
|
+
destroy() {
|
|
209
|
+
root.remove();
|
|
210
|
+
},
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* diff_window.js — a diff in a window of its own: FlexDesk's modal around a
|
|
3
|
+
* createDiffViewer, showing "Loading…" until the data arrives.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { openModal } from '../ui/components/modal.js';
|
|
7
|
+
import { createDiffViewer } from './diff_viewer.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {string} options.title
|
|
12
|
+
* @param {() => Promise<Object>|Object} options.load - Answers what `createDiffViewer`'s `update` takes, or
|
|
13
|
+
* `{ ok: false, error }`; a throw is shown the same way.
|
|
14
|
+
* @param {string} [options.icon='difference']
|
|
15
|
+
* @param {number} [options.width=880]
|
|
16
|
+
* @param {number} [options.height=600]
|
|
17
|
+
* @param {Object} [options.labels] - As for createDiffViewer.
|
|
18
|
+
* @returns {Promise<null>} Settles when the window closes.
|
|
19
|
+
*/
|
|
20
|
+
export function openDiffWindow({ title, load, icon = 'difference', width = 880, height = 600, labels } = {}) {
|
|
21
|
+
const content = document.createElement('div');
|
|
22
|
+
content.className = 'twm-diff-window';
|
|
23
|
+
const viewer = createDiffViewer(content, { labels });
|
|
24
|
+
viewer.showMessage('Loading…');
|
|
25
|
+
const closed = openModal({ title, icon, content, width, height, maximizable: true });
|
|
26
|
+
Promise.resolve()
|
|
27
|
+
.then(load)
|
|
28
|
+
.then((data) => {
|
|
29
|
+
if (data?.ok === false) viewer.showMessage(data.error || 'The diff could not be read.');
|
|
30
|
+
else viewer.update(data);
|
|
31
|
+
})
|
|
32
|
+
.catch((e) => viewer.showMessage(e?.message || String(e)));
|
|
33
|
+
return closed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The diff of one file, from an adapter's `diffFile`, in a window.
|
|
38
|
+
* @param {Object} options
|
|
39
|
+
* @param {Object} options.adapter - A git adapter with `diffFile` (docs/components/git.md).
|
|
40
|
+
* @param {string} options.path
|
|
41
|
+
* @param {string} [options.status]
|
|
42
|
+
* @param {string} [options.renamedFrom] - The path it had before, for a rename.
|
|
43
|
+
* @param {string} [options.ref] - A commit: the file's change in that commit, not the uncommitted one.
|
|
44
|
+
* @returns {Promise<null>}
|
|
45
|
+
*/
|
|
46
|
+
export function openGitDiffWindow({ adapter, path, status, renamedFrom, ref }) {
|
|
47
|
+
return openDiffWindow({
|
|
48
|
+
title: renamedFrom ? `${renamedFrom} → ${path}` : path,
|
|
49
|
+
load: () => adapter.diffFile({ path, status, ...(renamedFrom ? { renamedFrom } : {}), ...(ref ? { ref } : {}) }),
|
|
50
|
+
});
|
|
51
|
+
}
|