rune-lab 0.0.20 â 0.0.21
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/components/RuneProvider.svelte +10 -7
- package/dist/composables/usePersistence.d.ts +2 -0
- package/dist/composables/usePersistence.js +5 -0
- package/dist/context.d.ts +1 -0
- package/dist/context.js +1 -0
- package/dist/devtools/Toaster.svelte +1 -4
- package/dist/features/layout/smart/ConnectedNavigationPanel.svelte +30 -0
- package/dist/features/layout/smart/ConnectedNavigationPanel.svelte.d.ts +10 -0
- package/dist/features/layout/smart/ConnectedWorkspaceStrip.svelte +23 -0
- package/dist/features/layout/smart/ConnectedWorkspaceStrip.svelte.d.ts +9 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +11 -3
- package/dist/layout/NavigationPanel.svelte +62 -32
- package/dist/layout/NavigationPanel.svelte.d.ts +5 -1
- package/dist/layout/WorkspaceLayout.svelte +82 -47
- package/dist/layout/WorkspaceStrip.svelte +17 -20
- package/dist/layout/WorkspaceStrip.svelte.d.ts +3 -1
- package/dist/state/commands.svelte.js +3 -77
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { setContext, type Snippet } from "svelte";
|
|
2
|
+
import { setContext, untrack, type Snippet } from "svelte";
|
|
3
3
|
import {
|
|
4
4
|
createAppStore,
|
|
5
5
|
createLayoutStore,
|
|
@@ -25,15 +25,17 @@
|
|
|
25
25
|
const appStore = createAppStore();
|
|
26
26
|
const apiStore = createApiStore();
|
|
27
27
|
const toastStore = createToastStore();
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
const
|
|
28
|
+
|
|
29
|
+
// Capture the initial persistence prop to avoid Svelte 5 reactive capture warnings
|
|
30
|
+
const initialPersistence = untrack(() => persistence);
|
|
31
|
+
|
|
32
|
+
const themeStore = createThemeStore(initialPersistence);
|
|
33
|
+
const languageStore = createLanguageStore(initialPersistence);
|
|
34
|
+
const currencyStore = createCurrencyStore(initialPersistence);
|
|
33
35
|
const shortcutStore = createShortcutStore();
|
|
34
36
|
|
|
35
37
|
// 2. Initialize Complex Stores (Dependency Injection)
|
|
36
|
-
const layoutStore = createLayoutStore(
|
|
38
|
+
const layoutStore = createLayoutStore(initialPersistence);
|
|
37
39
|
const commandStore = createCommandStore({
|
|
38
40
|
appStore,
|
|
39
41
|
apiStore,
|
|
@@ -53,6 +55,7 @@
|
|
|
53
55
|
setContext(RUNE_LAB_CONTEXT.shortcut, shortcutStore);
|
|
54
56
|
setContext(RUNE_LAB_CONTEXT.layout, layoutStore);
|
|
55
57
|
setContext(RUNE_LAB_CONTEXT.commands, commandStore);
|
|
58
|
+
setContext(RUNE_LAB_CONTEXT.persistence, initialPersistence);
|
|
56
59
|
|
|
57
60
|
// Meta tags derived from app store state
|
|
58
61
|
const metaTags = $derived([
|
package/dist/context.d.ts
CHANGED
package/dist/context.js
CHANGED
|
@@ -48,10 +48,7 @@
|
|
|
48
48
|
{@const styles = typeDetails[toast.type] || typeDetails.info}
|
|
49
49
|
|
|
50
50
|
<div
|
|
51
|
-
|
|
52
|
-
in:fly={{ y: 20, duration: 300 }}
|
|
53
|
-
out:fade={{ duration: 200 }}
|
|
54
|
-
class="pointer-events-auto relative flex w-full max-w-sm items-start gap-4 overflow-hidden rounded-xl border p-4 shadow-lg backdrop-blur-xl transition-all sm:min-w-[320px] {styles.colors}"
|
|
51
|
+
class="pointer-events-auto relative flex w-full max-w-sm items-start gap-4 overflow-hidden rounded-xl border p-4 shadow-lg backdrop-blur-xl transition-all duration-300 sm:min-w-[320px] {styles.colors} animate-in fade-in slide-in-from-bottom-4 zoom-in-95 ease-out data-[state=closed]:animate-out data-[state=closed]:fade-out data-[state=closed]:slide-out-to-right-full"
|
|
55
52
|
role="alert"
|
|
56
53
|
>
|
|
57
54
|
<!-- Icon -->
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<!-- src/lib/features/layout/smart/ConnectedNavigationPanel.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import {
|
|
4
|
+
getLayoutStore,
|
|
5
|
+
type NavigationSection,
|
|
6
|
+
} from "../../../state/layout.svelte";
|
|
7
|
+
import NavigationPanel from "../../../layout/NavigationPanel.svelte";
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
|
|
10
|
+
let { sections, header, footer } = $props<{
|
|
11
|
+
sections: NavigationSection[];
|
|
12
|
+
header?: Snippet;
|
|
13
|
+
footer?: Snippet;
|
|
14
|
+
}>();
|
|
15
|
+
|
|
16
|
+
const layoutStore = getLayoutStore();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<NavigationPanel
|
|
20
|
+
{sections}
|
|
21
|
+
{header}
|
|
22
|
+
{footer}
|
|
23
|
+
activeId={layoutStore.activeNavItemId}
|
|
24
|
+
collapsedIds={layoutStore.collapsedSections}
|
|
25
|
+
onSelect={(item) => layoutStore.navigate(item.id!)}
|
|
26
|
+
onToggle={(id, isOpen) =>
|
|
27
|
+
isOpen
|
|
28
|
+
? layoutStore.expandSection(id)
|
|
29
|
+
: layoutStore.collapseSection(id)}
|
|
30
|
+
/>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type NavigationSection } from "../../../state/layout.svelte";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
sections: NavigationSection[];
|
|
5
|
+
header?: Snippet;
|
|
6
|
+
footer?: Snippet;
|
|
7
|
+
};
|
|
8
|
+
declare const ConnectedNavigationPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
|
+
type ConnectedNavigationPanel = ReturnType<typeof ConnectedNavigationPanel>;
|
|
10
|
+
export default ConnectedNavigationPanel;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!-- src/lib/features/layout/smart/ConnectedWorkspaceStrip.svelte -->
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
import {
|
|
4
|
+
getLayoutStore,
|
|
5
|
+
type WorkspaceItem,
|
|
6
|
+
} from "../../../state/layout.svelte";
|
|
7
|
+
import WorkspaceStrip from "../../../layout/WorkspaceStrip.svelte";
|
|
8
|
+
import type { Snippet } from "svelte";
|
|
9
|
+
|
|
10
|
+
let { items, globalActions } = $props<{
|
|
11
|
+
items: WorkspaceItem[];
|
|
12
|
+
globalActions?: Snippet;
|
|
13
|
+
}>();
|
|
14
|
+
|
|
15
|
+
const layoutStore = getLayoutStore();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<WorkspaceStrip
|
|
19
|
+
{items}
|
|
20
|
+
{globalActions}
|
|
21
|
+
activeId={layoutStore.activeWorkspaceId}
|
|
22
|
+
onSelect={(id) => layoutStore.activateWorkspace(id)}
|
|
23
|
+
/>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type WorkspaceItem } from "../../../state/layout.svelte";
|
|
2
|
+
import type { Snippet } from "svelte";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
items: WorkspaceItem[];
|
|
5
|
+
globalActions?: Snippet;
|
|
6
|
+
};
|
|
7
|
+
declare const ConnectedWorkspaceStrip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type ConnectedWorkspaceStrip = ReturnType<typeof ConnectedWorkspaceStrip>;
|
|
9
|
+
export default ConnectedWorkspaceStrip;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { createMessageResolver } from "./devtools/message-resolver";
|
|
|
3
3
|
export { RUNE_LAB_CONTEXT } from "./context";
|
|
4
4
|
export type { PersistenceDriver } from "./persistence/types";
|
|
5
5
|
export { cookieDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
6
|
-
export { useRuneLab } from "./composables/useRuneLab";
|
|
7
|
-
export
|
|
6
|
+
export { type RuneLabContext, useRuneLab } from "./composables/useRuneLab.js";
|
|
7
|
+
export { usePersistence } from "./composables/usePersistence.js";
|
|
8
8
|
export { portal } from "./actions/portal";
|
|
9
9
|
export { default as RuneProvider } from "./components/RuneProvider.svelte";
|
|
10
10
|
export { default as Icon } from "./components/Icon.svelte";
|
|
@@ -12,7 +12,13 @@ export { default as Toaster } from "./devtools/Toaster.svelte";
|
|
|
12
12
|
export { default as ApiMonitor } from "./devtools/API_Monitor.svelte";
|
|
13
13
|
export { default as CommandPalette } from "./features/command-palette/CommandPalette.svelte";
|
|
14
14
|
export { default as ShortcutPalette } from "./features/shortcuts/ShortcutPalette.svelte";
|
|
15
|
-
export
|
|
15
|
+
export { default as WorkspaceLayout } from "./layout/WorkspaceLayout.svelte";
|
|
16
|
+
export { default as WorkspaceStrip } from "./layout/WorkspaceStrip.svelte";
|
|
17
|
+
export { default as NavigationPanel } from "./layout/NavigationPanel.svelte";
|
|
18
|
+
export { default as ContentArea } from "./layout/ContentArea.svelte";
|
|
19
|
+
export { default as DetailPanel } from "./layout/DetailPanel.svelte";
|
|
20
|
+
export { default as ConnectedNavigationPanel } from "./features/layout/smart/ConnectedNavigationPanel.svelte";
|
|
21
|
+
export { default as ConnectedWorkspaceStrip } from "./features/layout/smart/ConnectedWorkspaceStrip.svelte";
|
|
16
22
|
export { default as AppSettingSelector } from "./features/config/components/AppSettingSelector.svelte";
|
|
17
23
|
export { default as ThemeSelector } from "./features/config/components/ThemeSelector.svelte";
|
|
18
24
|
export { default as LanguageSelector } from "./features/config/components/LanguageSelector.svelte";
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,8 @@ export { createMessageResolver } from "./devtools/message-resolver";
|
|
|
6
6
|
export { RUNE_LAB_CONTEXT } from "./context";
|
|
7
7
|
export { cookieDriver, inMemoryDriver, localStorageDriver, sessionStorageDriver, } from "./persistence/drivers";
|
|
8
8
|
// ââ Composables âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
9
|
-
export { useRuneLab } from "./composables/useRuneLab";
|
|
9
|
+
export { useRuneLab } from "./composables/useRuneLab.js";
|
|
10
|
+
export { usePersistence } from "./composables/usePersistence.js";
|
|
10
11
|
// ââ Actions âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
11
12
|
export { portal } from "./actions/portal";
|
|
12
13
|
// ââ UI Components âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
|
|
@@ -18,8 +19,15 @@ export { default as Toaster } from "./devtools/Toaster.svelte";
|
|
|
18
19
|
export { default as ApiMonitor } from "./devtools/API_Monitor.svelte";
|
|
19
20
|
export { default as CommandPalette } from "./features/command-palette/CommandPalette.svelte";
|
|
20
21
|
export { default as ShortcutPalette } from "./features/shortcuts/ShortcutPalette.svelte";
|
|
21
|
-
// Layout
|
|
22
|
-
export
|
|
22
|
+
// Layout - Dumb Primitives
|
|
23
|
+
export { default as WorkspaceLayout } from "./layout/WorkspaceLayout.svelte";
|
|
24
|
+
export { default as WorkspaceStrip } from "./layout/WorkspaceStrip.svelte";
|
|
25
|
+
export { default as NavigationPanel } from "./layout/NavigationPanel.svelte";
|
|
26
|
+
export { default as ContentArea } from "./layout/ContentArea.svelte";
|
|
27
|
+
export { default as DetailPanel } from "./layout/DetailPanel.svelte";
|
|
28
|
+
// Layout - Smart Connected Components
|
|
29
|
+
export { default as ConnectedNavigationPanel } from "./features/layout/smart/ConnectedNavigationPanel.svelte";
|
|
30
|
+
export { default as ConnectedWorkspaceStrip } from "./features/layout/smart/ConnectedWorkspaceStrip.svelte";
|
|
23
31
|
// Setting selectors
|
|
24
32
|
export { default as AppSettingSelector } from "./features/config/components/AppSettingSelector.svelte";
|
|
25
33
|
export { default as ThemeSelector } from "./features/config/components/ThemeSelector.svelte";
|
|
@@ -1,31 +1,82 @@
|
|
|
1
1
|
<!-- src/lib/layout/NavigationPanel.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
type NavigationSection,
|
|
4
|
+
import type {
|
|
5
|
+
NavigationItem,
|
|
6
|
+
NavigationSection,
|
|
8
7
|
} from "../state/layout.svelte";
|
|
9
|
-
import { page } from "$app/stores"; // Check if this is needed, or use layoutStore for active item
|
|
10
8
|
|
|
11
9
|
let {
|
|
12
10
|
header,
|
|
13
11
|
sections = [],
|
|
14
12
|
footer,
|
|
13
|
+
activeId,
|
|
14
|
+
collapsedIds = new Set(),
|
|
15
|
+
onSelect,
|
|
16
|
+
onToggle,
|
|
15
17
|
} = $props<{
|
|
16
18
|
header?: Snippet;
|
|
17
19
|
sections: NavigationSection[];
|
|
18
20
|
footer?: Snippet;
|
|
21
|
+
activeId?: string | null;
|
|
22
|
+
collapsedIds?: Set<string>;
|
|
23
|
+
onSelect?: (item: NavigationItem) => void;
|
|
24
|
+
onToggle?: (id: string, isOpen: boolean) => void;
|
|
19
25
|
}>();
|
|
20
26
|
|
|
21
|
-
const layoutStore = getLayoutStore();
|
|
22
|
-
|
|
23
27
|
function handleItemClick(item: NavigationItem) {
|
|
24
|
-
|
|
28
|
+
onSelect?.(item);
|
|
29
|
+
// Only trigger click handler, don't auto-navigate here (let parent handle it)
|
|
25
30
|
item.onClick?.();
|
|
26
31
|
}
|
|
27
32
|
</script>
|
|
28
33
|
|
|
34
|
+
{#snippet navItem(item: NavigationItem)}
|
|
35
|
+
<li>
|
|
36
|
+
{#if item.children && item.children.length > 0}
|
|
37
|
+
<!-- Folder Item (Recursive) -->
|
|
38
|
+
<details
|
|
39
|
+
open={/* You might want a way to track open state for sub-folders too */ false}
|
|
40
|
+
>
|
|
41
|
+
<summary>
|
|
42
|
+
{#if item.icon}<span class="text-lg">{item.icon}</span>{/if}
|
|
43
|
+
<span class="flex-1">{item.label}</span>
|
|
44
|
+
</summary>
|
|
45
|
+
<ul>
|
|
46
|
+
{#each item.children as child (child.id)}
|
|
47
|
+
{@render navItem(child)}
|
|
48
|
+
<!-- âģī¸ RECURSION HAPPENS HERE -->
|
|
49
|
+
{/each}
|
|
50
|
+
</ul>
|
|
51
|
+
</details>
|
|
52
|
+
{:else}
|
|
53
|
+
<!-- Leaf Item -->
|
|
54
|
+
<button
|
|
55
|
+
class:active={item.isActive !== undefined
|
|
56
|
+
? item.isActive
|
|
57
|
+
: activeId === item.id}
|
|
58
|
+
onclick={() => handleItemClick(item)}
|
|
59
|
+
>
|
|
60
|
+
{#if item.icon}
|
|
61
|
+
{#if typeof item.icon === "string" && (item.icon.startsWith("http") || item.icon.length > 2)}
|
|
62
|
+
<!-- Image or SVG string -->
|
|
63
|
+
<span class="text-lg">{item.icon}</span>
|
|
64
|
+
{:else}
|
|
65
|
+
<!-- Emoji or simple char -->
|
|
66
|
+
<span class="text-lg">{item.icon}</span>
|
|
67
|
+
{/if}
|
|
68
|
+
{/if}
|
|
69
|
+
|
|
70
|
+
<span class="flex-1">{item.label}</span>
|
|
71
|
+
|
|
72
|
+
{#if item.badge}
|
|
73
|
+
<span class="badge badge-sm badge-ghost">{item.badge}</span>
|
|
74
|
+
{/if}
|
|
75
|
+
</button>
|
|
76
|
+
{/if}
|
|
77
|
+
</li>
|
|
78
|
+
{/snippet}
|
|
79
|
+
|
|
29
80
|
<div class="flex flex-col h-full bg-base-200 text-base-content">
|
|
30
81
|
{#if header}
|
|
31
82
|
<div class="p-4 border-b border-base-content/10">
|
|
@@ -38,12 +89,11 @@
|
|
|
38
89
|
{#each sections as section (section.id)}
|
|
39
90
|
<li>
|
|
40
91
|
<details
|
|
41
|
-
open={!
|
|
92
|
+
open={!collapsedIds.has(section.id)}
|
|
42
93
|
ontoggle={(e) => {
|
|
43
94
|
const open = (e.currentTarget as HTMLDetailsElement)
|
|
44
95
|
.open;
|
|
45
|
-
|
|
46
|
-
else layoutStore.collapseSection(section.id);
|
|
96
|
+
onToggle?.(section.id, open);
|
|
47
97
|
}}
|
|
48
98
|
>
|
|
49
99
|
<summary
|
|
@@ -53,27 +103,7 @@
|
|
|
53
103
|
</summary>
|
|
54
104
|
<ul>
|
|
55
105
|
{#each section.items as item (item.id)}
|
|
56
|
-
|
|
57
|
-
<li>
|
|
58
|
-
<button
|
|
59
|
-
class:active={layoutStore.activeNavItemId ===
|
|
60
|
-
item.id}
|
|
61
|
-
onclick={() => handleItemClick(item)}
|
|
62
|
-
>
|
|
63
|
-
{#if item.icon}
|
|
64
|
-
<span class="text-lg"
|
|
65
|
-
>{item.icon}</span
|
|
66
|
-
>
|
|
67
|
-
{/if}
|
|
68
|
-
<span class="flex-1">{item.label}</span>
|
|
69
|
-
{#if item.badge}
|
|
70
|
-
<span
|
|
71
|
-
class="badge badge-sm badge-ghost"
|
|
72
|
-
>{item.badge}</span
|
|
73
|
-
>
|
|
74
|
-
{/if}
|
|
75
|
-
</button>
|
|
76
|
-
</li>
|
|
106
|
+
{@render navItem(item)}
|
|
77
107
|
{/each}
|
|
78
108
|
</ul>
|
|
79
109
|
</details>
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import {
|
|
2
|
+
import type { NavigationItem, NavigationSection } from "../state/layout.svelte";
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
header?: Snippet;
|
|
5
5
|
sections: NavigationSection[];
|
|
6
6
|
footer?: Snippet;
|
|
7
|
+
activeId?: string | null;
|
|
8
|
+
collapsedIds?: Set<string>;
|
|
9
|
+
onSelect?: (item: NavigationItem) => void;
|
|
10
|
+
onToggle?: (id: string, isOpen: boolean) => void;
|
|
7
11
|
};
|
|
8
12
|
declare const NavigationPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
9
13
|
type NavigationPanel = ReturnType<typeof NavigationPanel>;
|
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
<!-- src/lib/layout/WorkspaceLayout.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
-
import {
|
|
5
|
-
createLayoutStore,
|
|
6
|
-
getLayoutStore,
|
|
7
|
-
} from "../state/layout.svelte";
|
|
4
|
+
import { getLayoutStore } from "../state/layout.svelte";
|
|
8
5
|
import {
|
|
9
6
|
getShortcutStore,
|
|
10
7
|
shortcutListener,
|
|
11
8
|
LAYOUT_SHORTCUTS,
|
|
12
9
|
} from "../state/shortcuts.svelte";
|
|
13
|
-
import { onMount
|
|
10
|
+
import { onMount } from "svelte";
|
|
14
11
|
|
|
15
12
|
let {
|
|
16
13
|
workspaceStrip,
|
|
@@ -68,66 +65,104 @@
|
|
|
68
65
|
</script>
|
|
69
66
|
|
|
70
67
|
<div
|
|
71
|
-
class="rl-layout h-
|
|
72
|
-
style="
|
|
73
|
-
grid-template-columns:
|
|
74
|
-
{workspaceStrip ? 'var(--rl-strip-width, 72px)' : '0px'}
|
|
75
|
-
{navigationPanel && layoutStore.navOpen
|
|
76
|
-
? 'var(--rl-nav-width, 240px)'
|
|
77
|
-
: '0px'}
|
|
78
|
-
1fr
|
|
79
|
-
{detailPanel && layoutStore.detailOpen
|
|
80
|
-
? 'var(--rl-detail-width, 320px)'
|
|
81
|
-
: '0px'};
|
|
82
|
-
grid-template-rows: 100%;
|
|
83
|
-
transition: grid-template-columns 300ms ease-in-out;
|
|
84
|
-
"
|
|
68
|
+
class="rl-layout flex h-[100dvh] w-screen overflow-hidden bg-base-100 text-base-content font-sans relative"
|
|
85
69
|
use:shortcutListener={shortcutStore}
|
|
86
70
|
data-rl-layout
|
|
87
71
|
>
|
|
88
72
|
<!-- Zone 1: Workspace Strip -->
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
{#if workspaceStrip}
|
|
73
|
+
{#if workspaceStrip}
|
|
74
|
+
<aside
|
|
75
|
+
class="rl-strip h-full w-[var(--rl-strip-width,72px)] shrink-0 overflow-y-auto overflow-x-hidden bg-base-300 flex flex-col items-center py-3 gap-2 z-[60]"
|
|
76
|
+
>
|
|
94
77
|
{@render workspaceStrip()}
|
|
95
|
-
|
|
96
|
-
|
|
78
|
+
</aside>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
<!-- Mobile Nav Backdrop -->
|
|
82
|
+
{#if navigationPanel && layoutStore.navOpen}
|
|
83
|
+
<button
|
|
84
|
+
class="fixed inset-0 bg-black/50 z-[40] md:hidden cursor-default border-none"
|
|
85
|
+
onclick={() => (layoutStore.navOpen = false)}
|
|
86
|
+
aria-label="Close navigation"
|
|
87
|
+
></button>
|
|
88
|
+
{/if}
|
|
97
89
|
|
|
98
90
|
<!-- Zone 2: Navigation Panel -->
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
91
|
+
{#if navigationPanel}
|
|
92
|
+
<aside
|
|
93
|
+
class="rl-nav h-full shrink-0 bg-base-200 overflow-hidden flex flex-col transition-all duration-300 ease-in-out z-[50]"
|
|
94
|
+
class:border-r={layoutStore.navOpen}
|
|
95
|
+
class:border-base-content={true}
|
|
96
|
+
style="
|
|
97
|
+
border-opacity: 0.05;
|
|
98
|
+
width: {layoutStore.navOpen
|
|
99
|
+
? 'var(--rl-nav-width, 240px)'
|
|
100
|
+
: '0px'};
|
|
101
|
+
left: {workspaceStrip ? 'var(--rl-strip-width, 72px)' : '0px'};
|
|
102
|
+
"
|
|
103
|
+
class:max-md:!w-[var(--rl-nav-width,240px)]={true}
|
|
104
|
+
class:max-md:fixed={true}
|
|
105
|
+
class:max-md:-translate-x-[200%]={!layoutStore.navOpen}
|
|
106
|
+
data-rl-panel="navigation"
|
|
107
|
+
>
|
|
108
|
+
<div class="w-[var(--rl-nav-width,240px)] h-full flex flex-col">
|
|
109
|
+
{@render navigationPanel()}
|
|
110
|
+
</div>
|
|
111
|
+
</aside>
|
|
112
|
+
{/if}
|
|
109
113
|
|
|
110
114
|
<!-- Zone 3: Main Content Area -->
|
|
111
115
|
<main
|
|
112
|
-
class="rl-content h-full min-w-0 bg-base-100 flex flex-col overflow-hidden relative"
|
|
116
|
+
class="rl-content h-full flex-1 min-w-0 bg-base-100 flex flex-col overflow-hidden relative"
|
|
113
117
|
>
|
|
114
118
|
{@render content()}
|
|
115
119
|
</main>
|
|
116
120
|
|
|
121
|
+
<!-- Mobile Detail Backdrop -->
|
|
122
|
+
{#if detailPanel && layoutStore.detailOpen}
|
|
123
|
+
<button
|
|
124
|
+
class="fixed inset-0 bg-black/50 z-[40] md:hidden cursor-default border-none"
|
|
125
|
+
onclick={() => (layoutStore.detailOpen = false)}
|
|
126
|
+
aria-label="Close detail panel"
|
|
127
|
+
></button>
|
|
128
|
+
{/if}
|
|
129
|
+
|
|
117
130
|
<!-- Zone 4: Detail Panel -->
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
131
|
+
{#if detailPanel}
|
|
132
|
+
<aside
|
|
133
|
+
class="rl-detail h-full shrink-0 bg-base-100 overflow-y-auto overflow-x-hidden transition-all duration-300 ease-in-out z-[50]"
|
|
134
|
+
class:border-l={layoutStore.detailOpen}
|
|
135
|
+
class:border-base-content={true}
|
|
136
|
+
style="
|
|
137
|
+
border-opacity: 0.05;
|
|
138
|
+
width: {layoutStore.detailOpen
|
|
139
|
+
? 'var(--rl-detail-width, 320px)'
|
|
140
|
+
: '0px'};
|
|
141
|
+
"
|
|
142
|
+
class:max-md:!w-[var(--rl-detail-width,320px)]={true}
|
|
143
|
+
class:max-md:fixed={true}
|
|
144
|
+
class:max-md:right-0={true}
|
|
145
|
+
class:max-md:translate-x-[200%]={!layoutStore.detailOpen}
|
|
146
|
+
data-rl-panel="detail"
|
|
147
|
+
>
|
|
148
|
+
<div class="w-[var(--rl-detail-width,320px)] h-full flex flex-col">
|
|
149
|
+
{@render detailPanel()}
|
|
150
|
+
</div>
|
|
151
|
+
</aside>
|
|
152
|
+
{/if}
|
|
128
153
|
</div>
|
|
129
154
|
|
|
130
155
|
<style>
|
|
156
|
+
/* Global lock for entire screen, relying on WorkspaceLayout scroll areas */
|
|
157
|
+
:global(html),
|
|
158
|
+
:global(body) {
|
|
159
|
+
overflow: hidden;
|
|
160
|
+
height: 100%;
|
|
161
|
+
width: 100%;
|
|
162
|
+
margin: 0;
|
|
163
|
+
padding: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
131
166
|
:global(.rl-layout) {
|
|
132
167
|
--rl-strip-width: 72px;
|
|
133
168
|
--rl-nav-width: 240px;
|
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
<!-- src/lib/layout/WorkspaceStrip.svelte -->
|
|
2
2
|
<script lang="ts">
|
|
3
3
|
import type { Snippet } from "svelte";
|
|
4
|
-
import {
|
|
5
|
-
getLayoutStore,
|
|
6
|
-
type WorkspaceItem,
|
|
7
|
-
} from "../state/layout.svelte";
|
|
4
|
+
import type { WorkspaceItem } from "../state/layout.svelte";
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
let {
|
|
7
|
+
items = [],
|
|
8
|
+
globalActions,
|
|
9
|
+
activeId,
|
|
10
|
+
onSelect,
|
|
11
|
+
} = $props<{
|
|
12
12
|
items: WorkspaceItem[];
|
|
13
13
|
globalActions?: Snippet;
|
|
14
|
+
activeId?: string | null;
|
|
15
|
+
onSelect?: (id: string, item: WorkspaceItem) => void;
|
|
14
16
|
}>();
|
|
15
17
|
|
|
16
|
-
$effect(() => {
|
|
17
|
-
layoutStore.setWorkspaces(items);
|
|
18
|
-
});
|
|
19
|
-
|
|
20
18
|
function handleWorkspaceClick(item: WorkspaceItem) {
|
|
21
|
-
|
|
19
|
+
onSelect?.(item.id, item);
|
|
22
20
|
item.onClick?.();
|
|
23
21
|
}
|
|
24
22
|
</script>
|
|
@@ -29,19 +27,18 @@
|
|
|
29
27
|
<!-- Discord-style active indicator -->
|
|
30
28
|
<div
|
|
31
29
|
class="absolute left-0 w-1 bg-base-content rounded-r-full transition-all duration-200"
|
|
32
|
-
class:h-10={
|
|
33
|
-
class:h-2={
|
|
34
|
-
class:opacity-100={
|
|
35
|
-
class:opacity-0={
|
|
30
|
+
class:h-10={activeId === item.id}
|
|
31
|
+
class:h-2={activeId !== item.id}
|
|
32
|
+
class:opacity-100={activeId === item.id}
|
|
33
|
+
class:opacity-0={activeId !== item.id}
|
|
36
34
|
></div>
|
|
37
35
|
|
|
38
36
|
<button
|
|
39
37
|
onclick={() => handleWorkspaceClick(item)}
|
|
40
38
|
class="w-12 h-12 flex items-center justify-center rounded-[24px] hover:rounded-[16px] transition-all duration-200 bg-base-100 group-hover:bg-primary group-hover:text-primary-content shadow-sm overflow-hidden"
|
|
41
|
-
class:rounded-[16px]={
|
|
42
|
-
class:bg-primary={
|
|
43
|
-
class:text-primary-content={
|
|
44
|
-
item.id}
|
|
39
|
+
class:rounded-[16px]={activeId === item.id}
|
|
40
|
+
class:bg-primary={activeId === item.id}
|
|
41
|
+
class:text-primary-content={activeId === item.id}
|
|
45
42
|
title={item.label}
|
|
46
43
|
>
|
|
47
44
|
{#if item.icon.startsWith("http") || item.icon.startsWith("/")}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import {
|
|
2
|
+
import type { WorkspaceItem } from "../state/layout.svelte";
|
|
3
3
|
type $$ComponentProps = {
|
|
4
4
|
items: WorkspaceItem[];
|
|
5
5
|
globalActions?: Snippet;
|
|
6
|
+
activeId?: string | null;
|
|
7
|
+
onSelect?: (id: string, item: WorkspaceItem) => void;
|
|
6
8
|
};
|
|
7
9
|
declare const WorkspaceStrip: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
10
|
type WorkspaceStrip = ReturnType<typeof WorkspaceStrip>;
|
|
@@ -9,83 +9,9 @@ export class CommandStore {
|
|
|
9
9
|
}
|
|
10
10
|
commands = $state([]);
|
|
11
11
|
refreshDefaultCommands() {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
id: "send-toast",
|
|
16
|
-
label: "Send Toast Notification",
|
|
17
|
-
category: "System",
|
|
18
|
-
icon: "đ",
|
|
19
|
-
children: [
|
|
20
|
-
{
|
|
21
|
-
id: "toast-success",
|
|
22
|
-
label: "Success Toast",
|
|
23
|
-
icon: "â¨",
|
|
24
|
-
action: () => toastStore.success("Operation completed successfully!"),
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: "toast-info",
|
|
28
|
-
label: "Info Toast",
|
|
29
|
-
icon: "âšī¸",
|
|
30
|
-
action: () => toastStore.info("Here is some information."),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: "toast-warning",
|
|
34
|
-
label: "Warning Toast",
|
|
35
|
-
icon: "â ī¸",
|
|
36
|
-
action: () => toastStore.warn("Please be careful."),
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
id: "toast-error",
|
|
40
|
-
label: "Error Toast",
|
|
41
|
-
icon: "đĢ",
|
|
42
|
-
action: () => toastStore.error("Something went wrong!"),
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
id: "log-app",
|
|
48
|
-
label: "Log Current App State",
|
|
49
|
-
category: "Debug",
|
|
50
|
-
icon: "đ",
|
|
51
|
-
action: () => {
|
|
52
|
-
console.group("đ Rune Lab â Current State");
|
|
53
|
-
console.table(appStore.info);
|
|
54
|
-
console.log("đ¨ Theme:", themeStore.current, themeStore.getProp("icon"));
|
|
55
|
-
console.log("đ Language:", languageStore.current, languageStore.getProp("flag"));
|
|
56
|
-
console.log("đ° Currency:", currencyStore.current, currencyStore.getProp("symbol"));
|
|
57
|
-
console.log("đ API:", apiStore.connectionState, apiStore.URL);
|
|
58
|
-
console.groupEnd();
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
id: "log-all-stores",
|
|
63
|
-
label: "Log All Stores (Full Dump)",
|
|
64
|
-
category: "Debug",
|
|
65
|
-
icon: "đī¸",
|
|
66
|
-
action: () => {
|
|
67
|
-
console.group("đ Rune Lab â Full Store Dump");
|
|
68
|
-
Object.entries(this.#services).forEach(([name, store]) => {
|
|
69
|
-
console.group(`Store: ${name}`);
|
|
70
|
-
console.log("Reactive Instance:", store);
|
|
71
|
-
if (store && "available" in store) {
|
|
72
|
-
console.log("Available:", store.available);
|
|
73
|
-
}
|
|
74
|
-
if (store && "current" in store) {
|
|
75
|
-
console.log("Current:", store.current);
|
|
76
|
-
}
|
|
77
|
-
if (store && "toasts" in store) {
|
|
78
|
-
console.log("Queue:", store.toasts);
|
|
79
|
-
}
|
|
80
|
-
if (store && "commands" in store) {
|
|
81
|
-
console.log("Registry:", store.commands);
|
|
82
|
-
}
|
|
83
|
-
console.groupEnd();
|
|
84
|
-
});
|
|
85
|
-
console.groupEnd();
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
];
|
|
12
|
+
// Intentionally empty.
|
|
13
|
+
// Consumers (like the demo app) should inject their own default commands via `commandStore.register`.
|
|
14
|
+
this.commands = [];
|
|
89
15
|
}
|
|
90
16
|
/**
|
|
91
17
|
* Register a new command
|