@dxos/plugin-native 0.8.4-main.c85a9c8dae → 0.8.4-main.d9fc60f731
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/LICENSE +102 -5
- package/PLUGIN.mdl +284 -0
- package/README.md +1 -1
- package/dist/lib/neutral/NativePlugin.mjs +46 -0
- package/dist/lib/neutral/NativePlugin.mjs.map +7 -0
- package/dist/lib/neutral/NativeSettings-CLYG5TJH.mjs +113 -0
- package/dist/lib/neutral/NativeSettings-CLYG5TJH.mjs.map +7 -0
- package/dist/lib/neutral/capabilities/index.mjs +17 -0
- package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
- package/dist/lib/neutral/chunk-75E24KAJ.mjs +34 -0
- package/dist/lib/neutral/chunk-75E24KAJ.mjs.map +7 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
- package/dist/lib/neutral/chunk-J5LGTIGS.mjs.map +7 -0
- package/dist/lib/neutral/components/index.mjs +9 -0
- package/dist/lib/neutral/components/index.mjs.map +7 -0
- package/dist/lib/neutral/index.mjs +8 -0
- package/dist/lib/neutral/index.mjs.map +7 -0
- package/dist/lib/neutral/meta.json +1 -0
- package/dist/lib/neutral/meta.mjs +8 -0
- package/dist/lib/neutral/meta.mjs.map +7 -0
- package/dist/lib/{browser/ollama-QOB3EVAJ.mjs → neutral/ollama-BHWFTROM.mjs} +8 -21
- package/dist/lib/{browser/ollama-QOB3EVAJ.mjs.map → neutral/ollama-BHWFTROM.mjs.map} +3 -3
- package/dist/lib/neutral/plugin.mjs +12 -0
- package/dist/lib/neutral/plugin.mjs.map +7 -0
- package/dist/lib/neutral/react-surface-ETNGHN4X.mjs +27 -0
- package/dist/lib/neutral/react-surface-ETNGHN4X.mjs.map +7 -0
- package/dist/lib/neutral/settings-HF6TQNU4.mjs +28 -0
- package/dist/lib/neutral/settings-HF6TQNU4.mjs.map +7 -0
- package/dist/lib/neutral/spotlight-listener-K7S6LGL3.mjs +48 -0
- package/dist/lib/neutral/spotlight-listener-K7S6LGL3.mjs.map +7 -0
- package/dist/lib/neutral/translations.mjs +35 -0
- package/dist/lib/neutral/translations.mjs.map +7 -0
- package/dist/lib/neutral/types/index.mjs +31 -0
- package/dist/lib/neutral/types/index.mjs.map +7 -0
- package/dist/lib/neutral/updater-JEBXWSSK.mjs +244 -0
- package/dist/lib/neutral/updater-JEBXWSSK.mjs.map +7 -0
- package/dist/types/src/NativePlugin.d.ts +1 -0
- package/dist/types/src/NativePlugin.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +6 -3
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/ollama.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts +5 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
- package/dist/types/src/capabilities/settings.d.ts +7 -0
- package/dist/types/src/capabilities/settings.d.ts.map +1 -0
- package/dist/types/src/capabilities/spotlight-listener.d.ts +8 -0
- package/dist/types/src/capabilities/spotlight-listener.d.ts.map +1 -0
- package/dist/types/src/capabilities/updater.d.ts +6 -0
- package/dist/types/src/capabilities/updater.d.ts.map +1 -0
- package/dist/types/src/components/NativeSettings/NativeSettings.d.ts +6 -0
- package/dist/types/src/components/NativeSettings/NativeSettings.d.ts.map +1 -0
- package/dist/types/src/components/NativeSettings/index.d.ts +2 -0
- package/dist/types/src/components/NativeSettings/index.d.ts.map +1 -0
- package/dist/types/src/components/index.d.ts +3 -0
- package/dist/types/src/components/index.d.ts.map +1 -0
- package/dist/types/src/constants.d.ts +2 -0
- package/dist/types/src/constants.d.ts.map +1 -0
- package/dist/types/src/index.d.ts +0 -2
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/plugin.d.ts +3 -0
- package/dist/types/src/plugin.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +20 -6
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/NativeCapabilities.d.ts +5 -0
- package/dist/types/src/types/NativeCapabilities.d.ts.map +1 -0
- package/dist/types/src/types/Settings.d.ts +5 -0
- package/dist/types/src/types/Settings.d.ts.map +1 -0
- package/dist/types/src/types/Update.d.ts +30 -0
- package/dist/types/src/types/Update.d.ts.map +1 -0
- package/dist/types/src/types/index.d.ts +4 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +75 -27
- package/src/NativePlugin.tsx +19 -9
- package/src/capabilities/index.ts +7 -3
- package/src/capabilities/react-surface.tsx +29 -0
- package/src/capabilities/settings.ts +31 -0
- package/src/capabilities/spotlight-listener.ts +58 -0
- package/src/capabilities/updater.ts +194 -0
- package/src/components/NativeSettings/NativeSettings.tsx +132 -0
- package/src/components/NativeSettings/index.ts +5 -0
- package/src/components/index.ts +7 -0
- package/src/constants.ts +7 -0
- package/src/index.ts +0 -2
- package/src/meta.ts +24 -7
- package/src/plugin.ts +9 -0
- package/src/translations.ts +20 -5
- package/src/types/NativeCapabilities.ts +16 -0
- package/src/types/Settings.ts +11 -0
- package/src/types/Update.ts +24 -0
- package/src/types/index.ts +7 -0
- package/src/vite-env.d.ts +10 -0
- package/dist/lib/browser/chunk-6374DBU4.mjs +0 -15
- package/dist/lib/browser/chunk-6374DBU4.mjs.map +0 -7
- package/dist/lib/browser/index.mjs +0 -59
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/browser/updater-AR27PYM4.mjs +0 -190
- package/dist/lib/browser/updater-AR27PYM4.mjs.map +0 -7
- package/dist/lib/browser/window-GKYYTCEI.mjs +0 -38
- package/dist/lib/browser/window-GKYYTCEI.mjs.map +0 -7
- package/dist/types/src/capabilities/ollama/index.d.ts +0 -3
- package/dist/types/src/capabilities/ollama/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/ollama/ollama.d.ts.map +0 -1
- package/dist/types/src/capabilities/updater/index.d.ts +0 -3
- package/dist/types/src/capabilities/updater/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/updater/updater.d.ts +0 -5
- package/dist/types/src/capabilities/updater/updater.d.ts.map +0 -1
- package/dist/types/src/capabilities/window/index.d.ts +0 -3
- package/dist/types/src/capabilities/window/index.d.ts.map +0 -1
- package/dist/types/src/capabilities/window/window.d.ts +0 -11
- package/dist/types/src/capabilities/window/window.d.ts.map +0 -1
- package/src/capabilities/ollama/index.ts +0 -7
- package/src/capabilities/updater/index.ts +0 -7
- package/src/capabilities/updater/updater.ts +0 -120
- package/src/capabilities/window/index.ts +0 -7
- package/src/capabilities/window/window.ts +0 -72
- /package/dist/types/src/capabilities/{ollama/ollama.d.ts → ollama.d.ts} +0 -0
- /package/src/capabilities/{ollama/ollama.ts → ollama.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/plugin-native",
|
|
3
|
-
"version": "0.8.4-main.
|
|
3
|
+
"version": "0.8.4-main.d9fc60f731",
|
|
4
4
|
"description": "Composer plugin that runs when on a native device.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -8,50 +8,98 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/dxos/dxos"
|
|
10
10
|
},
|
|
11
|
-
"license": "
|
|
11
|
+
"license": "FSL-1.1-Apache-2.0",
|
|
12
12
|
"author": "DXOS.org",
|
|
13
13
|
"sideEffects": true,
|
|
14
14
|
"type": "module",
|
|
15
|
+
"imports": {
|
|
16
|
+
"#capabilities": {
|
|
17
|
+
"source": "./src/capabilities/index.ts",
|
|
18
|
+
"types": "./dist/types/src/capabilities/index.d.ts",
|
|
19
|
+
"default": "./dist/lib/neutral/capabilities/index.mjs"
|
|
20
|
+
},
|
|
21
|
+
"#components": {
|
|
22
|
+
"source": "./src/components/index.ts",
|
|
23
|
+
"types": "./dist/types/src/components/index.d.ts",
|
|
24
|
+
"default": "./dist/lib/neutral/components/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"#meta": {
|
|
27
|
+
"source": "./src/meta.ts",
|
|
28
|
+
"types": "./dist/types/src/meta.d.ts",
|
|
29
|
+
"default": "./dist/lib/neutral/meta.mjs"
|
|
30
|
+
},
|
|
31
|
+
"#plugin": {
|
|
32
|
+
"source": "./src/NativePlugin.tsx",
|
|
33
|
+
"types": "./dist/types/src/NativePlugin.d.ts",
|
|
34
|
+
"default": "./dist/lib/neutral/NativePlugin.mjs"
|
|
35
|
+
},
|
|
36
|
+
"#translations": {
|
|
37
|
+
"source": "./src/translations.ts",
|
|
38
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
39
|
+
"default": "./dist/lib/neutral/translations.mjs"
|
|
40
|
+
},
|
|
41
|
+
"#types": {
|
|
42
|
+
"source": "./src/types/index.ts",
|
|
43
|
+
"types": "./dist/types/src/types/index.d.ts",
|
|
44
|
+
"default": "./dist/lib/neutral/types/index.mjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
15
47
|
"exports": {
|
|
16
48
|
".": {
|
|
17
49
|
"source": "./src/index.ts",
|
|
18
50
|
"types": "./dist/types/src/index.d.ts",
|
|
19
|
-
"
|
|
51
|
+
"default": "./dist/lib/neutral/index.mjs"
|
|
52
|
+
},
|
|
53
|
+
"./assets/PLUGIN.mdl": "./PLUGIN.mdl",
|
|
54
|
+
"./plugin": {
|
|
55
|
+
"source": "./src/plugin.ts",
|
|
56
|
+
"types": "./dist/types/src/plugin.d.ts",
|
|
57
|
+
"default": "./dist/lib/neutral/plugin.mjs"
|
|
58
|
+
},
|
|
59
|
+
"./translations": {
|
|
60
|
+
"source": "./src/translations.ts",
|
|
61
|
+
"types": "./dist/types/src/translations.d.ts",
|
|
62
|
+
"default": "./dist/lib/neutral/translations.mjs"
|
|
20
63
|
}
|
|
21
64
|
},
|
|
22
65
|
"types": "dist/types/src/index.d.ts",
|
|
23
|
-
"typesVersions": {
|
|
24
|
-
"*": {}
|
|
25
|
-
},
|
|
26
66
|
"files": [
|
|
27
67
|
"dist",
|
|
28
|
-
"src"
|
|
68
|
+
"src",
|
|
69
|
+
"PLUGIN.mdl"
|
|
29
70
|
],
|
|
30
71
|
"dependencies": {
|
|
31
|
-
"@effect/
|
|
32
|
-
"@
|
|
33
|
-
"@tauri-apps/
|
|
34
|
-
"@tauri-apps/plugin-
|
|
35
|
-
"@tauri-apps/plugin-
|
|
36
|
-
"@tauri-apps/plugin-
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"@dxos/
|
|
40
|
-
"@dxos/app-
|
|
41
|
-
"@dxos/
|
|
42
|
-
"@dxos/
|
|
43
|
-
"@dxos/
|
|
44
|
-
"@dxos/
|
|
45
|
-
"@dxos/
|
|
46
|
-
"@dxos/
|
|
72
|
+
"@effect-atom/atom-react": "^0.5.0",
|
|
73
|
+
"@effect/platform": "0.96.1",
|
|
74
|
+
"@tauri-apps/api": "^2.11.0",
|
|
75
|
+
"@tauri-apps/plugin-os": "^2.3.2",
|
|
76
|
+
"@tauri-apps/plugin-process": "^2.3.1",
|
|
77
|
+
"@tauri-apps/plugin-shell": "^2.3.5",
|
|
78
|
+
"@tauri-apps/plugin-updater": "^2.10.1",
|
|
79
|
+
"date-fns": "^3.6.0",
|
|
80
|
+
"@dxos/ai": "0.8.4-main.d9fc60f731",
|
|
81
|
+
"@dxos/app-toolkit": "0.8.4-main.d9fc60f731",
|
|
82
|
+
"@dxos/app-framework": "0.8.4-main.d9fc60f731",
|
|
83
|
+
"@dxos/effect": "0.8.4-main.d9fc60f731",
|
|
84
|
+
"@dxos/log": "0.8.4-main.d9fc60f731",
|
|
85
|
+
"@dxos/keys": "0.8.4-main.d9fc60f731",
|
|
86
|
+
"@dxos/plugin-assistant": "0.8.4-main.d9fc60f731",
|
|
87
|
+
"@dxos/util": "0.8.4-main.d9fc60f731"
|
|
47
88
|
},
|
|
48
89
|
"devDependencies": {
|
|
49
|
-
"
|
|
50
|
-
"
|
|
90
|
+
"@types/react": "~19.2.7",
|
|
91
|
+
"effect": "3.21.2",
|
|
92
|
+
"react": "~19.2.3",
|
|
93
|
+
"vite": "^8.0.14",
|
|
94
|
+
"@dxos/plugin-testing": "0.8.4-main.d9fc60f731",
|
|
95
|
+
"@dxos/react-ui-form": "0.8.4-main.d9fc60f731",
|
|
96
|
+
"@dxos/react-ui": "0.8.4-main.d9fc60f731"
|
|
51
97
|
},
|
|
52
98
|
"peerDependencies": {
|
|
53
|
-
"effect": "3.
|
|
54
|
-
"
|
|
99
|
+
"effect": "3.21.2",
|
|
100
|
+
"react": "~19.2.3",
|
|
101
|
+
"@dxos/react-ui": "0.8.4-main.d9fc60f731",
|
|
102
|
+
"@dxos/react-ui-form": "0.8.4-main.d9fc60f731"
|
|
55
103
|
},
|
|
56
104
|
"publishConfig": {
|
|
57
105
|
"access": "public"
|
package/src/NativePlugin.tsx
CHANGED
|
@@ -6,26 +6,36 @@ import { ActivationEvents, Plugin } from '@dxos/app-framework';
|
|
|
6
6
|
import { AppPlugin } from '@dxos/app-toolkit';
|
|
7
7
|
import { AssistantEvents } from '@dxos/plugin-assistant';
|
|
8
8
|
|
|
9
|
-
import { Ollama,
|
|
10
|
-
import { meta } from '
|
|
11
|
-
import { translations } from '
|
|
9
|
+
import { NativeSettings, Ollama, ReactSurface, SpotlightListener, Updater } from '#capabilities';
|
|
10
|
+
import { meta } from '#meta';
|
|
11
|
+
import { translations } from '#translations';
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line import/no-relative-packages
|
|
14
|
+
import pluginSpec from '../PLUGIN.mdl?raw';
|
|
12
15
|
|
|
13
16
|
export const NativePlugin = Plugin.define(meta).pipe(
|
|
17
|
+
AppPlugin.addSettingsModule({ activate: NativeSettings }),
|
|
18
|
+
AppPlugin.addSurfaceModule({ activate: ReactSurface }),
|
|
14
19
|
AppPlugin.addTranslationsModule({ translations }),
|
|
15
20
|
Plugin.addModule({
|
|
16
|
-
id: '
|
|
17
|
-
activatesOn: ActivationEvents.
|
|
18
|
-
activate:
|
|
21
|
+
id: 'spotlight-listener',
|
|
22
|
+
activatesOn: ActivationEvents.ProcessManagerReady,
|
|
23
|
+
activate: SpotlightListener,
|
|
19
24
|
}),
|
|
20
25
|
Plugin.addModule({
|
|
21
|
-
id: '
|
|
22
|
-
activatesOn: ActivationEvents.
|
|
23
|
-
activate:
|
|
26
|
+
id: 'updater',
|
|
27
|
+
activatesOn: ActivationEvents.ProcessManagerReady,
|
|
28
|
+
activate: Updater,
|
|
24
29
|
}),
|
|
25
30
|
Plugin.addModule({
|
|
26
31
|
id: 'ollama',
|
|
27
32
|
activatesOn: AssistantEvents.SetupAiServiceProviders,
|
|
28
33
|
activate: Ollama,
|
|
29
34
|
}),
|
|
35
|
+
AppPlugin.addPluginAssetModule({
|
|
36
|
+
asset: { pluginId: meta.id, path: 'PLUGIN.mdl', content: pluginSpec, mimeType: 'application/x-mdl' },
|
|
37
|
+
}),
|
|
30
38
|
Plugin.make,
|
|
31
39
|
);
|
|
40
|
+
|
|
41
|
+
export default NativePlugin;
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
// Copyright 2025 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export
|
|
5
|
+
import { Capability } from '@dxos/app-framework';
|
|
6
|
+
|
|
7
|
+
export const NativeSettings = Capability.lazy('NativeSettings', () => import('./settings'));
|
|
8
|
+
export const Ollama = Capability.lazy('Ollama', () => import('./ollama'));
|
|
9
|
+
export const ReactSurface = Capability.lazy('ReactSurface', () => import('./react-surface'));
|
|
10
|
+
export const SpotlightListener = Capability.lazy('SpotlightListener', () => import('./spotlight-listener'));
|
|
11
|
+
export const Updater = Capability.lazy('Updater', () => import('./updater'));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
import React from 'react';
|
|
7
|
+
|
|
8
|
+
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
9
|
+
import { Surface, useSettingsState } from '@dxos/app-framework/ui';
|
|
10
|
+
import { AppSurface } from '@dxos/app-toolkit/ui';
|
|
11
|
+
|
|
12
|
+
import { NativeSettings } from '#components';
|
|
13
|
+
import { meta } from '#meta';
|
|
14
|
+
import { type Settings } from '#types';
|
|
15
|
+
|
|
16
|
+
export default Capability.makeModule(() =>
|
|
17
|
+
Effect.succeed(
|
|
18
|
+
Capability.contributes(Capabilities.ReactSurface, [
|
|
19
|
+
Surface.create({
|
|
20
|
+
id: 'pluginSettings',
|
|
21
|
+
filter: AppSurface.settings(AppSurface.Article, meta.id),
|
|
22
|
+
component: ({ data: { subject } }) => {
|
|
23
|
+
const { settings, updateSettings } = useSettingsState<Settings.Settings>(subject.atom);
|
|
24
|
+
return <NativeSettings settings={settings} onSettingsChange={updateSettings} />;
|
|
25
|
+
},
|
|
26
|
+
}),
|
|
27
|
+
]),
|
|
28
|
+
),
|
|
29
|
+
);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Capability } from '@dxos/app-framework';
|
|
8
|
+
import { AppCapabilities } from '@dxos/app-toolkit';
|
|
9
|
+
import { createKvsStore } from '@dxos/effect';
|
|
10
|
+
|
|
11
|
+
import { meta } from '#meta';
|
|
12
|
+
import { NativeCapabilities, Settings } from '#types';
|
|
13
|
+
|
|
14
|
+
export default Capability.makeModule(() =>
|
|
15
|
+
Effect.sync(() => {
|
|
16
|
+
const settingsAtom = createKvsStore({
|
|
17
|
+
key: meta.id,
|
|
18
|
+
schema: Settings.Settings,
|
|
19
|
+
defaultValue: () => ({}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return [
|
|
23
|
+
Capability.contributes(NativeCapabilities.Settings, settingsAtom),
|
|
24
|
+
Capability.contributes(AppCapabilities.Settings, {
|
|
25
|
+
prefix: meta.id,
|
|
26
|
+
schema: Settings.Settings,
|
|
27
|
+
atom: settingsAtom,
|
|
28
|
+
}),
|
|
29
|
+
];
|
|
30
|
+
}),
|
|
31
|
+
);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import * as Effect from 'effect/Effect';
|
|
6
|
+
|
|
7
|
+
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
8
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
9
|
+
import { log } from '@dxos/log';
|
|
10
|
+
|
|
11
|
+
// TODO(wittjosiah): Formalize with a stricter schema if we evolve this protocol.
|
|
12
|
+
type SpotlightInvokePayload = {
|
|
13
|
+
operation: string;
|
|
14
|
+
payload?: Record<string, any>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Listens for spotlight:invoke events from the popover window and dispatches the corresponding operation.
|
|
19
|
+
*/
|
|
20
|
+
export default Capability.makeModule(
|
|
21
|
+
Effect.fnUntraced(function* () {
|
|
22
|
+
const { invokePromise } = yield* Capability.get(Capabilities.OperationInvoker);
|
|
23
|
+
|
|
24
|
+
const unlisten = yield* Effect.promise(async () => {
|
|
25
|
+
const { listen } = await import('@tauri-apps/api/event');
|
|
26
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window');
|
|
27
|
+
|
|
28
|
+
return listen<SpotlightInvokePayload>('spotlight:invoke', async (event) => {
|
|
29
|
+
const { operation, payload } = event.payload;
|
|
30
|
+
log.info('Received spotlight invoke event', { operation, payload });
|
|
31
|
+
try {
|
|
32
|
+
switch (operation) {
|
|
33
|
+
case 'open':
|
|
34
|
+
await invokePromise(LayoutOperation.Open, payload as any);
|
|
35
|
+
break;
|
|
36
|
+
case 'switch-workspace':
|
|
37
|
+
await invokePromise(LayoutOperation.SwitchWorkspace, payload as any);
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
log.warn('Unknown spotlight operation', { operation });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const mainWindow = getCurrentWindow();
|
|
44
|
+
await mainWindow.show();
|
|
45
|
+
await mainWindow.setFocus();
|
|
46
|
+
} catch (err) {
|
|
47
|
+
log.catch(err);
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return Capability.contributes(Capabilities.Null, null, () =>
|
|
53
|
+
Effect.sync(() => {
|
|
54
|
+
unlisten();
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Atom } from '@effect-atom/atom-react';
|
|
6
|
+
import { type } from '@tauri-apps/plugin-os';
|
|
7
|
+
import { relaunch } from '@tauri-apps/plugin-process';
|
|
8
|
+
import * as Updater from '@tauri-apps/plugin-updater';
|
|
9
|
+
import * as Duration from 'effect/Duration';
|
|
10
|
+
import * as Effect from 'effect/Effect';
|
|
11
|
+
import * as Fiber from 'effect/Fiber';
|
|
12
|
+
import * as Match from 'effect/Match';
|
|
13
|
+
import * as Schedule from 'effect/Schedule';
|
|
14
|
+
|
|
15
|
+
import { Capabilities, Capability } from '@dxos/app-framework';
|
|
16
|
+
import { LayoutOperation } from '@dxos/app-toolkit';
|
|
17
|
+
import { log } from '@dxos/log';
|
|
18
|
+
|
|
19
|
+
import { meta } from '#meta';
|
|
20
|
+
import { NativeCapabilities, type Update } from '#types';
|
|
21
|
+
|
|
22
|
+
import { TAURI_LOCALHOST_PORT } from '../constants';
|
|
23
|
+
|
|
24
|
+
const SUPPORTS_OTA = ['linux', 'macos', 'windows'];
|
|
25
|
+
|
|
26
|
+
/** Safe wrapper around Updater.check(). Distinguishes "no update" (ok with null update) from a thrown error. */
|
|
27
|
+
const safeCheck = async (): Promise<{ ok: true; update: Updater.Update | null } | { ok: false; error: string }> => {
|
|
28
|
+
try {
|
|
29
|
+
return { ok: true, update: await Updater.check() };
|
|
30
|
+
} catch (error) {
|
|
31
|
+
log.error('failed to check for updates', { error });
|
|
32
|
+
return { ok: false, error: formatError(error) };
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Safe wrapper around update.downloadAndInstall(). */
|
|
37
|
+
const safeDownloadAndInstall = async (
|
|
38
|
+
update: Updater.Update,
|
|
39
|
+
onEvent: (event: Updater.DownloadEvent) => void,
|
|
40
|
+
): Promise<{ ok: true } | { ok: false; error: string }> => {
|
|
41
|
+
try {
|
|
42
|
+
await update.downloadAndInstall(onEvent);
|
|
43
|
+
return { ok: true };
|
|
44
|
+
} catch (error) {
|
|
45
|
+
log.error('failed to download and install update', { error });
|
|
46
|
+
return { ok: false, error: formatError(error) };
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/** Extract a user-readable error string from whatever the Tauri updater threw. */
|
|
51
|
+
const formatError = (error: unknown): string => {
|
|
52
|
+
if (typeof error === 'string') {
|
|
53
|
+
return error;
|
|
54
|
+
}
|
|
55
|
+
if (error instanceof Error) {
|
|
56
|
+
return error.message;
|
|
57
|
+
}
|
|
58
|
+
return String(error);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default Capability.makeModule(
|
|
62
|
+
Effect.fnUntraced(function* () {
|
|
63
|
+
const platform = type();
|
|
64
|
+
const isDevServer = window.location.port !== TAURI_LOCALHOST_PORT;
|
|
65
|
+
const enabled = SUPPORTS_OTA.includes(platform) && !isDevServer;
|
|
66
|
+
|
|
67
|
+
const registry = yield* Capability.get(Capabilities.AtomRegistry);
|
|
68
|
+
const { invoke } = yield* Capability.get(Capabilities.OperationInvoker);
|
|
69
|
+
|
|
70
|
+
const statusAtom = Atom.make<Update.Status>(enabled ? { kind: 'idle' } : { kind: 'unsupported' }).pipe(
|
|
71
|
+
Atom.keepAlive,
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Updater.Update is a class with instance methods (downloadAndInstall) and can't live in an
|
|
75
|
+
// atom value; cache it here between check and install.
|
|
76
|
+
let pendingUpdate: Updater.Update | null = null;
|
|
77
|
+
|
|
78
|
+
// Core: check for an update and update the status atom. Returns the update if available.
|
|
79
|
+
const doCheck = async (): Promise<Updater.Update | null> => {
|
|
80
|
+
registry.set(statusAtom, { kind: 'checking' });
|
|
81
|
+
log.info('checking for updates');
|
|
82
|
+
const result = await safeCheck();
|
|
83
|
+
if (!result.ok) {
|
|
84
|
+
pendingUpdate = null;
|
|
85
|
+
registry.set(statusAtom, { kind: 'failed', error: result.error });
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const update = result.update;
|
|
89
|
+
if (!update?.available) {
|
|
90
|
+
pendingUpdate = null;
|
|
91
|
+
registry.set(statusAtom, { kind: 'up-to-date', checkedAt: Date.now() });
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
log.info('update available', { version: update.version });
|
|
95
|
+
pendingUpdate = update;
|
|
96
|
+
registry.set(statusAtom, { kind: 'available', version: update.version });
|
|
97
|
+
return update;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Core: download + install the cached pending update, streaming progress to the status atom.
|
|
101
|
+
const doInstall = async (): Promise<boolean> => {
|
|
102
|
+
const update = pendingUpdate;
|
|
103
|
+
if (!update) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
let downloaded = 0;
|
|
107
|
+
let contentLength = 0;
|
|
108
|
+
registry.set(statusAtom, { kind: 'downloading', downloaded: 0, contentLength: 0 });
|
|
109
|
+
const onEvent = Match.type<Updater.DownloadEvent>().pipe(
|
|
110
|
+
Match.when({ event: 'Started' }, (event) => {
|
|
111
|
+
contentLength = event.data.contentLength ?? 0;
|
|
112
|
+
registry.set(statusAtom, { kind: 'downloading', downloaded, contentLength });
|
|
113
|
+
}),
|
|
114
|
+
Match.when({ event: 'Progress' }, (event) => {
|
|
115
|
+
downloaded += event.data.chunkLength;
|
|
116
|
+
registry.set(statusAtom, { kind: 'downloading', downloaded, contentLength });
|
|
117
|
+
}),
|
|
118
|
+
Match.when({ event: 'Finished' }, () => {
|
|
119
|
+
log.info('download completed');
|
|
120
|
+
}),
|
|
121
|
+
Match.exhaustive,
|
|
122
|
+
);
|
|
123
|
+
const result = await safeDownloadAndInstall(update, onEvent);
|
|
124
|
+
if (result.ok) {
|
|
125
|
+
registry.set(statusAtom, { kind: 'ready' });
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
registry.set(statusAtom, { kind: 'failed', error: result.error });
|
|
129
|
+
return false;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const manager: Update.Manager = {
|
|
133
|
+
status: statusAtom,
|
|
134
|
+
check: async () => {
|
|
135
|
+
if (!enabled) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
await doCheck();
|
|
139
|
+
},
|
|
140
|
+
install: async () => {
|
|
141
|
+
if (!enabled) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
await doInstall();
|
|
145
|
+
},
|
|
146
|
+
relaunch: async () => {
|
|
147
|
+
await relaunch();
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const managerContribution = Capability.contributes(NativeCapabilities.UpdateManager, manager);
|
|
152
|
+
|
|
153
|
+
if (!enabled) {
|
|
154
|
+
log.info('updater disabled', { platform, port: window.location.port });
|
|
155
|
+
return [managerContribution];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Background flow: periodic check + auto-install + toast when ready.
|
|
159
|
+
// The toast is the entry point for users who weren't watching the settings panel.
|
|
160
|
+
const backgroundAction = Effect.gen(function* () {
|
|
161
|
+
const update = yield* Effect.promise(() => doCheck());
|
|
162
|
+
if (!update) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
const ok = yield* Effect.promise(() => doInstall());
|
|
166
|
+
if (!ok) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
yield* invoke(LayoutOperation.AddToast, {
|
|
170
|
+
id: `${meta.id}.update-ready`,
|
|
171
|
+
title: ['update-ready.label', { ns: meta.id }],
|
|
172
|
+
description: ['update-ready.description', { ns: meta.id }],
|
|
173
|
+
duration: Infinity,
|
|
174
|
+
actionLabel: ['update.label', { ns: meta.id }],
|
|
175
|
+
actionAlt: ['update.alt', { ns: meta.id }],
|
|
176
|
+
onAction: () => relaunch(),
|
|
177
|
+
});
|
|
178
|
+
return false;
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const schedule = Schedule.fixed(Duration.hours(1)).pipe(
|
|
182
|
+
Schedule.whileInput((keepChecking: boolean) => keepChecking),
|
|
183
|
+
);
|
|
184
|
+
const fiber = yield* backgroundAction.pipe(Effect.repeat(schedule), Effect.forkDaemon);
|
|
185
|
+
log.info('updater module initialized, update check scheduled');
|
|
186
|
+
|
|
187
|
+
return [
|
|
188
|
+
managerContribution,
|
|
189
|
+
// Return the interruption effect directly; Fiber.interrupt is async and would throw
|
|
190
|
+
// AsyncFiberException if wrapped in Effect.runSync.
|
|
191
|
+
Capability.contributes(Capabilities.Null, null, () => Fiber.interrupt(fiber)),
|
|
192
|
+
];
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { useAtomValue } from '@effect-atom/atom-react';
|
|
6
|
+
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
|
|
7
|
+
import * as Match from 'effect/Match';
|
|
8
|
+
import React, { type ReactNode, useState } from 'react';
|
|
9
|
+
|
|
10
|
+
import { useCapability } from '@dxos/app-framework/ui';
|
|
11
|
+
import { type AppSurface } from '@dxos/app-toolkit/ui';
|
|
12
|
+
import { Button, useTranslation } from '@dxos/react-ui';
|
|
13
|
+
import { Settings as SettingsForm } from '@dxos/react-ui-form';
|
|
14
|
+
|
|
15
|
+
import { meta } from '#meta';
|
|
16
|
+
import { NativeCapabilities, type Settings, type Update } from '#types';
|
|
17
|
+
|
|
18
|
+
export type NativeSettingsProps = AppSurface.SettingsArticleProps<Settings.Settings>;
|
|
19
|
+
|
|
20
|
+
type Pending = null | 'check' | 'install' | 'relaunch';
|
|
21
|
+
|
|
22
|
+
type UpdateActions = {
|
|
23
|
+
onCheck: () => Promise<void>;
|
|
24
|
+
onInstall: () => Promise<void>;
|
|
25
|
+
onRelaunch: () => Promise<void>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type UpdateRow = { description: string; button: ReactNode };
|
|
29
|
+
|
|
30
|
+
export const NativeSettings = (_props: NativeSettingsProps) => {
|
|
31
|
+
const { t } = useTranslation(meta.id);
|
|
32
|
+
const manager = useCapability(NativeCapabilities.UpdateManager);
|
|
33
|
+
const status = useAtomValue(manager.status);
|
|
34
|
+
|
|
35
|
+
// UI-level pending flag. The status atom can flip between `checking` and `up-to-date` faster
|
|
36
|
+
// than the user can perceive, so we also gate the button on the click handler's lifetime to
|
|
37
|
+
// guarantee no duplicate triggers and a visible busy state.
|
|
38
|
+
const [pending, setPending] = useState<Pending>(null);
|
|
39
|
+
|
|
40
|
+
const runAction = (kind: Exclude<Pending, null>, action: () => Promise<void>) => async () => {
|
|
41
|
+
setPending(kind);
|
|
42
|
+
try {
|
|
43
|
+
await action();
|
|
44
|
+
} finally {
|
|
45
|
+
setPending(null);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const { description, button } = renderUpdateRow(status, pending, t, {
|
|
50
|
+
onCheck: runAction('check', manager.check),
|
|
51
|
+
onInstall: runAction('install', manager.install),
|
|
52
|
+
onRelaunch: runAction('relaunch', manager.relaunch),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<SettingsForm.Viewport>
|
|
57
|
+
<SettingsForm.Section title={t('settings.title')}>
|
|
58
|
+
<SettingsForm.Item title={t('settings.updates.label')} description={description}>
|
|
59
|
+
{button}
|
|
60
|
+
</SettingsForm.Item>
|
|
61
|
+
</SettingsForm.Section>
|
|
62
|
+
</SettingsForm.Viewport>
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const renderUpdateRow = (
|
|
67
|
+
status: Update.Status,
|
|
68
|
+
pending: Pending,
|
|
69
|
+
t: (key: string, options?: Record<string, unknown>) => string,
|
|
70
|
+
{ onCheck, onInstall, onRelaunch }: UpdateActions,
|
|
71
|
+
): UpdateRow => {
|
|
72
|
+
const isChecking = pending === 'check' || status.kind === 'checking';
|
|
73
|
+
const isInstalling = pending === 'install' || status.kind === 'downloading';
|
|
74
|
+
|
|
75
|
+
const checkButton = (disabled = false) => (
|
|
76
|
+
<Button disabled={disabled || isChecking || isInstalling || pending === 'relaunch'} onClick={() => void onCheck()}>
|
|
77
|
+
{isChecking ? t('settings.updates.checking.label') : t('settings.updates.check.label')}
|
|
78
|
+
</Button>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return Match.value(status).pipe(
|
|
82
|
+
Match.withReturnType<UpdateRow>(),
|
|
83
|
+
Match.when({ kind: 'unsupported' }, () => ({
|
|
84
|
+
description: t('settings.updates.unsupported.message'),
|
|
85
|
+
button: checkButton(true),
|
|
86
|
+
})),
|
|
87
|
+
Match.when({ kind: 'idle' }, () => ({
|
|
88
|
+
description: isChecking ? t('settings.updates.checking.message') : t('settings.updates.idle.message'),
|
|
89
|
+
button: checkButton(),
|
|
90
|
+
})),
|
|
91
|
+
Match.when({ kind: 'checking' }, () => ({
|
|
92
|
+
description: t('settings.updates.checking.message'),
|
|
93
|
+
button: checkButton(),
|
|
94
|
+
})),
|
|
95
|
+
Match.when({ kind: 'up-to-date' }, (s) => ({
|
|
96
|
+
description: isChecking
|
|
97
|
+
? t('settings.updates.checking.message')
|
|
98
|
+
: t('settings.updates.up-to-date.message', {
|
|
99
|
+
checkedAt: formatDistanceToNow(new Date(s.checkedAt), { addSuffix: true }),
|
|
100
|
+
}),
|
|
101
|
+
button: checkButton(),
|
|
102
|
+
})),
|
|
103
|
+
Match.when({ kind: 'available' }, (s) => ({
|
|
104
|
+
description: t('settings.updates.available.message', { version: s.version }),
|
|
105
|
+
button: (
|
|
106
|
+
<Button variant='primary' disabled={isInstalling} onClick={() => void onInstall()}>
|
|
107
|
+
{isInstalling ? t('settings.updates.downloading.label') : t('settings.updates.update-now.label')}
|
|
108
|
+
</Button>
|
|
109
|
+
),
|
|
110
|
+
})),
|
|
111
|
+
Match.when({ kind: 'downloading' }, (s) => {
|
|
112
|
+
const percent = s.contentLength > 0 ? Math.round((s.downloaded / s.contentLength) * 100) : 0;
|
|
113
|
+
return {
|
|
114
|
+
description: t('settings.updates.downloading.message', { percent }),
|
|
115
|
+
button: <Button disabled>{t('settings.updates.downloading.label')}</Button>,
|
|
116
|
+
};
|
|
117
|
+
}),
|
|
118
|
+
Match.when({ kind: 'ready' }, () => ({
|
|
119
|
+
description: t('settings.updates.ready.message'),
|
|
120
|
+
button: (
|
|
121
|
+
<Button variant='primary' disabled={pending === 'relaunch'} onClick={() => void onRelaunch()}>
|
|
122
|
+
{t('settings.updates.relaunch.label')}
|
|
123
|
+
</Button>
|
|
124
|
+
),
|
|
125
|
+
})),
|
|
126
|
+
Match.when({ kind: 'failed' }, (s) => ({
|
|
127
|
+
description: t('settings.updates.failed.message', { error: s.error }),
|
|
128
|
+
button: checkButton(),
|
|
129
|
+
})),
|
|
130
|
+
Match.exhaustive,
|
|
131
|
+
);
|
|
132
|
+
};
|
package/src/constants.ts
ADDED