rune-lab 0.5.0-rc.2 → 0.5.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/dist/src/core/forge/define-plugin.d.ts +11 -0
- package/dist/src/core/forge/define-plugin.js +11 -1
- package/dist/src/core/kernel/wiring.d.ts +1 -0
- package/dist/src/core/kernel/wiring.js +16 -17
- package/dist/src/plugins/layout/stores/theme.svelte.js +15 -7
- package/dist/src/plugins/palettes/palettes/shortcuts/ShortcutPalette.svelte +3 -1
- package/dist/src/plugins/palettes/plugin.d.ts +8 -3
- package/dist/src/plugins/palettes/plugin.js +2 -2
- package/dist/src/plugins/palettes/registry/registry.svelte.d.ts +11 -1
- package/dist/src/plugins/palettes/registry/registry.svelte.js +6 -2
- package/dist/src/plugins/palettes/settings-modal/AppMetadata.svelte +25 -17
- package/dist/src/plugins/palettes/settings-modal/route.svelte.js +12 -6
- package/dist/src/ui/RuneProvider.svelte +13 -14
- package/dist/src/ui/RuneProvider.svelte.d.ts +0 -3
- package/dist/src/ui/mod.js +1 -1
- package/dist/src/ui/reactivity/app.svelte.d.ts +2 -12
- package/dist/src/ui/reactivity/app.svelte.js +13 -50
- package/dist/src/ui/reactivity/data-store.svelte.d.ts +10 -0
- package/dist/src/ui/reactivity/data-store.svelte.js +35 -0
- package/dist/src/ui/settings/SettingsFields.svelte +6 -3
- package/package.json +1 -1
|
@@ -1,7 +1,16 @@
|
|
|
1
|
+
import type { Schema } from "effect";
|
|
1
2
|
import type { ContributionEntry } from "./define-contribution.js";
|
|
2
3
|
import type { SettingsSchema } from "./define-settings.js";
|
|
3
4
|
import type { BaseSlotSpec } from "./define-slot.js";
|
|
4
5
|
import { type SlotDescriptor } from "./descriptors.js";
|
|
6
|
+
export type SlotConfigInput<TSpec extends BaseSlotSpec> = TSpec extends {
|
|
7
|
+
config: Schema.Schema<infer TConfig, unknown, never>;
|
|
8
|
+
}
|
|
9
|
+
? TConfig
|
|
10
|
+
: unknown;
|
|
11
|
+
export type PluginConfigInput<TSlots extends Record<string, BaseSlotSpec>> = {
|
|
12
|
+
[K in keyof TSlots]?: SlotConfigInput<TSlots[K]>;
|
|
13
|
+
};
|
|
5
14
|
export interface ForgedPlugin<
|
|
6
15
|
TId extends string = string,
|
|
7
16
|
TSlots extends Record<string, BaseSlotSpec> = Record<string, BaseSlotSpec>,
|
|
@@ -13,6 +22,8 @@ export interface ForgedPlugin<
|
|
|
13
22
|
overlays?: unknown[];
|
|
14
23
|
contributions?: ContributionEntry<unknown>[];
|
|
15
24
|
descriptors: Record<keyof TSlots, SlotDescriptor>;
|
|
25
|
+
config?: Record<string, unknown>;
|
|
26
|
+
with(config: PluginConfigInput<TSlots>): ForgedPlugin<TId, TSlots>;
|
|
16
27
|
}
|
|
17
28
|
export type PluginInput =
|
|
18
29
|
| ForgedPlugin<string, Record<string, BaseSlotSpec>>
|
|
@@ -9,7 +9,7 @@ export function definePlugin(spec) {
|
|
|
9
9
|
accessorName: getAccessorName(slotName),
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
|
|
12
|
+
const plugin = {
|
|
13
13
|
id: spec.id,
|
|
14
14
|
requires: spec.requires,
|
|
15
15
|
slots: slots,
|
|
@@ -17,5 +17,15 @@ export function definePlugin(spec) {
|
|
|
17
17
|
overlays: spec.overlays,
|
|
18
18
|
contributions: spec.contributions,
|
|
19
19
|
descriptors: descriptors,
|
|
20
|
+
with(config) {
|
|
21
|
+
return {
|
|
22
|
+
...this,
|
|
23
|
+
config: {
|
|
24
|
+
...(this.config || {}),
|
|
25
|
+
...config,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
20
29
|
};
|
|
30
|
+
return plugin;
|
|
21
31
|
}
|
|
@@ -33,12 +33,6 @@ function normalizePlugins(inputs) {
|
|
|
33
33
|
return true;
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
|
-
function getPluginConfig(pluginId, config) {
|
|
37
|
-
if (config[pluginId] !== undefined) {
|
|
38
|
-
return config[pluginId];
|
|
39
|
-
}
|
|
40
|
-
return config;
|
|
41
|
-
}
|
|
42
36
|
function sortPlugins(resolved) {
|
|
43
37
|
const pluginMap = new Map(resolved.map((p) => [p.id, p]));
|
|
44
38
|
const graphNodes = resolved.map((p) => ({
|
|
@@ -97,6 +91,7 @@ export function normalizeSlots(sorted) {
|
|
|
97
91
|
expose: slotSpec.expose !== false,
|
|
98
92
|
persist: slotSpec.persist,
|
|
99
93
|
configSchema: slotSpec.config,
|
|
94
|
+
pluginConfig: plugin.config,
|
|
100
95
|
create: slotSpec.create,
|
|
101
96
|
};
|
|
102
97
|
allSlots.push(slot);
|
|
@@ -151,18 +146,22 @@ function buildLayers(slots, options) {
|
|
|
151
146
|
const storeLayer = Layer.effect(
|
|
152
147
|
storeTag,
|
|
153
148
|
Effect.gen(function* () {
|
|
154
|
-
const
|
|
155
|
-
let configSlice =
|
|
149
|
+
const rawConfig = slot.pluginConfig?.[slot.slotName];
|
|
150
|
+
let configSlice = rawConfig;
|
|
156
151
|
if (slot.configSchema) {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
152
|
+
if (rawConfig !== undefined) {
|
|
153
|
+
try {
|
|
154
|
+
configSlice = Schema.decodeUnknownSync(slot.configSchema)(
|
|
155
|
+
rawConfig,
|
|
156
|
+
);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
159
|
+
throw new Error(
|
|
160
|
+
`[Kernel] Config validation failed for plugin "${slot.pluginId}", slot "${slot.slotName}": ${errMsg}`,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
} else {
|
|
164
|
+
configSlice = undefined;
|
|
166
165
|
}
|
|
167
166
|
}
|
|
168
167
|
let persistenceHandle;
|
|
@@ -51,13 +51,21 @@ export function createThemeStore(ctx) {
|
|
|
51
51
|
icon: "🎨",
|
|
52
52
|
driver: ctx.persistence,
|
|
53
53
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
if (store.get(
|
|
60
|
-
store.set(
|
|
54
|
+
const saved = ctx.persistence.get("theme");
|
|
55
|
+
const hasPersisted =
|
|
56
|
+
typeof saved === "string" ? Boolean(saved && store.get(saved)) : false;
|
|
57
|
+
if (!hasPersisted) {
|
|
58
|
+
const configTheme = typeof ctx.config === "string" ? ctx.config : undefined;
|
|
59
|
+
if (configTheme && store.get(configTheme)) {
|
|
60
|
+
store.set(configTheme);
|
|
61
|
+
} else if (BROWSER) {
|
|
62
|
+
const prefersDark = globalThis.matchMedia(
|
|
63
|
+
"(prefers-color-scheme: dark)",
|
|
64
|
+
).matches;
|
|
65
|
+
const systemDefault = prefersDark ? "dark" : "light";
|
|
66
|
+
if (store.get(systemDefault)) {
|
|
67
|
+
store.set(systemDefault);
|
|
68
|
+
}
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
return store;
|
|
@@ -166,6 +166,8 @@ function formatKeys(keys: string) {
|
|
|
166
166
|
<span><kbd class="kbd kbd-xs">ctrl</kbd> +
|
|
167
167
|
<kbd class="kbd kbd-xs">/</kbd> to open help</span>
|
|
168
168
|
</div>
|
|
169
|
-
|
|
169
|
+
{#if appStore.data.version}
|
|
170
|
+
<div>rune-lab v{appStore.data.version}</div>
|
|
171
|
+
{/if}
|
|
170
172
|
</div>
|
|
171
173
|
</div>
|
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
import type { ForgedPlugin, SlotSpec } from "rune-lab/core";
|
|
2
2
|
import type { CommandStore } from "./commands/store.svelte.js";
|
|
3
3
|
import type { ToastStore } from "./notifications/store.svelte.js";
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
PaletteRegistryStore,
|
|
6
|
+
RouterAdapter,
|
|
7
|
+
} from "./registry/registry.svelte.js";
|
|
5
8
|
import type { ShortcutStore } from "./shortcuts/store.svelte.js";
|
|
6
|
-
|
|
9
|
+
export interface RegistrySlotConfig {
|
|
10
|
+
router?: RouterAdapter;
|
|
11
|
+
}
|
|
7
12
|
type PalettesSlots = {
|
|
8
13
|
commands: SlotSpec<unknown, CommandStore>;
|
|
9
14
|
shortcuts: SlotSpec<unknown, ShortcutStore>;
|
|
10
15
|
toasts: SlotSpec<unknown, ToastStore>;
|
|
11
|
-
registry: SlotSpec<
|
|
16
|
+
registry: SlotSpec<RegistrySlotConfig, PaletteRegistryStore>;
|
|
12
17
|
};
|
|
13
18
|
export declare const PalettesPlugin: ForgedPlugin<
|
|
14
19
|
"rune-lab.palettes",
|
|
@@ -31,8 +31,8 @@ export const PalettesPlugin = definePlugin({
|
|
|
31
31
|
expose: true,
|
|
32
32
|
}),
|
|
33
33
|
registry: defineSlot({
|
|
34
|
-
create: () => {
|
|
35
|
-
const store = createPaletteRegistryStore();
|
|
34
|
+
create: (ctx) => {
|
|
35
|
+
const store = createPaletteRegistryStore(ctx.config);
|
|
36
36
|
store.register({
|
|
37
37
|
id: "commands",
|
|
38
38
|
title: "Commands",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { Component } from "svelte";
|
|
2
|
+
export interface RouterAdapter {
|
|
3
|
+
replaceState?: (url: string) => void;
|
|
4
|
+
pushState?: (url: string) => void;
|
|
5
|
+
}
|
|
2
6
|
export interface PaletteDefinition {
|
|
3
7
|
id: string;
|
|
4
8
|
title: string;
|
|
@@ -12,10 +16,16 @@ export declare class PaletteRegistryStore {
|
|
|
12
16
|
palettes: PaletteDefinition[];
|
|
13
17
|
activePaletteId: string | null;
|
|
14
18
|
activeSectionId: string;
|
|
19
|
+
router?: RouterAdapter;
|
|
20
|
+
constructor(config?: {
|
|
21
|
+
router?: RouterAdapter;
|
|
22
|
+
});
|
|
15
23
|
register(palette: PaletteDefinition): void;
|
|
16
24
|
unregister(id: string): void;
|
|
17
25
|
open(id: string, section?: string): void;
|
|
18
26
|
setSection(sectionId: string): void;
|
|
19
27
|
close(): void;
|
|
20
28
|
}
|
|
21
|
-
export declare function createPaletteRegistryStore(
|
|
29
|
+
export declare function createPaletteRegistryStore(config?: {
|
|
30
|
+
router?: RouterAdapter;
|
|
31
|
+
}): PaletteRegistryStore;
|
|
@@ -2,6 +2,10 @@ export class PaletteRegistryStore {
|
|
|
2
2
|
palettes = $state([]);
|
|
3
3
|
activePaletteId = $state(null);
|
|
4
4
|
activeSectionId = $state("general");
|
|
5
|
+
router;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.router = config?.router;
|
|
8
|
+
}
|
|
5
9
|
register(palette) {
|
|
6
10
|
if (this.palettes.some((p) => p.id === palette.id)) {
|
|
7
11
|
return;
|
|
@@ -27,6 +31,6 @@ export class PaletteRegistryStore {
|
|
|
27
31
|
this.activePaletteId = null;
|
|
28
32
|
}
|
|
29
33
|
}
|
|
30
|
-
export function createPaletteRegistryStore() {
|
|
31
|
-
return new PaletteRegistryStore();
|
|
34
|
+
export function createPaletteRegistryStore(config) {
|
|
35
|
+
return new PaletteRegistryStore(config);
|
|
32
36
|
}
|
|
@@ -3,18 +3,18 @@ import { getAppStore } from "rune-lab";
|
|
|
3
3
|
|
|
4
4
|
const appStore = getAppStore();
|
|
5
5
|
const initial = $derived(
|
|
6
|
-
appStore.name ? appStore.name.charAt(0).toUpperCase() : "",
|
|
6
|
+
appStore.data.name ? appStore.data.name.charAt(0).toUpperCase() : "",
|
|
7
7
|
);
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
<div class="p-4 flex items-center gap-4 bg-base-200 rounded-box">
|
|
11
|
-
{#if appStore.icon}
|
|
11
|
+
{#if appStore.data.icon}
|
|
12
12
|
<img
|
|
13
|
-
src={appStore.icon}
|
|
14
|
-
alt={appStore.name}
|
|
13
|
+
src={appStore.data.icon}
|
|
14
|
+
alt={appStore.data.name ?? ""}
|
|
15
15
|
class="w-12 h-12 rounded-box object-cover shrink-0"
|
|
16
16
|
/>
|
|
17
|
-
{:else}
|
|
17
|
+
{:else if initial}
|
|
18
18
|
<div class="flex items-center justify-center w-12 h-12 bg-primary/10 text-primary font-bold rounded-box text-xl shrink-0">
|
|
19
19
|
{initial}
|
|
20
20
|
</div>
|
|
@@ -22,29 +22,37 @@ const initial = $derived(
|
|
|
22
22
|
|
|
23
23
|
<div class="flex-1 min-w-0">
|
|
24
24
|
<div class="flex items-center gap-2">
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
{#if appStore.data.name}
|
|
26
|
+
<span class="font-medium text-sm">{appStore.data.name}</span>
|
|
27
|
+
{/if}
|
|
28
|
+
{#if appStore.data.version}
|
|
29
|
+
<span class="badge badge-primary badge-sm">v{appStore.data.version}</span>
|
|
30
|
+
{/if}
|
|
27
31
|
</div>
|
|
28
|
-
|
|
32
|
+
{#if appStore.data.description}
|
|
33
|
+
<p class="text-base-content/60 text-xs truncate mt-0.5">{appStore.data.description}</p>
|
|
34
|
+
{/if}
|
|
29
35
|
</div>
|
|
30
36
|
|
|
31
37
|
<div class="text-right shrink-0 flex flex-col items-end gap-1">
|
|
32
|
-
|
|
38
|
+
{#if appStore.data.author}
|
|
39
|
+
<span class="text-base-content/70 text-xs font-medium">{appStore.data.author}</span>
|
|
40
|
+
{/if}
|
|
33
41
|
<div class="flex items-center gap-2 text-xs text-base-content/50">
|
|
34
|
-
{#if appStore.repository}
|
|
35
|
-
<a href={appStore.repository} target="_blank" rel="noopener noreferrer" class="link link-hover text-xs">Repo</a>
|
|
42
|
+
{#if appStore.data.repository}
|
|
43
|
+
<a href={appStore.data.repository} target="_blank" rel="noopener noreferrer" class="link link-hover text-xs">Repo</a>
|
|
36
44
|
{/if}
|
|
37
|
-
{#if appStore.homepage}
|
|
38
|
-
{#if appStore.repository}
|
|
45
|
+
{#if appStore.data.homepage}
|
|
46
|
+
{#if appStore.data.repository}
|
|
39
47
|
<span>•</span>
|
|
40
48
|
{/if}
|
|
41
|
-
<a href={appStore.homepage} target="_blank" rel="noopener noreferrer" class="link link-hover text-xs">Home</a>
|
|
49
|
+
<a href={appStore.data.homepage} target="_blank" rel="noopener noreferrer" class="link link-hover text-xs">Home</a>
|
|
42
50
|
{/if}
|
|
43
|
-
{#if appStore.license}
|
|
44
|
-
{#if appStore.repository || appStore.homepage}
|
|
51
|
+
{#if appStore.data.license}
|
|
52
|
+
{#if appStore.data.repository || appStore.data.homepage}
|
|
45
53
|
<span>•</span>
|
|
46
54
|
{/if}
|
|
47
|
-
<span class="text-xs">{appStore.license}</span>
|
|
55
|
+
<span class="text-xs">{appStore.data.license}</span>
|
|
48
56
|
{/if}
|
|
49
57
|
</div>
|
|
50
58
|
</div>
|
|
@@ -26,15 +26,21 @@ export function installSettingsRoute(registry) {
|
|
|
26
26
|
if (activeId === "settings") {
|
|
27
27
|
const want = formatSettingsHash(activeSec);
|
|
28
28
|
if (globalThis.location.hash !== want) {
|
|
29
|
-
|
|
29
|
+
if (registry.router?.replaceState) {
|
|
30
|
+
registry.router.replaceState(want);
|
|
31
|
+
} else {
|
|
32
|
+
history.replaceState(null, "", want);
|
|
33
|
+
}
|
|
30
34
|
}
|
|
31
35
|
} else {
|
|
32
36
|
if (globalThis.location.hash.startsWith("#settings")) {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
const targetUrl =
|
|
38
|
+
globalThis.location.pathname + globalThis.location.search;
|
|
39
|
+
if (registry.router?.pushState) {
|
|
40
|
+
registry.router.pushState(targetUrl);
|
|
41
|
+
} else {
|
|
42
|
+
history.pushState(null, "", targetUrl);
|
|
43
|
+
}
|
|
38
44
|
}
|
|
39
45
|
}
|
|
40
46
|
});
|
|
@@ -22,13 +22,10 @@ import { type AppData, createAppStore } from "./reactivity/app.svelte.js";
|
|
|
22
22
|
export interface RuneLabConfig {
|
|
23
23
|
persistence?: PersistenceDriver;
|
|
24
24
|
/** Optional head management properties */
|
|
25
|
-
favicon?: string;
|
|
26
25
|
manageHead?: boolean;
|
|
27
26
|
icons?: "material" | "none";
|
|
28
27
|
/** App metadata — passed to AppStore.init() */
|
|
29
28
|
app?: Partial<AppData>;
|
|
30
|
-
/** Namespaced config for plugins */
|
|
31
|
-
[pluginId: string]: unknown;
|
|
32
29
|
}
|
|
33
30
|
|
|
34
31
|
let {
|
|
@@ -59,11 +56,9 @@ const initialPersistence = untrack(() => {
|
|
|
59
56
|
// 0. Create and provide the built-in AppStore
|
|
60
57
|
const appStore = createAppStore();
|
|
61
58
|
untrack(() => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
66
|
-
appStore.init(appData);
|
|
59
|
+
if (config.app) {
|
|
60
|
+
appStore.init(config.app);
|
|
61
|
+
}
|
|
67
62
|
});
|
|
68
63
|
setContext(RUNE_LAB_CONTEXT.app, appStore);
|
|
69
64
|
|
|
@@ -100,19 +95,23 @@ const allOverlays = $derived(kernel.overlays as Component[]);
|
|
|
100
95
|
|
|
101
96
|
// Meta tags derived from app store state
|
|
102
97
|
const metaTags = $derived([
|
|
103
|
-
{ name: "description", content: appStore.description },
|
|
104
|
-
{ name: "author", content: appStore.author },
|
|
98
|
+
{ name: "description", content: appStore.data.description },
|
|
99
|
+
{ name: "author", content: appStore.data.author },
|
|
105
100
|
]);
|
|
106
101
|
</script>
|
|
107
102
|
|
|
108
103
|
<svelte:head>
|
|
109
104
|
{#if config.manageHead !== false && appStore}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
105
|
+
{#if appStore.data.name}
|
|
106
|
+
<title>{appStore.data.name}</title>
|
|
107
|
+
{/if}
|
|
108
|
+
{#if appStore.data.icon}
|
|
109
|
+
<link rel="icon" href={appStore.data.icon} />
|
|
113
110
|
{/if}
|
|
114
111
|
{#each metaTags as meta}
|
|
115
|
-
|
|
112
|
+
{#if meta.content}
|
|
113
|
+
<meta name={meta.name} content={meta.content} />
|
|
114
|
+
{/if}
|
|
116
115
|
{/each}
|
|
117
116
|
{#if config.icons === "material"}
|
|
118
117
|
<link
|
|
@@ -12,13 +12,10 @@ import { type AppData } from "./reactivity/app.svelte.js";
|
|
|
12
12
|
export interface RuneLabConfig {
|
|
13
13
|
persistence?: PersistenceDriver;
|
|
14
14
|
/** Optional head management properties */
|
|
15
|
-
favicon?: string;
|
|
16
15
|
manageHead?: boolean;
|
|
17
16
|
icons?: "material" | "none";
|
|
18
17
|
/** App metadata — passed to AppStore.init() */
|
|
19
18
|
app?: Partial<AppData>;
|
|
20
|
-
/** Namespaced config for plugins */
|
|
21
|
-
[pluginId: string]: unknown;
|
|
22
19
|
}
|
|
23
20
|
type $$ComponentProps = {
|
|
24
21
|
children: Snippet;
|
package/dist/src/ui/mod.js
CHANGED
|
@@ -15,4 +15,4 @@ export { createAppStore, getAppStore } from "./reactivity/app.svelte.js";
|
|
|
15
15
|
// Reactivity & Context exports
|
|
16
16
|
export { useCell } from "./reactivity/use-cell.svelte.js";
|
|
17
17
|
export { default as SettingsFields } from "./settings/SettingsFields.svelte";
|
|
18
|
-
export const version = () => "0.5.0
|
|
18
|
+
export const version = () => "0.5.0";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type DataStore } from "./data-store.svelte.js";
|
|
1
2
|
export interface AppData {
|
|
2
3
|
name: string;
|
|
3
4
|
version: string;
|
|
@@ -8,17 +9,6 @@ export interface AppData {
|
|
|
8
9
|
homepage?: string;
|
|
9
10
|
icon?: string;
|
|
10
11
|
}
|
|
11
|
-
export
|
|
12
|
-
#private;
|
|
13
|
-
name: string;
|
|
14
|
-
version: string;
|
|
15
|
-
description: string;
|
|
16
|
-
author: string;
|
|
17
|
-
repository: string;
|
|
18
|
-
license: string;
|
|
19
|
-
homepage: string;
|
|
20
|
-
icon: string;
|
|
21
|
-
init(data: Partial<AppData>): void;
|
|
22
|
-
}
|
|
12
|
+
export type AppStore = DataStore<AppData>;
|
|
23
13
|
export declare function createAppStore(): AppStore;
|
|
24
14
|
export declare function getAppStore(): AppStore;
|
|
@@ -1,56 +1,19 @@
|
|
|
1
|
-
import { DEV } from "esm-env";
|
|
2
1
|
import { getContext } from "svelte";
|
|
3
2
|
import { RUNE_LAB_CONTEXT } from "../provider/context.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (DEV) {
|
|
17
|
-
console.warn(
|
|
18
|
-
"AppStore.init() called multiple times. Ignoring subsequent calls.",
|
|
19
|
-
"Overwritten properties would have been:",
|
|
20
|
-
data,
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
if (data.name) {
|
|
26
|
-
this.name = data.name;
|
|
27
|
-
}
|
|
28
|
-
if (data.version) {
|
|
29
|
-
this.version = data.version;
|
|
30
|
-
}
|
|
31
|
-
if (data.description) {
|
|
32
|
-
this.description = data.description;
|
|
33
|
-
}
|
|
34
|
-
if (data.author) {
|
|
35
|
-
this.author = data.author;
|
|
36
|
-
}
|
|
37
|
-
if (data.repository) {
|
|
38
|
-
this.repository = data.repository;
|
|
39
|
-
}
|
|
40
|
-
if (data.license) {
|
|
41
|
-
this.license = data.license;
|
|
42
|
-
}
|
|
43
|
-
if (data.homepage) {
|
|
44
|
-
this.homepage = data.homepage;
|
|
45
|
-
}
|
|
46
|
-
if (data.icon) {
|
|
47
|
-
this.icon = data.icon;
|
|
48
|
-
}
|
|
49
|
-
this.#initialized = true;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
3
|
+
import { createDataStore } from "./data-store.svelte.js";
|
|
4
|
+
|
|
5
|
+
const APP_DATA_KEYS = [
|
|
6
|
+
"name",
|
|
7
|
+
"version",
|
|
8
|
+
"description",
|
|
9
|
+
"author",
|
|
10
|
+
"repository",
|
|
11
|
+
"license",
|
|
12
|
+
"homepage",
|
|
13
|
+
"icon",
|
|
14
|
+
];
|
|
52
15
|
export function createAppStore() {
|
|
53
|
-
return
|
|
16
|
+
return createDataStore(APP_DATA_KEYS);
|
|
54
17
|
}
|
|
55
18
|
export function getAppStore() {
|
|
56
19
|
const store = getContext(RUNE_LAB_CONTEXT.app);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type NullableData<T> = {
|
|
2
|
+
[K in keyof T]: T[K] | null;
|
|
3
|
+
};
|
|
4
|
+
export interface DataStore<T extends object> {
|
|
5
|
+
readonly data: NullableData<T>;
|
|
6
|
+
init(input?: Partial<T>): void;
|
|
7
|
+
}
|
|
8
|
+
export declare function createDataStore<T extends object>(
|
|
9
|
+
keys: readonly (keyof T & string)[],
|
|
10
|
+
): DataStore<T>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { DEV } from "esm-env";
|
|
2
|
+
export function createDataStore(keys) {
|
|
3
|
+
const initial = {};
|
|
4
|
+
for (const k of keys) {
|
|
5
|
+
initial[k] = null;
|
|
6
|
+
}
|
|
7
|
+
const data = $state(initial);
|
|
8
|
+
let initialized = false;
|
|
9
|
+
return {
|
|
10
|
+
get data() {
|
|
11
|
+
return data;
|
|
12
|
+
},
|
|
13
|
+
init(input) {
|
|
14
|
+
if (initialized) {
|
|
15
|
+
if (DEV) {
|
|
16
|
+
console.warn(
|
|
17
|
+
"DataStore.init() called multiple times. Ignoring subsequent calls.",
|
|
18
|
+
"Overwritten properties would have been:",
|
|
19
|
+
input,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
initialized = true;
|
|
25
|
+
if (input) {
|
|
26
|
+
for (const k of keys) {
|
|
27
|
+
const val = input[k];
|
|
28
|
+
if (val !== undefined && val !== null) {
|
|
29
|
+
data[k] = val;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { getKernel } from "../provider/context.js";
|
|
3
3
|
import { useCell } from "../reactivity/use-cell.svelte.js";
|
|
4
4
|
|
|
5
5
|
let {
|
|
@@ -12,13 +12,15 @@ let {
|
|
|
12
12
|
onCommit?: (fieldId: string, value: any) => void;
|
|
13
13
|
} = $props();
|
|
14
14
|
|
|
15
|
+
const kernel = getKernel();
|
|
16
|
+
|
|
15
17
|
// Resolve cells reactively using Svelte 5's $derived.by
|
|
16
18
|
const cellBinds = $derived.by(() => {
|
|
17
19
|
const binds = new Map<string, any>();
|
|
18
20
|
for (const field of fields) {
|
|
19
21
|
if (field.target?.type === "cell") {
|
|
20
22
|
try {
|
|
21
|
-
binds.set(field.id, useCell(field.target.name));
|
|
23
|
+
binds.set(field.id, useCell(kernel, field.target.name));
|
|
22
24
|
} catch (e) {
|
|
23
25
|
console.warn(
|
|
24
26
|
`[SettingsFields] Failed to bind cell ${field.target.name}:`,
|
|
@@ -33,7 +35,8 @@ const cellBinds = $derived.by(() => {
|
|
|
33
35
|
function getStoreForField(field: any): any {
|
|
34
36
|
if (field.target?.type !== "store") return undefined;
|
|
35
37
|
const pluginId = field.id.slice(0, field.id.lastIndexOf("."));
|
|
36
|
-
|
|
38
|
+
const storeKey = `rl:${pluginId}:${field.target.storeId}`;
|
|
39
|
+
return kernel.stores.get(storeKey);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
function getValue(field: any): any {
|