@lapis-notes/workspace 0.2.0 → 0.3.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +108 -9
  2. package/README.md +4 -0
  3. package/dist/WorkspaceShell.svelte +19 -2
  4. package/dist/WorkspaceShell.svelte.d.ts +3 -1
  5. package/dist/desktop/DesktopRecoveryBanner.css +46 -0
  6. package/dist/desktop/DesktopRecoveryBanner.svelte +70 -0
  7. package/dist/desktop/DesktopRecoveryBanner.svelte.d.ts +15 -0
  8. package/dist/desktop/DesktopVaultLauncher.svelte +278 -0
  9. package/dist/desktop/DesktopVaultLauncher.svelte.d.ts +17 -0
  10. package/dist/desktop/DesktopVaultLauncherDialogs.svelte +136 -0
  11. package/dist/desktop/DesktopVaultLauncherDialogs.svelte.d.ts +9 -0
  12. package/dist/desktop/DesktopWorkspaceStartup.css +8 -0
  13. package/dist/desktop/DesktopWorkspaceStartup.svelte +34 -0
  14. package/dist/desktop/DesktopWorkspaceStartup.svelte.d.ts +9 -0
  15. package/dist/desktop/desktop-vault-launcher-controls.css +142 -0
  16. package/dist/desktop/desktop-vault-launcher.css +181 -0
  17. package/dist/desktop/desktop-vault-launcher.d.ts +30 -0
  18. package/dist/desktop/desktop-vault-launcher.js +26 -0
  19. package/dist/desktop/desktop-vault-launcher.svelte.d.ts +36 -0
  20. package/dist/desktop/desktop-vault-launcher.svelte.js +140 -0
  21. package/dist/desktop/lapis-logo.svg +471 -0
  22. package/dist/file-conflicts/ConflictReview.svelte +145 -0
  23. package/dist/file-conflicts/ConflictReview.svelte.d.ts +8 -0
  24. package/dist/file-conflicts/ConflictReviewControls.svelte +66 -0
  25. package/dist/file-conflicts/ConflictReviewControls.svelte.d.ts +12 -0
  26. package/dist/file-conflicts/conflict-review-state.svelte.d.ts +22 -0
  27. package/dist/file-conflicts/conflict-review-state.svelte.js +59 -0
  28. package/dist/file-conflicts/conflict-review-view.d.ts +19 -0
  29. package/dist/file-conflicts/conflict-review-view.js +50 -0
  30. package/dist/file-conflicts/register-conflict-reviews.d.ts +7 -0
  31. package/dist/file-conflicts/register-conflict-reviews.js +132 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.js +4 -0
  34. package/dist/plugin-management/CorePluginsSettingsPage.svelte +47 -16
  35. package/dist/plugin-management/PluginBrowser.svelte +63 -7
  36. package/dist/plugin-management/PluginRegistryToolbar.svelte.d.ts +1 -1
  37. package/dist/plugin-management/plugin-browser-model.d.ts +1 -0
  38. package/dist/plugin-management/plugin-browser-model.js +11 -2
  39. package/dist/plugin-management/plugin-browser-navigation.d.ts +1 -1
  40. package/dist/plugin-management/plugin-browser-navigation.js +4 -2
  41. package/dist/plugin-management/plugin-registry-controller.svelte.js +5 -1
  42. package/dist/plugin-management/plugins-view.svelte.d.ts +2 -1
  43. package/dist/plugin-management/plugins-view.svelte.js +6 -2
  44. package/dist/plugin-management/register-plugin-management-settings.js +7 -6
  45. package/package.json +5 -4
package/CHANGELOG.md CHANGED
@@ -1,28 +1,127 @@
1
1
  # @lapis-notes/workspace
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`535a6cc`](https://github.com/lapismd/lapis-notes/commit/535a6cc7b39f397cf8761a06db7b831386121928)
8
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Share the existing
9
+ desktop vault chooser and branded startup presentation across native hosts,
10
+ with injected profile storage and native actions.
11
+
12
+ - [`fc3ecdf`](https://github.com/lapismd/lapis-notes/commit/fc3ecdf374005e3c2e960af7d84709b92803537a)
13
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Share the retained
14
+ text-conflict presentation through the App-owned `bindFileConflictReviews`
15
+ binding: one reference-counted lease for WorkspaceShell mounts and early host
16
+ surfaces captured writer conflicts as Problems diagnostics and a three-way
17
+ MergeEditor review leaf whose saves and refreshes run through the writer-owned
18
+ captured actions (LN-WS-102–104).
19
+
20
+ ### Patch Changes
21
+
22
+ - [`4cdbb7b`](https://github.com/lapismd/lapis-notes/commit/4cdbb7b09f2875d413bf0e3fa054ed16345866a8)
23
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Expose the existing
24
+ desktop Safe Mode recovery presentation through shared, host-controlled
25
+ callbacks for Tauri.
26
+
27
+ - [`d8946b9`](https://github.com/lapismd/lapis-notes/commit/d8946b941569484b813eb0c1e8b1c5729fffc1a7)
28
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Add owned
29
+ diagnostic-resource navigation and themed recovery-banner actions. Accept
30
+ Design Core 0.5.1 alongside the existing 0.4 range.
31
+
32
+ - Require released Design Core 0.6 for the close-guard, plugin-flush and
33
+ centered panel contracts used by current source. Retain portable registry
34
+ dependency ranges for the shared application runtime.
35
+ - Updated dependencies
36
+ [[`bdd4923`](https://github.com/lapismd/lapis-notes/commit/bdd492357135de2cc0175fa42413709b2a38cd63),
37
+ [`86e85f6`](https://github.com/lapismd/lapis-notes/commit/86e85f6e814430c1755147c913eb91090a439bf4),
38
+ [`631aeec`](https://github.com/lapismd/lapis-notes/commit/631aeec539e2fb061dc80c5c3a30a248ee15c529),
39
+ [`fdee7e4`](https://github.com/lapismd/lapis-notes/commit/fdee7e4defde17a1009e16138d78c93c3ead8e2f),
40
+ [`a91d5e2`](https://github.com/lapismd/lapis-notes/commit/a91d5e2eb5ec037029f6a7a2df316a9bd9440af7),
41
+ [`fbba8d4`](https://github.com/lapismd/lapis-notes/commit/fbba8d48498c34939fec475e6bed67a682045820),
42
+ [`d8946b9`](https://github.com/lapismd/lapis-notes/commit/d8946b941569484b813eb0c1e8b1c5729fffc1a7),
43
+ [`ebf033a`](https://github.com/lapismd/lapis-notes/commit/ebf033af0fc0752be5ce5ffa7d55784163d291b4),
44
+ [`023259d`](https://github.com/lapismd/lapis-notes/commit/023259d96d4ff0414fec6427924ed917f89405a3),
45
+ [`63a5e7f`](https://github.com/lapismd/lapis-notes/commit/63a5e7fe40eea45762b5842b588c742076c7dd1d),
46
+ [`2bffe48`](https://github.com/lapismd/lapis-notes/commit/2bffe484b38761af197afe7ab9b0d7b3affb50d7),
47
+ [`86f6316`](https://github.com/lapismd/lapis-notes/commit/86f63166c3d5be648c595ef4e230ea6c4081bc48),
48
+ [`edf1d76`](https://github.com/lapismd/lapis-notes/commit/edf1d76f4f3aa0442c1d9153d812f29b7d14e5c2),
49
+ [`b3be91d`](https://github.com/lapismd/lapis-notes/commit/b3be91daecbd43187bacf8cfdc39f8311d700720),
50
+ [`5ef0a63`](https://github.com/lapismd/lapis-notes/commit/5ef0a6331c1b846b70357f990d919977a561112c),
51
+ [`bac3e5e`](https://github.com/lapismd/lapis-notes/commit/bac3e5eda6f467dbc86ec88c9f27a275f2b27953),
52
+ [`978b987`](https://github.com/lapismd/lapis-notes/commit/978b987ba4c95408a63e49fad11bc44bab020758),
53
+ [`ad4490c`](https://github.com/lapismd/lapis-notes/commit/ad4490ca1cd808002765582a17a5c81fa5335e0d),
54
+ [`74f544e`](https://github.com/lapismd/lapis-notes/commit/74f544e2aa8a967524c0c218f3db7e1a495a1a17),
55
+ [`55be8dc`](https://github.com/lapismd/lapis-notes/commit/55be8dce6f7b19b0e8036be86a3cf5305db25317),
56
+ [`501c50e`](https://github.com/lapismd/lapis-notes/commit/501c50e049264560e13e29c75a74c089922c7297),
57
+ [`98b333f`](https://github.com/lapismd/lapis-notes/commit/98b333f73ff5ba8d5f28c52e4e3a2ceaa5ade0fb),
58
+ [`535a6cc`](https://github.com/lapismd/lapis-notes/commit/535a6cc7b39f397cf8761a06db7b831386121928),
59
+ [`8a27bc3`](https://github.com/lapismd/lapis-notes/commit/8a27bc36a7a7fa81594e517908a6f5ed8ca77313),
60
+ [`109e208`](https://github.com/lapismd/lapis-notes/commit/109e208a63253dcc1a7abad3fd5a9b13c39d3def),
61
+ [`1e64589`](https://github.com/lapismd/lapis-notes/commit/1e6458922b486c07f36a5bc7d85d020b5805cd49),
62
+ [`f828eae`](https://github.com/lapismd/lapis-notes/commit/f828eaeba8411aa342d7ab4954393e84612af2f3)]:
63
+ - @lapis-notes/api@0.2.0
64
+ - @lapis-notes/ui@0.1.4
65
+
66
+ ## 0.2.2
67
+
68
+ ### Patch Changes
69
+
70
+ - [#36](https://github.com/lapismd/lapis-notes/pull/36)
71
+ [`17060a1`](https://github.com/lapismd/lapis-notes/commit/17060a1df152c9fc2302c93767cd7172868ff030)
72
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Consume published Design
73
+ Core 0.5.0 with prepared browser CSS and bundled font assets through normal
74
+ npm ranges.
75
+ - Updated dependencies
76
+ [[`17060a1`](https://github.com/lapismd/lapis-notes/commit/17060a1df152c9fc2302c93767cd7172868ff030)]:
77
+ - @lapis-notes/api@0.1.5
78
+
79
+ ## 0.2.1
80
+
81
+ ### Patch Changes
82
+
83
+ - [#15](https://github.com/lapismd/lapis-notes/pull/15)
84
+ [`57996c6`](https://github.com/lapismd/lapis-notes/commit/57996c6d26c1e7ed723df1291b415ee94d3a58e5)
85
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Retain selected plugin
86
+ releases in navigation and persisted Plugins views so application links can
87
+ open a specific version. Fail closed for unavailable versions and keep
88
+ historical installation bound to the selected release.
89
+
3
90
  ## 0.2.0
4
91
 
5
92
  ### Minor Changes
6
93
 
7
- - [#4](https://github.com/lapismd/lapis-notes/pull/4) [`08a9e4b`](https://github.com/lapismd/lapis-notes/commit/08a9e4bc671c87484e1fbb7c3271b2aad3f176e5) Thanks [@stevejuma](https://github.com/stevejuma)! - Add the Plugins workspace browser with a category homepage, update listings,
8
- responsive filters, and verified install/update actions shared with Settings.
9
- Settings Browse and Updates now open shared plugin details directly. Details
10
- include a Back action, fixed navigation, themed media, image loading and failure
11
- states, release notes, download counts, and linked authors.
94
+ - [#4](https://github.com/lapismd/lapis-notes/pull/4)
95
+ [`08a9e4b`](https://github.com/lapismd/lapis-notes/commit/08a9e4bc671c87484e1fbb7c3271b2aad3f176e5)
96
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Add the Plugins workspace
97
+ browser with a category homepage, update listings, responsive filters, and
98
+ verified install/update actions shared with Settings. Settings Browse and
99
+ Updates now open shared plugin details directly. Details include a Back
100
+ action, fixed navigation, themed media, image loading and failure states,
101
+ release notes, download counts, and linked authors.
12
102
 
13
103
  ### Patch Changes
14
104
 
15
- - [#4](https://github.com/lapismd/lapis-notes/pull/4) [`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2) Thanks [@stevejuma](https://github.com/stevejuma)! - Require published Design Core 0.3.2 for native window-controls spacing, bold settings headings, selected-tab indicators and mobile shell teardown. Consume the npm release through portable semver ranges and lockfile integrity.
105
+ - [#4](https://github.com/lapismd/lapis-notes/pull/4)
106
+ [`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2)
107
+ Thanks [@stevejuma](https://github.com/stevejuma)! - Require published Design
108
+ Core 0.3.2 for native window-controls spacing, bold settings headings,
109
+ selected-tab indicators and mobile shell teardown. Consume the npm release
110
+ through portable semver ranges and lockfile integrity.
16
111
 
17
- - Updated dependencies [[`39f886d`](https://github.com/lapismd/lapis-notes/commit/39f886d017a28d916b3f6cac7d08b7723a04f45b), [`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2), [`1e949fc`](https://github.com/lapismd/lapis-notes/commit/1e949fc9d51b795a50760e1146a9599602746c6b)]:
112
+ - Updated dependencies
113
+ [[`39f886d`](https://github.com/lapismd/lapis-notes/commit/39f886d017a28d916b3f6cac7d08b7723a04f45b),
114
+ [`c1ad3c4`](https://github.com/lapismd/lapis-notes/commit/c1ad3c4acee2eac1ae87c22f66e946d2e0db3bf2),
115
+ [`1e949fc`](https://github.com/lapismd/lapis-notes/commit/1e949fc9d51b795a50760e1146a9599602746c6b)]:
18
116
  - @lapis-notes/api@0.1.2
19
117
 
20
118
  ## 0.1.1
21
119
 
22
120
  ### Patch Changes
23
121
 
24
- - Support verified static V2 catalogs alongside Nostr sources, retain installation
25
- source provenance, and render themed registry images and structured release notes.
122
+ - Support verified static V2 catalogs alongside Nostr sources, retain
123
+ installation source provenance, and render themed registry images and
124
+ structured release notes.
26
125
  - Updated dependencies []:
27
126
  - @lapis-notes/api@0.1.1
28
127
 
package/README.md CHANGED
@@ -11,6 +11,10 @@ backend setup.
11
11
 
12
12
  - `WorkspaceShell`, the Svelte component used by hosts and demos.
13
13
  - `WorkspaceShellProps`, the public prop contract for typed Svelte consumers.
14
+ - `DesktopVaultLauncher` and `DesktopVaultLauncherHost`, the existing Deno
15
+ vault chooser with injected profile storage, appearance and native actions.
16
+ - `DesktopWorkspaceStartup`, the shared Lapis-branded Design Core startup
17
+ presentation. Both native hosts pass actual phase progress and recovery actions.
14
18
 
15
19
  ## Usage
16
20
 
@@ -1,10 +1,15 @@
1
1
  <script lang="ts" module>
2
2
  import type { App } from "@lapis-notes/api";
3
3
  import type { WorkspaceNavigation } from "@lapismd/design-core/workspace/app-shell";
4
- import type { WorkspaceRequestedDisplayMode } from "@lapismd/design-core/workspace/core";
4
+ import type {
5
+ WorkspaceRequestedDisplayMode,
6
+ WorkspacePopoutHost,
7
+ } from "@lapismd/design-core/workspace/core";
5
8
 
6
9
  export interface WorkspaceShellProps {
7
10
  app: App;
11
+ /** Optional host-owned popup creation policy. null disables detached windows. */
12
+ popoutHost?: WorkspacePopoutHost | null;
8
13
  displayMode?: WorkspaceRequestedDisplayMode;
9
14
  workspaceLabel?: string;
10
15
  workspaceNavigation?: WorkspaceNavigation;
@@ -14,14 +19,19 @@
14
19
 
15
20
  <script lang="ts">
16
21
  import { onMount, untrack } from "svelte";
17
- import { provideApplicationState } from "@lapis-notes/api";
22
+ import {
23
+ bindNativeEditingProblems,
24
+ provideApplicationState,
25
+ } from "@lapis-notes/api";
18
26
  import { getWorkspaceHostBinding } from "@lapis-notes/api/workspace-host";
19
27
  import { AppShell } from "@lapismd/design-core/workspace/app-shell";
28
+ import { bindFileConflictReviews } from "./file-conflicts/register-conflict-reviews";
20
29
  import "./workspace-shell.css";
21
30
 
22
31
  let {
23
32
  app,
24
33
  displayMode,
34
+ popoutHost,
25
35
  workspaceLabel = "Lapis Notes",
26
36
  workspaceNavigation,
27
37
  class: className = "",
@@ -39,6 +49,12 @@
39
49
  // AppShell.Root does not own startup. By the time this shell is mounted,
40
50
  // the consumer has loaded the vault and restored the Lapis workspace.
41
51
  void controller.start();
52
+ const stopNative = bindNativeEditingProblems(app);
53
+ const stopConflicts = bindFileConflictReviews(app);
54
+ return () => {
55
+ stopConflicts();
56
+ stopNative();
57
+ };
42
58
  });
43
59
  </script>
44
60
 
@@ -49,6 +65,7 @@
49
65
  >
50
66
  <AppShell.Root
51
67
  {controller}
68
+ {popoutHost}
52
69
  autoStart={false}
53
70
  {activateNewTabs}
54
71
  disposeOnDestroy={false}
@@ -1,8 +1,10 @@
1
1
  import type { App } from "@lapis-notes/api";
2
2
  import type { WorkspaceNavigation } from "@lapismd/design-core/workspace/app-shell";
3
- import type { WorkspaceRequestedDisplayMode } from "@lapismd/design-core/workspace/core";
3
+ import type { WorkspaceRequestedDisplayMode, WorkspacePopoutHost } from "@lapismd/design-core/workspace/core";
4
4
  export interface WorkspaceShellProps {
5
5
  app: App;
6
+ /** Optional host-owned popup creation policy. null disables detached windows. */
7
+ popoutHost?: WorkspacePopoutHost | null;
6
8
  displayMode?: WorkspaceRequestedDisplayMode;
7
9
  workspaceLabel?: string;
8
10
  workspaceNavigation?: WorkspaceNavigation;
@@ -0,0 +1,46 @@
1
+ .desktop-recovery-banner {
2
+ position: absolute;
3
+ z-index: 80;
4
+ inset-block-end: 0.75rem;
5
+ inset-inline-start: 50%;
6
+ display: flex;
7
+ width: min(56rem, calc(100% - 2rem));
8
+ box-sizing: border-box;
9
+ align-items: center;
10
+ gap: 1rem;
11
+ padding: 0.75rem 1rem;
12
+ border: 1px solid var(--ui-workspace-border);
13
+ border-radius: var(--ui-radius, 0.625rem);
14
+ color: var(--ui-workspace-foreground);
15
+ background: var(--ui-workspace-background, var(--background));
16
+ box-shadow: var(--ui-workspace-shadow-lg, 0 12px 30px rgb(0 0 0 / 16%));
17
+ translate: -50% 0;
18
+ }
19
+ .desktop-recovery-banner__message {
20
+ display: flex;
21
+ min-width: 0;
22
+ flex: 1;
23
+ flex-direction: column;
24
+ gap: 0.125rem;
25
+ font-size: 0.75rem;
26
+ color: var(--ui-workspace-muted-foreground);
27
+ }
28
+ .desktop-recovery-banner__message strong {
29
+ font-size: 0.875rem;
30
+ color: var(--ui-workspace-foreground);
31
+ }
32
+ .desktop-recovery-banner__actions {
33
+ display: flex;
34
+ flex-wrap: wrap;
35
+ justify-content: flex-end;
36
+ gap: 0.5rem;
37
+ }
38
+ @media (width < 48rem) {
39
+ .desktop-recovery-banner {
40
+ align-items: stretch;
41
+ flex-direction: column;
42
+ }
43
+ .desktop-recovery-banner__actions {
44
+ justify-content: flex-start;
45
+ }
46
+ }
@@ -0,0 +1,70 @@
1
+ <script lang="ts">
2
+ import DatabaseIcon from "@lucide/svelte/icons/database";
3
+ import SearchIcon from "@lucide/svelte/icons/search";
4
+ import RotateCcwIcon from "@lucide/svelte/icons/rotate-ccw";
5
+ import * as Button from "@lapismd/design-core/shadcn/button";
6
+ import "./DesktopRecoveryBanner.css";
7
+
8
+ let {
9
+ reasons = [],
10
+ lastFailure = "",
11
+ message = "",
12
+ metadataBusy = false,
13
+ searchBusy = false,
14
+ disabled = false,
15
+ onRebuildMetadata,
16
+ onRebuildSearch,
17
+ onRestart,
18
+ }: {
19
+ reasons?: readonly string[];
20
+ lastFailure?: string;
21
+ message?: string;
22
+ metadataBusy?: boolean;
23
+ searchBusy?: boolean;
24
+ disabled?: boolean;
25
+ onRebuildMetadata(): Promise<void>;
26
+ onRebuildSearch(): Promise<void>;
27
+ onRestart(): Promise<void>;
28
+ } = $props();
29
+ </script>
30
+
31
+ <div
32
+ class="desktop-recovery-banner"
33
+ role="status"
34
+ aria-label="Safe Mode"
35
+ data-ui-component="desktop-recovery-banner"
36
+ data-ui-part="root"
37
+ data-desktop-drag-region="false"
38
+ >
39
+ <div class="desktop-recovery-banner__message">
40
+ <strong>Safe Mode is active</strong>
41
+ <span
42
+ >{reasons.length
43
+ ? reasons.join(". ")
44
+ : "Startup recovery restrictions are active for this session."}</span
45
+ >
46
+ {#if lastFailure}<span>Last failure: {lastFailure}</span>{/if}
47
+ {#if message}<span>{message}</span>{/if}
48
+ </div>
49
+ <div class="desktop-recovery-banner__actions">
50
+ <Button.Root
51
+ variant="outline"
52
+ disabled={disabled || metadataBusy}
53
+ onclick={onRebuildMetadata}
54
+ >
55
+ <DatabaseIcon aria-hidden="true" />
56
+ {metadataBusy ? "Rebuilding metadata…" : "Rebuild metadata"}
57
+ </Button.Root>
58
+ <Button.Root
59
+ variant="outline"
60
+ disabled={disabled || searchBusy}
61
+ onclick={onRebuildSearch}
62
+ >
63
+ <SearchIcon aria-hidden="true" />
64
+ {searchBusy ? "Rebuilding Search…" : "Rebuild Search"}
65
+ </Button.Root>
66
+ <Button.Root {disabled} onclick={onRestart}>
67
+ <RotateCcwIcon aria-hidden="true" />Restart normally
68
+ </Button.Root>
69
+ </div>
70
+ </div>
@@ -0,0 +1,15 @@
1
+ import "./DesktopRecoveryBanner.css";
2
+ type $$ComponentProps = {
3
+ reasons?: readonly string[];
4
+ lastFailure?: string;
5
+ message?: string;
6
+ metadataBusy?: boolean;
7
+ searchBusy?: boolean;
8
+ disabled?: boolean;
9
+ onRebuildMetadata(): Promise<void>;
10
+ onRebuildSearch(): Promise<void>;
11
+ onRestart(): Promise<void>;
12
+ };
13
+ declare const DesktopRecoveryBanner: import("svelte").Component<$$ComponentProps, {}, "">;
14
+ type DesktopRecoveryBanner = ReturnType<typeof DesktopRecoveryBanner>;
15
+ export default DesktopRecoveryBanner;
@@ -0,0 +1,278 @@
1
+ <script lang="ts">
2
+ import Archive from "@lucide/svelte/icons/archive";
3
+ import Copy from "@lucide/svelte/icons/copy";
4
+ import Ellipsis from "@lucide/svelte/icons/ellipsis";
5
+ import FolderInput from "@lucide/svelte/icons/folder-input";
6
+ import FolderOpen from "@lucide/svelte/icons/folder-open";
7
+ import FolderPlus from "@lucide/svelte/icons/folder-plus";
8
+ import PenLine from "@lucide/svelte/icons/pen-line";
9
+ import Settings from "@lucide/svelte/icons/settings";
10
+ import X from "@lucide/svelte/icons/x";
11
+ import type { VaultProfile } from "@lapis-notes/api";
12
+ import * as Button from "@lapismd/design-core/shadcn/button";
13
+ import * as Card from "@lapismd/design-core/shadcn/card";
14
+ import * as DropdownMenu from "@lapismd/design-core/shadcn/dropdown-menu";
15
+ import { onMount, untrack } from "svelte";
16
+ import { DesktopVaultLauncherState } from "./desktop-vault-launcher.svelte";
17
+ import {
18
+ getVaultProfileLocation,
19
+ getVaultProfileRootPath,
20
+ type LauncherStatus,
21
+ type DesktopVaultLauncherHost,
22
+ } from "./desktop-vault-launcher";
23
+ import DesktopVaultLauncherDialogs from "./DesktopVaultLauncherDialogs.svelte";
24
+ import lapisLogo from "./lapis-logo.svg";
25
+ import "./desktop-vault-launcher.css";
26
+ import "./desktop-vault-launcher-controls.css";
27
+ let {
28
+ status,
29
+ errorMessage = "",
30
+ canReturn = false,
31
+ host,
32
+ onCreate,
33
+ onOpen,
34
+ onOpenRecent,
35
+ onClose,
36
+ }: {
37
+ status: LauncherStatus;
38
+ errorMessage?: string;
39
+ canReturn?: boolean;
40
+ host: DesktopVaultLauncherHost;
41
+ onCreate(): Promise<void>;
42
+ onOpen(): Promise<void>;
43
+ onOpenRecent(profile: VaultProfile): Promise<void>;
44
+ onClose?(): void;
45
+ } = $props();
46
+ const model = new DesktopVaultLauncherState(untrack(() => host));
47
+ const busy = $derived(status === "loading" || status === "opening");
48
+ const visibleError = $derived(errorMessage || model.actionError);
49
+ const actionClass = "desktop-launcher__action";
50
+ const outlineActionClass = `${actionClass} desktop-launcher__outline`;
51
+ onMount(() => {
52
+ void model.refresh();
53
+ });
54
+ </script>
55
+
56
+ <section
57
+ class="desktop-launcher"
58
+ data-ui-component="desktop-vault-launcher"
59
+ data-desktop-vault-launcher
60
+ data-desktop-drag-region
61
+ aria-label="Vaults"
62
+ aria-busy={busy}
63
+ >
64
+ <div
65
+ class="desktop-launcher__content"
66
+ data-desktop-vault-launcher-content
67
+ data-desktop-drag-region="false"
68
+ >
69
+ <div class="desktop-launcher__header">
70
+ <div class="desktop-launcher__brand">
71
+ <img src={lapisLogo} alt="" class="desktop-launcher__logo" />
72
+ <div class="desktop-launcher__copy">
73
+ <div class="desktop-launcher__eyebrow">Lapis Notes</div>
74
+ <h1 class="desktop-launcher__heading">
75
+ {model.recentVaults.length === 0
76
+ ? "Create a vault"
77
+ : "Open a vault"}
78
+ </h1>
79
+ <p class="desktop-launcher__description">
80
+ {#if model.recentVaults.length === 0}
81
+ Start a new vault on this Mac, or open an existing folder if you
82
+ already have one.
83
+ {:else}
84
+ Jump back into a recent vault or create a new one on this Mac.
85
+ {/if}
86
+ </p>
87
+ </div>
88
+ </div>
89
+ <div class="desktop-launcher__header-actions">
90
+ <Button.Root
91
+ variant="link"
92
+ size="sm"
93
+ class="desktop-launcher__settings-trigger"
94
+ onclick={() => (model.settingsOpen = true)}
95
+ >
96
+ <Settings class="desktop-launcher__icon" />
97
+ Settings
98
+ </Button.Root>
99
+ {#if canReturn}
100
+ <Button.Root
101
+ type="button"
102
+ variant="secondary"
103
+ size="icon-lg"
104
+ class="desktop-launcher__close"
105
+ aria-label="Return to previous vault"
106
+ onclick={() => onClose?.()}
107
+ >
108
+ <X class="desktop-launcher__large-icon" />
109
+ </Button.Root>
110
+ {/if}
111
+ </div>
112
+ </div>
113
+
114
+ {#if visibleError}
115
+ <div class="desktop-launcher__error" role="alert">
116
+ {visibleError}
117
+ </div>
118
+ {:else if model.actionMessage}
119
+ <div class="desktop-launcher__message" role="status">
120
+ {model.actionMessage}
121
+ </div>
122
+ {/if}
123
+
124
+ <div class="desktop-launcher__columns">
125
+ <Card.Root class="desktop-launcher__actions desktop-launcher__card">
126
+ <Card.Header class="desktop-launcher__card-header">
127
+ <Card.Title class="desktop-launcher__card-title">
128
+ {model.recentVaults.length === 0
129
+ ? "New Desktop Vault"
130
+ : "Desktop Vaults"}
131
+ </Card.Title>
132
+ <Card.Description>
133
+ {model.recentVaults.length === 0
134
+ ? "Create a new vault directory first, or open an existing folder."
135
+ : "Choose an existing folder or create a new vault directory."}
136
+ </Card.Description>
137
+ </Card.Header>
138
+ <Card.Content class="desktop-launcher__action-list">
139
+ <Button.Root
140
+ variant={model.recentVaults.length === 0 ? "default" : "outline"}
141
+ class={model.recentVaults.length === 0
142
+ ? actionClass
143
+ : outlineActionClass}
144
+ disabled={busy}
145
+ onclick={() => void model.invoke(onCreate)}
146
+ >
147
+ <FolderPlus class="desktop-launcher__large-icon" />
148
+ <span class="desktop-launcher__action-copy">
149
+ <span class="desktop-launcher__label">Create New Vault</span>
150
+ <span class="desktop-launcher__hint">
151
+ Pick a new folder location and open it immediately.
152
+ </span>
153
+ </span>
154
+ </Button.Root>
155
+ <Button.Root
156
+ variant={model.recentVaults.length === 0 ? "outline" : "default"}
157
+ class={model.recentVaults.length === 0
158
+ ? outlineActionClass
159
+ : actionClass}
160
+ disabled={busy}
161
+ onclick={() => void model.invoke(onOpen)}
162
+ >
163
+ <FolderOpen class="desktop-launcher__large-icon" />
164
+ <span class="desktop-launcher__action-copy">
165
+ <span class="desktop-launcher__label">Open Vault</span>
166
+ <span class="desktop-launcher__hint">
167
+ Choose a folder you have already been using as a vault.
168
+ </span>
169
+ </span>
170
+ </Button.Root>
171
+ </Card.Content>
172
+ </Card.Root>
173
+
174
+ <Card.Root class="desktop-launcher__recents desktop-launcher__card">
175
+ <Card.Header class="desktop-launcher__card-header">
176
+ <div class="desktop-launcher__recents-header">
177
+ <div>
178
+ <Card.Title class="desktop-launcher__card-title"
179
+ >Recent Projects</Card.Title
180
+ >
181
+ <Card.Description
182
+ >Recently opened vaults stored on this device.</Card.Description
183
+ >
184
+ </div>
185
+ {#if model.recentVaults.length > 0}
186
+ <Button.Root
187
+ variant="link"
188
+ size="sm"
189
+ class="desktop-launcher__link"
190
+ onclick={() => (model.recentVaultDialogOpen = true)}
191
+ >
192
+ View all
193
+ </Button.Root>
194
+ {/if}
195
+ </div>
196
+ </Card.Header>
197
+ <Card.Content class="desktop-launcher__recent-list">
198
+ {#if model.inlineRecentVaults.length > 0}
199
+ {#each model.inlineRecentVaults as profile (profile.id)}
200
+ <div class="desktop-launcher__recent">
201
+ <button
202
+ class="desktop-launcher__recent-open"
203
+ data-ui-part="recent-open"
204
+ type="button"
205
+ disabled={busy || model.activeActionId === profile.id}
206
+ onclick={() => void model.invoke(() => onOpenRecent(profile))}
207
+ >
208
+ <FolderOpen class="desktop-launcher__recent-icon" />
209
+ <span class="desktop-launcher__recent-copy">
210
+ <span class="desktop-launcher__recent-name"
211
+ >{profile.name}</span
212
+ >
213
+ <span class="desktop-launcher__location"
214
+ >{getVaultProfileLocation(profile)}</span
215
+ >
216
+ </span>
217
+ </button>
218
+ {#if host.copyId || host.rename || host.move || host.reveal || host.remove}
219
+ <DropdownMenu.Root>
220
+ <DropdownMenu.Trigger
221
+ class="desktop-launcher__recent-menu"
222
+ aria-label={`Open actions for ${profile.name}`}
223
+ title={`Open actions for ${profile.name}`}
224
+ disabled={busy || model.activeActionId === profile.id}
225
+ >
226
+ <Ellipsis class="desktop-launcher__icon" />
227
+ </DropdownMenu.Trigger>
228
+ <DropdownMenu.Content align="end" side="bottom">
229
+ {#if host.copyId}<DropdownMenu.Item
230
+ onclick={() => void model.copyVaultId(profile)}
231
+ >
232
+ <Copy class="desktop-launcher__icon" />
233
+ Copy vault ID
234
+ </DropdownMenu.Item>{/if}
235
+ {#if host.rename}<DropdownMenu.Item
236
+ onclick={() => model.openRenameDialog(profile)}
237
+ >
238
+ <PenLine class="desktop-launcher__icon" />
239
+ Rename vault...
240
+ </DropdownMenu.Item>{/if}
241
+ {#if host.move}<DropdownMenu.Item
242
+ disabled={!getVaultProfileRootPath(profile)}
243
+ onclick={() => void model.moveRecentVault(profile)}
244
+ >
245
+ <FolderInput class="desktop-launcher__icon" />
246
+ Move vault...
247
+ </DropdownMenu.Item>{/if}
248
+ {#if host.reveal}<DropdownMenu.Item
249
+ disabled={!getVaultProfileRootPath(profile)}
250
+ onclick={() => void model.revealRecentVault(profile)}
251
+ >
252
+ <FolderOpen class="desktop-launcher__icon" />
253
+ {host.revealLabel ?? "Reveal vault in file manager"}
254
+ </DropdownMenu.Item>{/if}
255
+ {#if host.remove}<DropdownMenu.Separator />{/if}
256
+ {#if host.remove}<DropdownMenu.Item
257
+ onclick={() => void model.removeRecentVault(profile)}
258
+ >
259
+ <Archive class="desktop-launcher__icon" />
260
+ Remove from list
261
+ </DropdownMenu.Item>{/if}
262
+ </DropdownMenu.Content>
263
+ </DropdownMenu.Root>
264
+ {/if}
265
+ </div>
266
+ {/each}
267
+ {:else}
268
+ <div class="desktop-launcher__empty">
269
+ Your recent vaults will appear here after you create or open one.
270
+ </div>
271
+ {/if}
272
+ </Card.Content>
273
+ </Card.Root>
274
+ </div>
275
+ </div>
276
+ </section>
277
+
278
+ <DesktopVaultLauncherDialogs {model} {onOpenRecent} />
@@ -0,0 +1,17 @@
1
+ import type { VaultProfile } from "@lapis-notes/api";
2
+ import { type LauncherStatus, type DesktopVaultLauncherHost } from "./desktop-vault-launcher";
3
+ import "./desktop-vault-launcher.css";
4
+ import "./desktop-vault-launcher-controls.css";
5
+ type $$ComponentProps = {
6
+ status: LauncherStatus;
7
+ errorMessage?: string;
8
+ canReturn?: boolean;
9
+ host: DesktopVaultLauncherHost;
10
+ onCreate(): Promise<void>;
11
+ onOpen(): Promise<void>;
12
+ onOpenRecent(profile: VaultProfile): Promise<void>;
13
+ onClose?(): void;
14
+ };
15
+ declare const DesktopVaultLauncher: import("svelte").Component<$$ComponentProps, {}, "">;
16
+ type DesktopVaultLauncher = ReturnType<typeof DesktopVaultLauncher>;
17
+ export default DesktopVaultLauncher;