@columenlabs/lumen-ui 0.1.194 → 0.1.195
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/dist/joins.d.ts +7 -1
- package/dist/joins.js +14 -0
- package/dist/lumen-widget.js +11 -2
- package/dist/view-editor.js +21 -4
- package/dist/widget-data.d.ts +15 -0
- package/dist/widget-data.js +44 -2
- package/dist/workspaces-config.d.ts +46 -0
- package/dist/workspaces-config.js +6 -0
- package/dist/workspaces.d.ts +3 -43
- package/dist/workspaces.js +4 -5
- package/package.json +2 -2
package/dist/joins.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type JoinKey, type SourceState } from '@columenlabs/lumen-ui-types/domains';
|
|
2
|
-
import { type FrameChoice, type FrameColumn, type FrameData, type FrameEditInput, type FrameMeta, type YourColumn } from '@columenlabs/lumen-ui-types/frames';
|
|
2
|
+
import { type FrameChoice, type FrameColumn, type FrameData, type FrameEditInput, type FrameMeta, type FrameScript, type YourColumn } from '@columenlabs/lumen-ui-types/frames';
|
|
3
3
|
import { type CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
4
4
|
import { type ReactNode } from 'react';
|
|
5
5
|
import { type ConnectedDomain } from './connections.js';
|
|
@@ -88,6 +88,12 @@ export declare const frameGroupId: typeof recipeId;
|
|
|
88
88
|
export declare function useDraftFrames<T>(domain: ViewDomain<T>, ids: readonly string[]): ViewDomain<T>;
|
|
89
89
|
/** The person's linked frames (recipes) a card can attach, for Edit widget's + Data. */
|
|
90
90
|
export declare function useFrameChoices(enabled: boolean): import("@tanstack/react-query").UseQueryResult<FrameChoice[], Error>;
|
|
91
|
+
/** Makes a frame from one of the product's recipes (+ Data's app recipes), or reuses the one that
|
|
92
|
+
* already runs it, and lists it with the person's frames. */
|
|
93
|
+
export declare function useMakeFrame(): import("@tanstack/react-query").UseMutationResult<FrameChoice, Error, {
|
|
94
|
+
name: string;
|
|
95
|
+
recipe: FrameScript;
|
|
96
|
+
}, unknown>;
|
|
91
97
|
/** A row of plain cells, as a link to a catalog domain or a recipe reads it. */
|
|
92
98
|
type CellRow = Record<string, ViewCellValue>;
|
|
93
99
|
interface PlainColumn {
|
package/dist/joins.js
CHANGED
|
@@ -375,6 +375,20 @@ export function useFrameChoices(enabled) {
|
|
|
375
375
|
staleTime: 30_000,
|
|
376
376
|
});
|
|
377
377
|
}
|
|
378
|
+
/** Makes a frame from one of the product's recipes (+ Data's app recipes), or reuses the one that
|
|
379
|
+
* already runs it, and lists it with the person's frames. */
|
|
380
|
+
export function useMakeFrame() {
|
|
381
|
+
const api = useAssistant()?.api ?? null;
|
|
382
|
+
const qc = useQueryClient();
|
|
383
|
+
return useMutation({
|
|
384
|
+
mutationFn: (r) => json(`${api}/frames`, {
|
|
385
|
+
method: 'POST',
|
|
386
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
387
|
+
body: JSON.stringify(r),
|
|
388
|
+
}),
|
|
389
|
+
onSuccess: () => void qc.invalidateQueries({ queryKey: [...WORKSPACES_KEY, api, 'frames'] }),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
378
392
|
const titled = (k) => (k.charAt(0).toUpperCase() + k.slice(1)).replace(/_/g, ' ');
|
|
379
393
|
/** Plain rows as a domain: a field per column, and the keys (symbol, account) a column carries. */
|
|
380
394
|
export function rowsDomain(id, columns, keys) {
|
package/dist/lumen-widget.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { FRAME_LINK } from '@columenlabs/lumen-ui-types/catalog';
|
|
2
3
|
import { OUTLOOK_INDEXES } from '@columenlabs/lumen-ui-types/market';
|
|
3
4
|
// LumenSavedWidget (as blocks; renamed): a page (LumenLayout) is an ordered list of cards, each a third, half, two thirds or the full width.
|
|
4
5
|
// A card is the view (LumenView) or a widget. Chart and top-N widgets read every row of the view
|
|
@@ -39,7 +40,7 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '.
|
|
|
39
40
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from './ui/tabs.js';
|
|
40
41
|
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
41
42
|
import { computedOf } from './view-model.js';
|
|
42
|
-
import { PAGE_SOURCE, useWidgetDataOptions } from './widget-data.js';
|
|
43
|
+
import { MAKE_RECIPE, PAGE_SOURCE, useMakeRecipe, useWidgetDataOptions } from './widget-data.js';
|
|
43
44
|
import { COMPACT, MINUS } from './widget-values.js';
|
|
44
45
|
// Loaded when it first shows (lazy-part.tsx).
|
|
45
46
|
const MarketWidget = lazyPart(() => import('./market/market-widget.js').then((m) => m.MarketWidget), _jsx(Skeleton, { className: "h-full min-h-40 w-full" }));
|
|
@@ -394,6 +395,14 @@ export function AddWidgetDialog({ open, onOpenChange, domain, cards, onAdd, onBu
|
|
|
394
395
|
useEffect(() => setData(PAGE_SOURCE), [open]);
|
|
395
396
|
const pageLabel = `${name} (this page)`;
|
|
396
397
|
const dataOptions = useWidgetDataOptions(open, pageLabel);
|
|
398
|
+
// A product recipe not made yet pulls its rows once, then is the widget's data.
|
|
399
|
+
const maker = useMakeRecipe();
|
|
400
|
+
const pickData = (v) => v.startsWith(MAKE_RECIPE)
|
|
401
|
+
? void maker.run(v).then((f) => {
|
|
402
|
+
if (f)
|
|
403
|
+
setData(`${FRAME_LINK}${f.id}`);
|
|
404
|
+
})
|
|
405
|
+
: setData(v);
|
|
397
406
|
const own = data !== PAGE_SOURCE;
|
|
398
407
|
const ids = useMemo(() => (own ? [data] : []), [own, data]);
|
|
399
408
|
const read = useDomainReads(ids, ids)[data];
|
|
@@ -404,7 +413,7 @@ export function AddWidgetDialog({ open, onOpenChange, domain, cards, onAdd, onBu
|
|
|
404
413
|
const hasRow = !own && cards.some(isRowCard);
|
|
405
414
|
const full = cards.length >= 12;
|
|
406
415
|
const offered = useOffered(shapeDomain);
|
|
407
|
-
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Add widget" }), _jsxs(DialogDescription, { children: ["Pick its data, then its shape", full ? '. A page holds 12 cards at most.' : '.'] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "add-widget-data", className: "text-xs font-medium text-muted-foreground", children: "Data" }), _jsx(LumenSelector, { id: "add-widget-data", icon: DatabaseIcon, label: "Widget data", search: true, value: data, options: dataOptions, onChange:
|
|
416
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "sm:max-w-2xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: "Add widget" }), _jsxs(DialogDescription, { children: ["Pick its data, then its shape", full ? '. A page holds 12 cards at most.' : '.'] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "add-widget-data", className: "text-xs font-medium text-muted-foreground", children: "Data" }), _jsx(LumenSelector, { id: "add-widget-data", icon: DatabaseIcon, label: "Widget data", search: true, value: data, options: dataOptions, onChange: pickData }), own && read?.state !== 'ok' && (_jsx("p", { className: cn('text-xs', read?.state === 'loading' || !read ? 'text-muted-foreground' : 'text-destructive'), children: read?.state === 'loading' || !read
|
|
408
417
|
? 'Reading its fields…'
|
|
409
418
|
: (read.detail ?? 'Its rows could not be read.') }))] }), _jsxs(Tabs, { defaultValue: catalog.length ? 'prebuilt' : 'own', children: [_jsxs(TabsList, { children: [!own && (_jsxs(TabsTrigger, { value: "prebuilt", children: [_jsx(PackageIcon, {}), "Pre-built"] })), _jsxs(TabsTrigger, { value: "own", children: [_jsx(WrenchIcon, {}), "Build your own"] })] }), _jsx(TabsContent, { value: "prebuilt", className: dialogBody, children: catalog.length ? (_jsx("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-2", children: catalog.map((it) => {
|
|
410
419
|
const added = cards.some((b) => b.id === it.widget.id);
|
package/dist/view-editor.js
CHANGED
|
@@ -46,7 +46,7 @@ import { AGG_LABELS, FORMAT_LABELS, FormatChip, FormulaForm, MAX_FORMULAS, field
|
|
|
46
46
|
import { shapeIcon } from './view-icons.js';
|
|
47
47
|
import { FORMULAS_GROUP, canFilter, chipsOf, formulaBlanks, formulasOf, groupOf, hiddenOf, subgroupLabel, subgroupOf, subgroupsOf, summaryCash, takesFormulas, tilesOf, totalAggOf, withViewFields, } from './view-model.js';
|
|
48
48
|
import { WidgetControlsEditor } from './widget-controls.js';
|
|
49
|
-
import { PAGE_SOURCE, useWidgetDataOptions } from './widget-data.js';
|
|
49
|
+
import { MAKE_RECIPE, PAGE_SOURCE, recipeOptions, useMakeRecipe, useWidgetDataOptions, } from './widget-data.js';
|
|
50
50
|
import { EditorRow as Row, WidgetFields, WidgetSettings, hasWidgetSettings, readsRows, slotKeys, widgetSlots, } from './widget-settings.js';
|
|
51
51
|
import { widgetThumb } from './widget-thumbs.js';
|
|
52
52
|
/** The field's own group and Total rows: a formula over the rolled-up inputs, or a share. */
|
|
@@ -246,6 +246,13 @@ export function ViewEditDialog({ open, onOpenChange, domain: pageDomain, config,
|
|
|
246
246
|
const byKey = (f) => !!f.key && !!base.keys?.[f.key];
|
|
247
247
|
const recipes = (choices.data ?? []).filter((f) => !frameIds.includes(f.id) && !connections.some((c) => c.to === `${FRAME_LINK}${f.id}`));
|
|
248
248
|
const addRecipe = (f) => (byKey(f) ? addFrame(f) : link(`${FRAME_LINK}${f.id}`, []));
|
|
249
|
+
// The product's recipes not made yet, by app: picking one pulls its rows once, then links them.
|
|
250
|
+
const maker = useMakeRecipe();
|
|
251
|
+
const templates = recipeOptions(maker.recipes, choices.data ?? []);
|
|
252
|
+
const makeRecipe = (v) => void maker.run(v).then((f) => {
|
|
253
|
+
if (f)
|
|
254
|
+
addRecipe(f);
|
|
255
|
+
});
|
|
249
256
|
const addFrame = (f) => setDraft({
|
|
250
257
|
...draft,
|
|
251
258
|
frames: [...frameIds, f.id],
|
|
@@ -625,7 +632,12 @@ export function ViewEditDialog({ open, onOpenChange, domain: pageDomain, config,
|
|
|
625
632
|
label: mainLabel,
|
|
626
633
|
empty: source === '',
|
|
627
634
|
options: mainOptions,
|
|
628
|
-
onPick:
|
|
635
|
+
onPick: (to) => to.startsWith(MAKE_RECIPE)
|
|
636
|
+
? void maker.run(to).then((f) => {
|
|
637
|
+
if (f)
|
|
638
|
+
pickMain(`${FRAME_LINK}${f.id}`);
|
|
639
|
+
})
|
|
640
|
+
: pickMain(to),
|
|
629
641
|
onDrop: dropMain,
|
|
630
642
|
onClear: clearAll,
|
|
631
643
|
onMakeMain: makeMain,
|
|
@@ -635,7 +647,7 @@ export function ViewEditDialog({ open, onOpenChange, domain: pageDomain, config,
|
|
|
635
647
|
fieldsOf: (to) => allRead[to]?.domain.fields ?? [],
|
|
636
648
|
onOf: (to) => connections.find((c) => c.to === to)?.on ?? [],
|
|
637
649
|
set: setPairs,
|
|
638
|
-
}, deskLabel: app?.label ?? null, recipes: recipes, recipeNote: choices.isError ? `Recipes did not load: ${choices.error.message}` : null, onAddRecipe: addRecipe, recipeByKey: byKey, onRemoveRecipe: card ? dropRecipe : removeFrame, onNewRecipe: askRecipe })) : (_jsx("p", { className: "px-1.5 text-sm text-muted-foreground", children: widget?.kind === 'chart' && (widget.style === 'price' || widget.style === 'market')
|
|
650
|
+
}, deskLabel: app?.label ?? null, recipes: recipes, recipeNote: choices.isError ? `Recipes did not load: ${choices.error.message}` : null, onAddRecipe: addRecipe, templates: templates, onMakeRecipe: makeRecipe, making: maker.pending, recipeByKey: byKey, onRemoveRecipe: card ? dropRecipe : removeFrame, onNewRecipe: askRecipe })) : (_jsx("p", { className: "px-1.5 text-sm text-muted-foreground", children: widget?.kind === 'chart' && (widget.style === 'price' || widget.style === 'market')
|
|
639
651
|
? 'This chart reads its symbols, not fields.'
|
|
640
652
|
: 'This widget takes no fields.' }));
|
|
641
653
|
const treeHint = widget ? (slot && (_jsxs("div", { className: "flex flex-col gap-2 px-1.5 pb-2", children: [slots.length > 1 && (_jsx(ToggleGroup, { type: "single", variant: "outline", size: "sm", spacing: 0, className: "justify-self-start", "aria-label": "Fill", value: slot.id, onValueChange: (v) => v && setSlotId(v), children: slots.map((s) => (_jsx(ToggleGroupItem, { value: s.id, className: "px-2.5", children: s.label }, s.id))) })), _jsx("p", { className: "text-xs text-muted-foreground", children: slot.max === 1
|
|
@@ -718,7 +730,7 @@ function LinkPairs({ label, on, cardFields, fields, onChange, }) {
|
|
|
718
730
|
const row = (p, i) => (_jsxs("div", { className: "flex min-w-0 items-center gap-1", children: [_jsx(LumenSelector, { label: "This card's field", size: "sm", className: "min-w-0 flex-1", placeholder: "This card", search: true, value: p.from || null, options: mine, onChange: (v) => put({ ...p, from: v }, i) }), _jsx("span", { className: "text-xs text-muted-foreground", children: "=" }), _jsx(LumenSelector, { label: `${label}'s field`, size: "sm", className: "min-w-0 flex-1", placeholder: theirs.length ? label : 'Loading…', search: true, disabled: !theirs.length, value: p.to || null, options: theirs, onChange: (v) => put({ ...p, to: v }, i) }), i !== null && (_jsx(IconButton, { icon: XIcon, size: "icon-xs", label: "Remove this pair", onClick: () => onChange(on.filter((_, j) => j !== i)) }))] }, i ?? 'new'));
|
|
719
731
|
return (_jsxs("div", { className: "flex flex-col gap-1 px-1 py-1", children: [on.map((p, i) => row(p, i)), open && row(open, null), !open && on.length < MAX_PAIRS && (_jsxs(Button, { size: "xs", variant: "ghost", className: "self-start text-primary", onClick: () => setPart({ from: '', to: '' }), children: [_jsx(PlusIcon, {}), "Field pair"] }))] }));
|
|
720
732
|
}
|
|
721
|
-
function FieldTree({ domain, draft, picked, onPick, why, formulaProblems, onEditFormula, onRemoveFormula, onAddFormula, deskDomains = [], onLink, onUnlink, deskLabel, recipes = [], recipeNote, onAddRecipe, recipeByKey = () => false, onRemoveRecipe, onNewRecipe, linkPairs, main, }) {
|
|
733
|
+
function FieldTree({ domain, draft, picked, onPick, why, formulaProblems, onEditFormula, onRemoveFormula, onAddFormula, deskDomains = [], onLink, onUnlink, deskLabel, recipes = [], recipeNote, onAddRecipe, templates = [], onMakeRecipe, making = false, recipeByKey = () => false, onRemoveRecipe, onNewRecipe, linkPairs, main, }) {
|
|
722
734
|
const assistantName = useAssistantName();
|
|
723
735
|
const [query, setQuery] = useState('');
|
|
724
736
|
// New recipe's box, open while not null.
|
|
@@ -753,6 +765,9 @@ function FieldTree({ domain, draft, picked, onPick, why, formulaProblems, onEdit
|
|
|
753
765
|
: {}),
|
|
754
766
|
}))
|
|
755
767
|
: []),
|
|
768
|
+
...(onMakeRecipe
|
|
769
|
+
? templates.map((o) => (making ? { ...o, disabled: 'Pulling rows…' } : o))
|
|
770
|
+
: []),
|
|
756
771
|
...(onNewRecipe
|
|
757
772
|
? [
|
|
758
773
|
{
|
|
@@ -768,6 +783,8 @@ function FieldTree({ domain, draft, picked, onPick, why, formulaProblems, onEdit
|
|
|
768
783
|
const addData = (v) => {
|
|
769
784
|
if (v === 'new')
|
|
770
785
|
setWish('');
|
|
786
|
+
else if (v.startsWith(MAKE_RECIPE))
|
|
787
|
+
onMakeRecipe?.(v);
|
|
771
788
|
else if (v.startsWith('link:')) {
|
|
772
789
|
const d = deskDomains.find((x) => `link:${x.id}` === v);
|
|
773
790
|
if (d && !d.why)
|
package/dist/widget-data.d.ts
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
import type { FrameChoice } from '@columenlabs/lumen-ui-types/frames';
|
|
1
2
|
import type { LumenOption } from './lumen-selector.js';
|
|
3
|
+
import { type WorkspaceRecipe } from './workspaces-config.js';
|
|
2
4
|
/** The page's own rows, as a data option's value. */
|
|
3
5
|
export declare const PAGE_SOURCE = "_page";
|
|
6
|
+
/** A product recipe not made yet, as a data option's value: picking it runs it once. */
|
|
7
|
+
export declare const MAKE_RECIPE = "make:";
|
|
8
|
+
/**
|
|
9
|
+
* The product's recipes (WorkspacesProvider `recipes`) the person has no frame of yet, as options
|
|
10
|
+
* grouped by the app they read. A recipe already made shows once, as the person's recipe.
|
|
11
|
+
*/
|
|
12
|
+
export declare function recipeOptions(recipes: readonly WorkspaceRecipe[], made: readonly FrameChoice[]): LumenOption[];
|
|
13
|
+
/** Turns a picked product recipe into its frame (made once, then reused). */
|
|
14
|
+
export declare function useMakeRecipe(): {
|
|
15
|
+
run: (value: string) => Promise<FrameChoice | null>;
|
|
16
|
+
pending: boolean;
|
|
17
|
+
recipes: WorkspaceRecipe[];
|
|
18
|
+
};
|
|
4
19
|
export declare function useWidgetDataOptions(enabled: boolean, pageLabel: string): LumenOption[];
|
package/dist/widget-data.js
CHANGED
|
@@ -1,14 +1,55 @@
|
|
|
1
1
|
// The data a widget can take as its main data: this page's rows, the app's live domains with a
|
|
2
|
-
// reader,
|
|
2
|
+
// reader, the person's recipes, and the product's recipes not yet made (by app). Add widget's
|
|
3
|
+
// first step and Edit widget ▸ Data both pick from it.
|
|
3
4
|
import { CATALOG, FRAME_LINK, catalogKeys, readableDomain, } from '@columenlabs/lumen-ui-types/catalog';
|
|
4
5
|
import { DatabaseIcon, SparklesIcon, TableIcon } from 'lucide-react';
|
|
5
|
-
import { asOf, useFrameChoices } from './joins.js';
|
|
6
|
+
import { asOf, useFrameChoices, useMakeFrame } from './joins.js';
|
|
7
|
+
import { lumenToast } from './lumen-toast.js';
|
|
6
8
|
import { useApp } from './shell.js';
|
|
9
|
+
import { useWorkspacesConfig } from './workspaces-config.js';
|
|
7
10
|
/** The page's own rows, as a data option's value. */
|
|
8
11
|
export const PAGE_SOURCE = '_page';
|
|
12
|
+
/** A product recipe not made yet, as a data option's value: picking it runs it once. */
|
|
13
|
+
export const MAKE_RECIPE = 'make:';
|
|
14
|
+
/**
|
|
15
|
+
* The product's recipes (WorkspacesProvider `recipes`) the person has no frame of yet, as options
|
|
16
|
+
* grouped by the app they read. A recipe already made shows once, as the person's recipe.
|
|
17
|
+
*/
|
|
18
|
+
export function recipeOptions(recipes, made) {
|
|
19
|
+
const names = new Set(made.map((f) => f.name));
|
|
20
|
+
return recipes
|
|
21
|
+
.filter((r) => !names.has(r.label))
|
|
22
|
+
.map((r) => ({
|
|
23
|
+
value: `${MAKE_RECIPE}${r.id}`,
|
|
24
|
+
label: r.label,
|
|
25
|
+
group: r.group ?? 'Recipes',
|
|
26
|
+
...(r.description ? { detail: r.description } : {}),
|
|
27
|
+
icon: r.icon ?? SparklesIcon,
|
|
28
|
+
keywords: [r.group ?? '', r.description ?? ''],
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
/** Turns a picked product recipe into its frame (made once, then reused). */
|
|
32
|
+
export function useMakeRecipe() {
|
|
33
|
+
const { recipes = [] } = useWorkspacesConfig();
|
|
34
|
+
const make = useMakeFrame();
|
|
35
|
+
const run = async (value) => {
|
|
36
|
+
const r = recipes.find((x) => `${MAKE_RECIPE}${x.id}` === value);
|
|
37
|
+
if (!r)
|
|
38
|
+
return null;
|
|
39
|
+
try {
|
|
40
|
+
return await make.mutateAsync({ name: r.label, recipe: r.recipe });
|
|
41
|
+
}
|
|
42
|
+
catch (e) {
|
|
43
|
+
lumenToast.error(`Couldn’t pull ${r.label}: ${e.message}`);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
return { run, pending: make.isPending, recipes };
|
|
48
|
+
}
|
|
9
49
|
export function useWidgetDataOptions(enabled, pageLabel) {
|
|
10
50
|
const app = useApp();
|
|
11
51
|
const choices = useFrameChoices(enabled);
|
|
52
|
+
const { recipes = [] } = useWorkspacesConfig();
|
|
12
53
|
return [
|
|
13
54
|
{ value: PAGE_SOURCE, label: pageLabel, group: 'This page', icon: TableIcon },
|
|
14
55
|
...Object.entries(CATALOG)
|
|
@@ -28,5 +69,6 @@ export function useWidgetDataOptions(enabled, pageLabel) {
|
|
|
28
69
|
detail: asOf(f.at),
|
|
29
70
|
icon: SparklesIcon,
|
|
30
71
|
})),
|
|
72
|
+
...recipeOptions(recipes, choices.data ?? []),
|
|
31
73
|
];
|
|
32
74
|
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { FrameScript } from '@columenlabs/lumen-ui-types/frames';
|
|
2
|
+
import type { CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
3
|
+
import { type ComponentType } from 'react';
|
|
4
|
+
type Icon = ComponentType<{
|
|
5
|
+
className?: string;
|
|
6
|
+
}>;
|
|
7
|
+
/** An app a workspace can belong to (the product's apps). */
|
|
8
|
+
export interface WorkspaceApp {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
/** Its address; a workspace there is at `${href}workspaces/<id>`. */
|
|
12
|
+
href: string;
|
|
13
|
+
icon?: Icon;
|
|
14
|
+
}
|
|
15
|
+
/** What the product tells its workspaces; every field may be left out by a one-app product. */
|
|
16
|
+
export interface WorkspacesConfig {
|
|
17
|
+
/** The apps a workspace may move to that this person can open, this one included. */
|
|
18
|
+
apps?: WorkspaceApp[];
|
|
19
|
+
/** What the product calls an app: e.g. `desk`. */
|
|
20
|
+
appNoun?: string;
|
|
21
|
+
/** Which app draws a tab over a domain; one it does not say is this app's. */
|
|
22
|
+
domainApp?: (domain: string) => string | undefined;
|
|
23
|
+
/** A domain tab's starting layout. */
|
|
24
|
+
defaultConfig?: (domain: string) => CustomViewConfig | null;
|
|
25
|
+
/** The empty workspace's example ask, on an app: `put my holdings with their earnings dates in`. */
|
|
26
|
+
example?: (app: string) => string;
|
|
27
|
+
/** Recipes a person picks to start a tab by hand (+ in the tab bar); none hides the +. */
|
|
28
|
+
recipes?: WorkspaceRecipe[];
|
|
29
|
+
}
|
|
30
|
+
/** A saved data pull a person can start a tab from: one call to one of the product's read tools. */
|
|
31
|
+
export interface WorkspaceRecipe {
|
|
32
|
+
id: string;
|
|
33
|
+
/** The tab's name and the picker's row: `Jobs scoring 70+`. */
|
|
34
|
+
label: string;
|
|
35
|
+
/** When to pick it, under the label. */
|
|
36
|
+
description?: string;
|
|
37
|
+
/** The picker's section: the app the rows come from. */
|
|
38
|
+
group?: string;
|
|
39
|
+
icon?: Icon;
|
|
40
|
+
recipe: FrameScript;
|
|
41
|
+
}
|
|
42
|
+
/** The product's apps and domains for its workspaces. */
|
|
43
|
+
export declare const WorkspacesProvider: import("react").Provider<WorkspacesConfig>;
|
|
44
|
+
/** The product's workspace config; empty outside a WorkspacesProvider. */
|
|
45
|
+
export declare const useWorkspacesConfig: () => WorkspacesConfig;
|
|
46
|
+
export {};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
const ConfigCtx = createContext({});
|
|
3
|
+
/** The product's apps and domains for its workspaces. */
|
|
4
|
+
export const WorkspacesProvider = ConfigCtx.Provider;
|
|
5
|
+
/** The product's workspace config; empty outside a WorkspacesProvider. */
|
|
6
|
+
export const useWorkspacesConfig = () => useContext(ConfigCtx);
|
package/dist/workspaces.d.ts
CHANGED
|
@@ -1,47 +1,8 @@
|
|
|
1
|
-
import { type FrameScript } from '@columenlabs/lumen-ui-types/frames';
|
|
2
1
|
import { type CustomViewConfig } from '@columenlabs/lumen-ui-types/views';
|
|
3
|
-
import { type
|
|
2
|
+
import { type ReactNode } from 'react';
|
|
4
3
|
import { type Crumb } from './page.js';
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}>;
|
|
8
|
-
/** An app a workspace can belong to (the product's apps). */
|
|
9
|
-
export interface WorkspaceApp {
|
|
10
|
-
id: string;
|
|
11
|
-
label: string;
|
|
12
|
-
/** Its address; a workspace there is at `${href}workspaces/<id>`. */
|
|
13
|
-
href: string;
|
|
14
|
-
icon?: Icon;
|
|
15
|
-
}
|
|
16
|
-
/** What the product tells its workspaces; every field may be left out by a one-app product. */
|
|
17
|
-
export interface WorkspacesConfig {
|
|
18
|
-
/** The apps a workspace may move to that this person can open, this one included. */
|
|
19
|
-
apps?: WorkspaceApp[];
|
|
20
|
-
/** What the product calls an app: e.g. `desk`. */
|
|
21
|
-
appNoun?: string;
|
|
22
|
-
/** Which app draws a tab over a domain; one it does not say is this app's. */
|
|
23
|
-
domainApp?: (domain: string) => string | undefined;
|
|
24
|
-
/** A domain tab's starting layout. */
|
|
25
|
-
defaultConfig?: (domain: string) => CustomViewConfig | null;
|
|
26
|
-
/** The empty workspace's example ask, on an app: `put my holdings with their earnings dates in`. */
|
|
27
|
-
example?: (app: string) => string;
|
|
28
|
-
/** Recipes a person picks to start a tab by hand (+ in the tab bar); none hides the +. */
|
|
29
|
-
recipes?: WorkspaceRecipe[];
|
|
30
|
-
}
|
|
31
|
-
/** A saved data pull a person can start a tab from: one call to one of the product's read tools. */
|
|
32
|
-
export interface WorkspaceRecipe {
|
|
33
|
-
id: string;
|
|
34
|
-
/** The tab's name and the picker's row: `Jobs scoring 70+`. */
|
|
35
|
-
label: string;
|
|
36
|
-
/** When to pick it, under the label. */
|
|
37
|
-
description?: string;
|
|
38
|
-
/** The picker's section: the app the rows come from. */
|
|
39
|
-
group?: string;
|
|
40
|
-
icon?: Icon;
|
|
41
|
-
recipe: FrameScript;
|
|
42
|
-
}
|
|
43
|
-
/** The product's apps and domains for its workspaces. */
|
|
44
|
-
export declare const WorkspacesProvider: import("react").Provider<WorkspacesConfig>;
|
|
4
|
+
export { WorkspacesProvider } from './workspaces-config.js';
|
|
5
|
+
export type { WorkspaceApp, WorkspaceRecipe, WorkspacesConfig } from './workspaces-config.js';
|
|
45
6
|
/**
|
|
46
7
|
* Workspaces in the sidebar: one item per workspace, newest first; `+` starts an empty one, the
|
|
47
8
|
* trash icon deletes one (its frames stay until no tab shows them). Hidden where the app has no
|
|
@@ -69,4 +30,3 @@ export interface WorkspaceDraftProps {
|
|
|
69
30
|
export declare function WorkspacePage({ draft }: {
|
|
70
31
|
draft?: (p: WorkspaceDraftProps) => ReactNode;
|
|
71
32
|
}): import("react").JSX.Element;
|
|
72
|
-
export {};
|
package/dist/workspaces.js
CHANGED
|
@@ -5,7 +5,7 @@ import { workspacePath, } from '@columenlabs/lumen-ui-types/workspaces';
|
|
|
5
5
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
|
6
6
|
import { useNavigate, useParams, useSearch } from '@tanstack/react-router';
|
|
7
7
|
import { ArrowRightLeftIcon, CalendarIcon, ExternalLinkIcon, HashIcon, LayersIcon, PencilIcon, PlusIcon, RefreshCwIcon, SparklesIcon, TableIcon, TextCursorInputIcon, Trash2Icon, TypeIcon, } from 'lucide-react';
|
|
8
|
-
import {
|
|
8
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
9
9
|
import { apiJson } from './api-fetch.js';
|
|
10
10
|
import { useAssistant, useAssistantName, useAssistantPending } from './assistant-context.js';
|
|
11
11
|
import { useAssistantWorkspace } from './assistant-screen.js';
|
|
@@ -38,9 +38,8 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
|
|
|
38
38
|
import { Input } from './ui/input.js';
|
|
39
39
|
import { iconOf } from './view-icons.js';
|
|
40
40
|
import { WORKSPACES_KEY, frameKey, workspacesListQuery } from './workspace-keys.js';
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
export const WorkspacesProvider = ConfigCtx.Provider;
|
|
41
|
+
import { useWorkspacesConfig, } from './workspaces-config.js';
|
|
42
|
+
export { WorkspacesProvider } from './workspaces-config.js';
|
|
44
43
|
const json = (url, init) => apiJson(url, init, 'workspaces');
|
|
45
44
|
const workspaceQuery = (api, id) => ({
|
|
46
45
|
queryKey: [...WORKSPACES_KEY, api, 'ws', id],
|
|
@@ -146,7 +145,7 @@ function WorkspaceBody({ api, w, tab, draft, }) {
|
|
|
146
145
|
});
|
|
147
146
|
// Move to app: the workspace leaves this app's sidebar for another's.
|
|
148
147
|
const here = useShell().app;
|
|
149
|
-
const config =
|
|
148
|
+
const config = useWorkspacesConfig();
|
|
150
149
|
const assistant = useAssistantName();
|
|
151
150
|
const noun = config.appNoun ?? 'app';
|
|
152
151
|
const appOf = (id) => config.apps?.find((a) => a.id === id);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@columenlabs/lumen-ui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.195",
|
|
4
4
|
"description": "Lumen: shared React components (frame, views, widgets, charts, drawers).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
"@codemirror/lang-javascript": "6.2.5",
|
|
50
50
|
"@codemirror/lang-json": "6.0.2",
|
|
51
51
|
"@codemirror/lang-sql": "6.10.0",
|
|
52
|
-
"@columenlabs/lumen-ui-types": "0.1.
|
|
52
|
+
"@columenlabs/lumen-ui-types": "0.1.195",
|
|
53
53
|
"@dnd-kit/core": "6.3.1",
|
|
54
54
|
"@dnd-kit/sortable": "10.0.0",
|
|
55
55
|
"@dnd-kit/utilities": "3.2.2",
|