@lowdefy/shell 0.0.0-experimental-20261019052246
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 +201 -0
- package/README.md +308 -0
- package/app/(t0,t1,t2,t3,t4)/[...path].js +38 -0
- package/app/(t0,t1,t2,t3,t4)/_layout.js +48 -0
- package/app/(t0,t1,t2,t3,t4)/index.js +38 -0
- package/app/+native-intent.js +30 -0
- package/app/_layout.js +33 -0
- package/app.config.ts +168 -0
- package/applyShellPlugins.js +43 -0
- package/authCallbackIntentFilters.js +46 -0
- package/babel.config.js +25 -0
- package/checkShellConfig.js +145 -0
- package/checkShellPlugins.js +74 -0
- package/getPrivacyManifest.js +39 -0
- package/metro.config.js +73 -0
- package/native-fingerprint.json +12 -0
- package/package.json +130 -0
- package/profiles.js +274 -0
- package/readShellConfig.js +36 -0
- package/resolveShellPlugins.js +109 -0
- package/runtimeVersion.js +105 -0
- package/scripts/checkReleaseBundle.mjs +358 -0
- package/scripts/createDayjsLocales.mjs +67 -0
- package/scripts/createShellAssets.mjs +54 -0
- package/scripts/createShellVersion.mjs +83 -0
- package/scripts/createTypeManifest.mjs +205 -0
- package/shell.dev.json +11 -0
- package/shellVariant.js +111 -0
- package/src/ShellContext.js +32 -0
- package/src/actions/CopyToClipboard.js +29 -0
- package/src/actions/CopyToClipboard.test.js +33 -0
- package/src/actions/GeolocationCurrentPosition.js +55 -0
- package/src/actions/GeolocationCurrentPosition.test.js +66 -0
- package/src/actions/GetLocalStorage.js +36 -0
- package/src/actions/RemoveLocalStorage.js +31 -0
- package/src/actions/ScrollTo.js +66 -0
- package/src/actions/ScrollTo.test.js +83 -0
- package/src/actions/SetFocus.js +33 -0
- package/src/actions/SetFocus.test.js +49 -0
- package/src/actions/SetLocalStorage.js +34 -0
- package/src/actions/localStorage.test.js +68 -0
- package/src/actions/measureInScrollView.js +36 -0
- package/src/actions/nativeActions.js +41 -0
- package/src/auth/AuthButton.js +44 -0
- package/src/auth/AuthGate.js +57 -0
- package/src/auth/AuthLink.js +43 -0
- package/src/auth/LockScreen.js +67 -0
- package/src/auth/LockScreen.test.js +37 -0
- package/src/auth/SignInScreen.js +256 -0
- package/src/auth/SignInScreen.test.js +182 -0
- package/src/auth/createAppAuth.js +306 -0
- package/src/auth/createAppAuth.test.js +414 -0
- package/src/auth/createAuthApi.js +99 -0
- package/src/auth/createAuthApi.test.js +135 -0
- package/src/auth/createCoreAuth.js +96 -0
- package/src/auth/createCoreAuth.test.js +188 -0
- package/src/auth/createSessionStores.js +45 -0
- package/src/auth/createTokenStore.js +103 -0
- package/src/auth/createTokenStore.test.js +116 -0
- package/src/auth/getAuthSettings.js +24 -0
- package/src/auth/getAuthView.js +39 -0
- package/src/auth/getAuthView.test.js +52 -0
- package/src/auth/getSheetReturnUrl.js +35 -0
- package/src/auth/getTokenStoreOptions.js +46 -0
- package/src/auth/isBackgroundShell.js +26 -0
- package/src/auth/isSheetReturnLink.js +35 -0
- package/src/auth/parseQuery.js +39 -0
- package/src/auth/purgeAppData.js +45 -0
- package/src/auth/shouldLock.js +26 -0
- package/src/auth/signInWithSheet.js +97 -0
- package/src/auth/signInWithSheet.test.js +205 -0
- package/src/auth/styles.js +78 -0
- package/src/auth/unregisterPushToken.js +21 -0
- package/src/auth/usesBiometricUnlock.js +23 -0
- package/src/auth/watchSession.js +47 -0
- package/src/auth/withoutCookies.js +26 -0
- package/src/cache/createSqliteCacheStore.js +113 -0
- package/src/cache/createSqliteCacheStore.test.js +93 -0
- package/src/cache/getCacheKey.js +25 -0
- package/src/cache/getLastCacheKeyName.js +22 -0
- package/src/components/AppProvider.js +90 -0
- package/src/components/Frame.js +116 -0
- package/src/components/Frame.test.js +101 -0
- package/src/components/LoadingScreen.js +35 -0
- package/src/components/MenuButton.js +108 -0
- package/src/components/MenuButton.test.js +51 -0
- package/src/components/MessageScreen.js +51 -0
- package/src/components/NavigationTheme.js +33 -0
- package/src/components/NoConnectionBanner.js +51 -0
- package/src/components/NoConnectionBanner.test.js +73 -0
- package/src/components/OtherPageScreen.js +56 -0
- package/src/components/PageScreen.js +244 -0
- package/src/components/PageScreen.test.js +492 -0
- package/src/components/ShellRoot.js +34 -0
- package/src/components/ShellRoot.test.js +62 -0
- package/src/components/ShellThemeProvider.js +45 -0
- package/src/components/SwitchToTab.js +36 -0
- package/src/components/SwitchToTab.test.js +63 -0
- package/src/components/TabIconImages.js +72 -0
- package/src/components/UpdateScreen.js +36 -0
- package/src/components/getNavigationTheme.js +44 -0
- package/src/components/getNavigationTheme.test.js +41 -0
- package/src/components/styles.js +50 -0
- package/src/components/useSurfaceTokens.js +47 -0
- package/src/config/getAppApiBase.js +31 -0
- package/src/config/getShellConfig.js +35 -0
- package/src/config/getShellConfig.test.js +80 -0
- package/src/createShellApp.js +105 -0
- package/src/dev/DevShell.js +228 -0
- package/src/dev/DevShell.test.js +233 -0
- package/src/dev/connect/ConfirmSheet.js +71 -0
- package/src/dev/connect/ConnectScreen.js +117 -0
- package/src/dev/connect/QrScanner.js +89 -0
- package/src/dev/connection/createDevShellConfig.js +47 -0
- package/src/dev/connection/createDevTransport.js +51 -0
- package/src/dev/connection/createDevTransport.test.js +87 -0
- package/src/dev/connection/createServerList.js +48 -0
- package/src/dev/connection/isLoopbackHost.js +25 -0
- package/src/dev/connection/pairedShellApp.test.js +104 -0
- package/src/dev/connection/parseDevLink.js +66 -0
- package/src/dev/connection/parseDevLink.test.js +97 -0
- package/src/dev/connection/parseQuery.js +33 -0
- package/src/dev/connection/parseServerInput.js +40 -0
- package/src/dev/connection/parseServerUrl.js +45 -0
- package/src/dev/devStyles.js +97 -0
- package/src/dev/index.js +21 -0
- package/src/dev/inject/createDeviceInjections.js +185 -0
- package/src/dev/inject/createDeviceInjections.test.js +208 -0
- package/src/dev/inject/createOfflineFetch.js +35 -0
- package/src/dev/inject/createOfflineFetch.test.js +68 -0
- package/src/dev/menu/BlockGallery.js +33 -0
- package/src/dev/menu/DevMenu.js +121 -0
- package/src/dev/menu/createLogBuffer.js +83 -0
- package/src/dev/menu/watchShake.js +52 -0
- package/src/dev/menu/watchShake.test.js +61 -0
- package/src/dev/recorder/createNativeRecorder.js +204 -0
- package/src/dev/recorder/createNativeRecorder.test.js +307 -0
- package/src/dev/redirectDevLink.js +30 -0
- package/src/dev/redirectDevLink.test.js +49 -0
- package/src/dev/steps/createStepRunner.js +184 -0
- package/src/dev/steps/createStepRunner.test.js +262 -0
- package/src/dev/steps/findBlock.js +78 -0
- package/src/dev/steps/isContextSettled.js +37 -0
- package/src/dev/steps/waitForSettle.js +39 -0
- package/src/dev/steps/waitUntil.js +40 -0
- package/src/dev/tab/DevTabClient.js +97 -0
- package/src/dev/tab/answerEval.js +39 -0
- package/src/dev/tab/answerInspect.js +45 -0
- package/src/dev/tab/answerLoadState.js +50 -0
- package/src/dev/tab/createDevTabClient.js +277 -0
- package/src/dev/tab/createDevTabClient.test.js +314 -0
- package/src/dev/tab/devClientMarker.js +22 -0
- package/src/dev/tab/devClientMarker.test.js +23 -0
- package/src/dev/tab/findPageContext.js +43 -0
- package/src/dev/tab/getShownPage.js +42 -0
- package/src/dev/tab/readEventStream.js +68 -0
- package/src/dev/tab/reloadApp.js +42 -0
- package/src/dev/tab/reloadApp.test.js +115 -0
- package/src/device/injected.js +60 -0
- package/src/device/injected.test.js +39 -0
- package/src/host/createDeviceStore.js +32 -0
- package/src/host/createMemoryStore.js +32 -0
- package/src/host/createNetworkStatus.js +94 -0
- package/src/host/createNetworkStatus.test.js +94 -0
- package/src/host/createRouterAdapter.js +33 -0
- package/src/host/createShellFetch.js +60 -0
- package/src/host/createShellFetch.test.js +150 -0
- package/src/host/createShellGlobals.js +62 -0
- package/src/host/createShellHost.js +73 -0
- package/src/host/createShellHost.test.js +36 -0
- package/src/host/getOrigin.js +26 -0
- package/src/host/isOnline.js +22 -0
- package/src/host/networkStatus.js +24 -0
- package/src/host/withShellHeader.js +25 -0
- package/src/host/withShellHeader.test.js +28 -0
- package/src/js/evaluateJsEntries.js +43 -0
- package/src/js/evaluateJsEntries.test.js +48 -0
- package/src/locale/createShellLocale.js +112 -0
- package/src/locale/createShellLocale.test.js +74 -0
- package/src/locale/dayjsLocales.js +165 -0
- package/src/locale/getDeviceLocales.js +23 -0
- package/src/locale/loadDayjsLocale.js +32 -0
- package/src/locale/resolveActiveLocale.js +46 -0
- package/src/locale/resolveActiveLocale.test.js +40 -0
- package/src/navigation/SlotContext.js +24 -0
- package/src/navigation/frameSlots.js +27 -0
- package/src/navigation/getFrame.js +30 -0
- package/src/navigation/getInitialScreenOptions.js +63 -0
- package/src/navigation/getInitialScreenOptions.test.js +66 -0
- package/src/navigation/getPresentation.js +28 -0
- package/src/navigation/getSlotRootPath.js +27 -0
- package/src/navigation/getStackOptions.js +185 -0
- package/src/navigation/getTabHref.js +45 -0
- package/src/navigation/getTabIcons.js +43 -0
- package/src/navigation/isPageUnderHeader.js +46 -0
- package/src/navigation/navigation.test.js +342 -0
- package/src/navigation/toRouterHref.js +29 -0
- package/src/plugins/appTypes.js +28 -0
- package/src/plugins/prefixTypes.js +28 -0
- package/src/plugins/shellPlugins.json +14 -0
- package/src/plugins/typeManifest.js +649 -0
- package/src/plugins/types.js +61 -0
- package/src/plugins/types.test.js +173 -0
- package/src/polyfills/installGetRandomValues.js +28 -0
- package/src/session/createAppSession.js +318 -0
- package/src/session/createAppSession.test.js +359 -0
- package/src/session/getPageView.js +59 -0
- package/src/session/getPageView.test.js +137 -0
- package/src/sync/encodePagePath.js +27 -0
- package/src/sync/fetchPage.js +49 -0
- package/src/sync/fetchRoot.js +38 -0
- package/src/sync/fetchUser.js +28 -0
- package/src/sync/runLimited.js +36 -0
- package/src/sync/toRootConfig.js +35 -0
- package/src/test/createFakeNetwork.js +40 -0
- package/src/test/createFakeSecureStore.js +40 -0
- package/src/test/createFakeServer.js +98 -0
- package/src/test/createMemoryCacheStore.js +59 -0
- package/src/test/expoImage.js +38 -0
- package/src/test/expoSpeechRecognition.js +31 -0
- package/src/test/expoUi.js +55 -0
- package/src/test/expoUiBottomSheet.js +64 -0
- package/src/test/expoUiDateTimePicker.js +26 -0
- package/src/test/expoUiMenu.js +46 -0
- package/src/test/expoUiSegmentedControl.js +47 -0
- package/src/test/expoUiSwiftUi.js +62 -0
- package/src/test/expoUiSwiftUiModifiers.js +23 -0
- package/src/test/expoWebBrowser.js +23 -0
- package/src/test/fixtures.js +98 -0
- package/src/test/renderPageScreen.js +109 -0
- package/src/types/checkTypeSet.js +39 -0
- package/src/types/collectPageTypes.js +49 -0
- package/src/types/collectPageTypes.test.js +90 -0
- package/src/types/findMissingPageTypes.js +40 -0
- package/src/version/compareVersions.js +38 -0
- package/src/version/isShellTooOld.js +30 -0
- package/src/version/shellVersion.js +21 -0
- package/src/version/shellVersion.test.js +32 -0
- package/withExcludedNativeModules.js +93 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
The type manifest: every block, action and operator type this shell carries, with the plugin
|
|
19
|
+
package that provides it and that package's major version. A page renders only when the shell
|
|
20
|
+
carries every type its type set names at the same major version (src/types/checkTypeSet.js).
|
|
21
|
+
|
|
22
|
+
Made from src/plugins/shellPlugins.json, the plugin packages the shell registers
|
|
23
|
+
(src/plugins/types.js), and the shell's plugins (shell.json `plugins`, read with
|
|
24
|
+
readShellConfig.js): each one's `./types` `native.blocks` and `native.actions`, named with its
|
|
25
|
+
typePrefix, as src/plugins/appTypes.js registers them. A plugin without a `./types` export adds
|
|
26
|
+
none. Written to src/plugins/typeManifest.js, which the shell's JavaScript
|
|
27
|
+
imports (src/config/getShellConfig.js). The manifest travels in the JavaScript bundle, not the
|
|
28
|
+
Expo config, so it always matches the code that runs: a type added to the plugin maps reaches a
|
|
29
|
+
shell with an over-the-air update or from Metro, without a native build. The file is committed;
|
|
30
|
+
src/plugins/types.test.js fails when it is stale, and `pnpm release:version` rewrites it after
|
|
31
|
+
the version bump. `lowdefy mobile config` rewrites it in a copied shell, with that shell's
|
|
32
|
+
plugins.
|
|
33
|
+
|
|
34
|
+
node scripts/createTypeManifest.mjs prints the manifest as JSON
|
|
35
|
+
node scripts/createTypeManifest.mjs --write writes src/plugins/typeManifest.js
|
|
36
|
+
|
|
37
|
+
Usage: pnpm --filter=@lowdefy/shell type-manifest
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import fs from 'node:fs';
|
|
41
|
+
import { createRequire } from 'node:module';
|
|
42
|
+
import path from 'node:path';
|
|
43
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
44
|
+
|
|
45
|
+
const shellRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
46
|
+
const require = createRequire(import.meta.url);
|
|
47
|
+
const readShellConfig = require('../readShellConfig.js');
|
|
48
|
+
|
|
49
|
+
const NATIVE_CATEGORIES = ['actions', 'blocks'];
|
|
50
|
+
|
|
51
|
+
const CATEGORIES = ['actions', 'blocks', 'operators'];
|
|
52
|
+
|
|
53
|
+
const HEADER = `/*
|
|
54
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
55
|
+
|
|
56
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
57
|
+
you may not use this file except in compliance with the License.
|
|
58
|
+
You may obtain a copy of the License at
|
|
59
|
+
|
|
60
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
61
|
+
|
|
62
|
+
Unless required by applicable law or agreed to in writing, software
|
|
63
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
64
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
65
|
+
See the License for the specific language governing permissions and
|
|
66
|
+
limitations under the License.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
// Generated by scripts/createTypeManifest.mjs from src/plugins/shellPlugins.json and shell.json
|
|
70
|
+
// \`plugins\`. Do not edit.
|
|
71
|
+
`;
|
|
72
|
+
|
|
73
|
+
// The plugin packages export no package.json, so it is found above the resolved types file.
|
|
74
|
+
function readMajorVersion(packageName) {
|
|
75
|
+
let directory = path.dirname(require.resolve(`${packageName}/types`));
|
|
76
|
+
while (!fs.existsSync(path.join(directory, 'package.json'))) {
|
|
77
|
+
directory = path.dirname(directory);
|
|
78
|
+
}
|
|
79
|
+
const { name, version } = JSON.parse(
|
|
80
|
+
fs.readFileSync(path.join(directory, 'package.json'), 'utf8')
|
|
81
|
+
);
|
|
82
|
+
if (name !== packageName) {
|
|
83
|
+
throw new Error(`Could not find the package.json of "${packageName}".`);
|
|
84
|
+
}
|
|
85
|
+
return Number(version.split('.')[0]);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getPackageTypeNames({ category, types }) {
|
|
89
|
+
if (category === 'operators') {
|
|
90
|
+
return types.operators.client;
|
|
91
|
+
}
|
|
92
|
+
return types[category];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Two packages giving one type name would leave one of them unreachable on a page.
|
|
96
|
+
function addType({ category, manifest, name, packageName }) {
|
|
97
|
+
const existing = manifest[category][name];
|
|
98
|
+
if (existing !== undefined && existing.package !== packageName) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Shell plugin "${packageName}" gives the ${category.slice(0, -1)} type "${name}", which "${
|
|
101
|
+
existing.package
|
|
102
|
+
}" already gives. Set a typePrefix on the plugin in mobile.shells[].plugins.`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
manifest[category][name] = { package: packageName, version: readMajorVersion(packageName) };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// A shell plugin need not export "./types": a native-only plugin, such as a wrapped SDK, gives no
|
|
109
|
+
// types. Resolving a missing export throws, so the plugin's own package.json says, found on the
|
|
110
|
+
// lookup paths of `require` (its exports need not list package.json).
|
|
111
|
+
function hasTypesExport(packageName) {
|
|
112
|
+
const packageJsonPath = require.resolve
|
|
113
|
+
.paths(packageName)
|
|
114
|
+
.map((directory) => path.join(directory, packageName, 'package.json'))
|
|
115
|
+
.find((filePath) => fs.existsSync(filePath));
|
|
116
|
+
if (packageJsonPath === undefined) {
|
|
117
|
+
throw new Error(`Shell plugin "${packageName}" is not installed.`);
|
|
118
|
+
}
|
|
119
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
120
|
+
return packageJson.exports?.['./types'] !== undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// A shell plugin's native type names, from its `./types` `native: { blocks, actions }`.
|
|
124
|
+
async function addShellPluginTypes({ manifest, plugin }) {
|
|
125
|
+
const { default: types } = await import(pathToFileURL(require.resolve(`${plugin.name}/types`)));
|
|
126
|
+
NATIVE_CATEGORIES.forEach((category) => {
|
|
127
|
+
const names = types.native?.[category] ?? [];
|
|
128
|
+
if (!Array.isArray(names)) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Shell plugin "${
|
|
131
|
+
plugin.name
|
|
132
|
+
}" "./types" native.${category} should be an array of type names. Received ${JSON.stringify(
|
|
133
|
+
names
|
|
134
|
+
)}.`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
names.forEach((name) => {
|
|
138
|
+
addType({
|
|
139
|
+
category,
|
|
140
|
+
manifest,
|
|
141
|
+
name: `${plugin.typePrefix ?? ''}${name}`,
|
|
142
|
+
packageName: plugin.name,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readShellPlugins() {
|
|
149
|
+
return JSON.parse(fs.readFileSync(path.join(shellRoot, 'src/plugins/shellPlugins.json'), 'utf8'));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function createTypeManifest({
|
|
153
|
+
plugins = readShellPlugins(),
|
|
154
|
+
shellPlugins = readShellConfig().shell.plugins ?? [],
|
|
155
|
+
} = {}) {
|
|
156
|
+
const manifest = { actions: {}, blocks: {}, operators: {} };
|
|
157
|
+
for (const plugin of plugins) {
|
|
158
|
+
if (!CATEGORIES.includes(plugin.category)) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
`Shell plugin "${plugin.package}" has an unknown category. Received ${JSON.stringify(
|
|
161
|
+
plugin.category
|
|
162
|
+
)}.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const { default: types } = await import(`${plugin.package}/types`);
|
|
166
|
+
const packageTypeNames = getPackageTypeNames({ category: plugin.category, types });
|
|
167
|
+
const names = plugin.types ?? packageTypeNames;
|
|
168
|
+
names.forEach((name) => {
|
|
169
|
+
if (!packageTypeNames.includes(name)) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
`Shell plugin "${plugin.package}" has no ${plugin.category} type "${name}".`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
addType({ category: plugin.category, manifest, name, packageName: plugin.package });
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
for (const plugin of shellPlugins) {
|
|
178
|
+
if (hasTypesExport(plugin.name)) {
|
|
179
|
+
await addShellPluginTypes({ manifest, plugin });
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return manifest;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function createTypeManifestModule() {
|
|
186
|
+
const manifest = await createTypeManifest();
|
|
187
|
+
return `${HEADER}\nconst typeManifest = ${JSON.stringify(
|
|
188
|
+
manifest,
|
|
189
|
+
null,
|
|
190
|
+
2
|
|
191
|
+
)};\n\nexport default typeManifest;\n`;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
195
|
+
if (process.argv.includes('--write')) {
|
|
196
|
+
fs.writeFileSync(
|
|
197
|
+
path.join(shellRoot, 'src/plugins/typeManifest.js'),
|
|
198
|
+
await createTypeManifestModule()
|
|
199
|
+
);
|
|
200
|
+
} else {
|
|
201
|
+
process.stdout.write(JSON.stringify(await createTypeManifest()));
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export default createTypeManifest;
|
package/shell.dev.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "lowdefy-dev",
|
|
3
|
+
"name": "Lowdefy Dev",
|
|
4
|
+
"profile": "dev",
|
|
5
|
+
"scheme": "lowdefy-dev",
|
|
6
|
+
"environment": "dev",
|
|
7
|
+
"ios": { "bundleIdentifier": "com.lowdefy.dev" },
|
|
8
|
+
"android": { "package": "com.lowdefy.dev" },
|
|
9
|
+
"store": {},
|
|
10
|
+
"app": { "id": "dev", "name": "Lowdefy Dev", "urls": { "dev": "http://localhost:3000" } }
|
|
11
|
+
}
|
package/shellVariant.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { DEV_ENTRY, PROFILES, mergeProfiles, pluginName, resolveProfile } = require('./profiles.js');
|
|
18
|
+
|
|
19
|
+
const VARIANTS = ['dev', 'release'];
|
|
20
|
+
|
|
21
|
+
// The string the dev-tab client sends to the dev server. scripts/checkReleaseBundle.mjs fails when a
|
|
22
|
+
// release bundle holds it. Keep it in step with src/dev/tab/devClientMarker.js.
|
|
23
|
+
const DEV_CLIENT_MARKER = 'lowdefy-dev-tab-client';
|
|
24
|
+
|
|
25
|
+
// Everything any profile carries: every module, entry and permission a build can leave out.
|
|
26
|
+
const EVERYTHING = Object.keys(PROFILES).reduce(
|
|
27
|
+
(all, name) => mergeProfiles(all, resolveProfile(name)),
|
|
28
|
+
resolveProfile('basic')
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
// What the `dev` profile adds itself: the developer features, which a release build never carries.
|
|
32
|
+
const DEV_ONLY = PROFILES.dev;
|
|
33
|
+
|
|
34
|
+
// The shell's capability profile: shell.json `profile`, else `dev` for Lowdefy Dev and `basic` for
|
|
35
|
+
// any other shell.
|
|
36
|
+
function getProfileName({ shell, variant }) {
|
|
37
|
+
return shell.profile ?? (variant === 'dev' ? 'dev' : 'basic');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* What a shell build of this variant carries: its profile, resolved (profiles.js). The dev variant
|
|
42
|
+
* also carries Lowdefy Dev's developer features, whatever the profile; a release build carries
|
|
43
|
+
* none of them.
|
|
44
|
+
*/
|
|
45
|
+
function getShellProfile({ shell, variant }) {
|
|
46
|
+
const profile = resolveProfile(getProfileName({ shell, variant }));
|
|
47
|
+
if (variant === 'dev') {
|
|
48
|
+
return mergeProfiles(profile, { ...resolveProfile('dev'), name: profile.name });
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
...profile,
|
|
52
|
+
modules: Object.fromEntries(
|
|
53
|
+
Object.entries(profile.modules).filter(([name]) => !(name in DEV_ONLY.modules))
|
|
54
|
+
),
|
|
55
|
+
plugins: profile.plugins.filter((plugin) => !(pluginName(plugin) in DEV_ONLY.modules)),
|
|
56
|
+
permissions: profile.permissions.filter(
|
|
57
|
+
(permission) => !DEV_ONLY.permissions.includes(permission)
|
|
58
|
+
),
|
|
59
|
+
jsEntries: profile.jsEntries.filter((entry) => !DEV_ONLY.jsEntries.includes(entry)),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* What a shell build of this variant leaves out: every module, JavaScript entry and permission some
|
|
65
|
+
* profile carries and its own does not. app.config.ts leaves the modules unlinked and their config
|
|
66
|
+
* plugins unapplied (withExcludedNativeModules.js) and blocks the permissions; metro.config.js
|
|
67
|
+
* resolves the entries to empty modules.
|
|
68
|
+
*
|
|
69
|
+
* @returns {{ modules: Object<string, string|null>, jsEntries: string[], permissions: string[] }}
|
|
70
|
+
* modules as { [package]: nativeModuleName }.
|
|
71
|
+
*/
|
|
72
|
+
function getExclusions({ shell, variant }) {
|
|
73
|
+
const profile = getShellProfile({ shell, variant });
|
|
74
|
+
return {
|
|
75
|
+
modules: Object.fromEntries(
|
|
76
|
+
Object.entries(EVERYTHING.modules).filter(([name]) => !(name in profile.modules))
|
|
77
|
+
),
|
|
78
|
+
jsEntries: EVERYTHING.jsEntries.filter((entry) => !profile.jsEntries.includes(entry)),
|
|
79
|
+
permissions: EVERYTHING.permissions.filter(
|
|
80
|
+
(permission) => !profile.permissions.includes(permission)
|
|
81
|
+
),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The native modules a shell build leaves unlinked, as { [package]: nativeModuleName }.
|
|
86
|
+
function getExcludedModules({ shell, variant }) {
|
|
87
|
+
return getExclusions({ shell, variant }).modules;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// LOWDEFY_SHELL_VARIANT=dev | release. Without it, Lowdefy Dev (no LOWDEFY_SHELL_CONFIG) is dev and
|
|
91
|
+
// every other shell is release, so a customer shell leaves the developer features out unless its
|
|
92
|
+
// build asks for them.
|
|
93
|
+
function getShellVariant(env = process.env) {
|
|
94
|
+
const variant = env.LOWDEFY_SHELL_VARIANT || (env.LOWDEFY_SHELL_CONFIG ? 'release' : 'dev');
|
|
95
|
+
if (!VARIANTS.includes(variant)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
`LOWDEFY_SHELL_VARIANT must be "dev" or "release". Received ${JSON.stringify(variant)}.`
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
return variant;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
module.exports = {
|
|
104
|
+
DEV_CLIENT_MARKER,
|
|
105
|
+
DEV_ENTRY,
|
|
106
|
+
getExcludedModules,
|
|
107
|
+
getExclusions,
|
|
108
|
+
getProfileName,
|
|
109
|
+
getShellProfile,
|
|
110
|
+
getShellVariant,
|
|
111
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { createContext, useContext, useSyncExternalStore } from 'react';
|
|
18
|
+
|
|
19
|
+
// { auth, session, host, locale, shell } of the app the shell has open.
|
|
20
|
+
const ShellContext = createContext(null);
|
|
21
|
+
|
|
22
|
+
function useShell() {
|
|
23
|
+
return useContext(ShellContext);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function useSessionState() {
|
|
27
|
+
const { session } = useShell();
|
|
28
|
+
return useSyncExternalStore(session.subscribe, session.getState);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { useSessionState, useShell };
|
|
32
|
+
export default ShellContext;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
// The native body of actions-core's CopyToClipboard, with its params ({ copy }), through
|
|
20
|
+
// expo-clipboard (globals.clipboard).
|
|
21
|
+
async function CopyToClipboard({ globals, params }) {
|
|
22
|
+
const { copy } = params;
|
|
23
|
+
if (!type.isString(copy)) {
|
|
24
|
+
throw new Error(`CopyToClipboard requires "copy" text. Received ${JSON.stringify(copy)}.`);
|
|
25
|
+
}
|
|
26
|
+
await globals.clipboard.setString(copy);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default CopyToClipboard;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { jest } from '@jest/globals';
|
|
18
|
+
|
|
19
|
+
import CopyToClipboard from './CopyToClipboard.js';
|
|
20
|
+
|
|
21
|
+
test('CopyToClipboard copies the text through the shell clipboard', async () => {
|
|
22
|
+
const clipboard = { setString: jest.fn(async () => undefined) };
|
|
23
|
+
await CopyToClipboard({ globals: { clipboard }, params: { copy: 'JOB-42' } });
|
|
24
|
+
expect(clipboard.setString.mock.calls).toEqual([['JOB-42']]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('CopyToClipboard throws when there is no text to copy', async () => {
|
|
28
|
+
const clipboard = { setString: jest.fn(async () => undefined) };
|
|
29
|
+
await expect(CopyToClipboard({ globals: { clipboard }, params: {} })).rejects.toThrow(
|
|
30
|
+
'CopyToClipboard requires "copy" text. Received undefined.'
|
|
31
|
+
);
|
|
32
|
+
expect(clipboard.setString).not.toHaveBeenCalled();
|
|
33
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import injected from '../device/injected.js';
|
|
20
|
+
|
|
21
|
+
function numberOrNull(value) {
|
|
22
|
+
return type.isNumber(value) ? value : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The native body of actions-core's GeolocationCurrentPosition, answering as the web body does:
|
|
27
|
+
* `{ coords, timestamp }`, or `{ code: 1, error: 'PERMISSION_DENIED', message }` when location is
|
|
28
|
+
* refused. Until the shell carries device location (the device actions), it answers only with a
|
|
29
|
+
* location Lowdefy Dev injected (src/device/injected.js), and throws otherwise.
|
|
30
|
+
*/
|
|
31
|
+
async function GeolocationCurrentPosition() {
|
|
32
|
+
if (injected('permission:location') === 'denied') {
|
|
33
|
+
return { code: 1, error: 'PERMISSION_DENIED', message: 'Location permission was denied.' };
|
|
34
|
+
}
|
|
35
|
+
const location = injected('location');
|
|
36
|
+
if (type.isUndefined(location)) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'GeolocationCurrentPosition is not available on this phone yet: the shell has no device location. In Lowdefy Dev, inject a location.'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
coords: {
|
|
43
|
+
accuracy: numberOrNull(location.accuracy),
|
|
44
|
+
altitude: numberOrNull(location.altitude),
|
|
45
|
+
altitudeAccuracy: numberOrNull(location.altitudeAccuracy),
|
|
46
|
+
heading: numberOrNull(location.heading),
|
|
47
|
+
latitude: location.latitude,
|
|
48
|
+
longitude: location.longitude,
|
|
49
|
+
speed: numberOrNull(location.speed),
|
|
50
|
+
},
|
|
51
|
+
timestamp: location.timestamp ?? Date.now(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default GeolocationCurrentPosition;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import createDeviceInjections from '../dev/inject/createDeviceInjections.js';
|
|
18
|
+
import createFakeNetwork from '../test/createFakeNetwork.js';
|
|
19
|
+
import createNetworkStatus from '../host/createNetworkStatus.js';
|
|
20
|
+
import GeolocationCurrentPosition from './GeolocationCurrentPosition.js';
|
|
21
|
+
import { installInjector } from '../device/injected.js';
|
|
22
|
+
|
|
23
|
+
let injections;
|
|
24
|
+
let remove;
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
injections = createDeviceInjections({
|
|
27
|
+
networkStatus: createNetworkStatus({ network: createFakeNetwork() }),
|
|
28
|
+
});
|
|
29
|
+
remove = installInjector(injections);
|
|
30
|
+
});
|
|
31
|
+
afterEach(() => remove());
|
|
32
|
+
|
|
33
|
+
test('GeolocationCurrentPosition answers with the injected location in the web shape', async () => {
|
|
34
|
+
injections.inject({
|
|
35
|
+
kind: 'location',
|
|
36
|
+
value: { latitude: -33.92, longitude: 18.42, accuracy: 12, timestamp: 1760778000000 },
|
|
37
|
+
});
|
|
38
|
+
await expect(GeolocationCurrentPosition({ params: {} })).resolves.toEqual({
|
|
39
|
+
coords: {
|
|
40
|
+
accuracy: 12,
|
|
41
|
+
altitude: null,
|
|
42
|
+
altitudeAccuracy: null,
|
|
43
|
+
heading: null,
|
|
44
|
+
latitude: -33.92,
|
|
45
|
+
longitude: 18.42,
|
|
46
|
+
speed: null,
|
|
47
|
+
},
|
|
48
|
+
timestamp: 1760778000000,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('GeolocationCurrentPosition answers as the web does when location is refused', async () => {
|
|
53
|
+
injections.inject({ kind: 'location', value: { latitude: -33.92, longitude: 18.42 } });
|
|
54
|
+
injections.inject({ kind: 'permission', name: 'location', value: 'denied' });
|
|
55
|
+
await expect(GeolocationCurrentPosition({ params: {} })).resolves.toEqual({
|
|
56
|
+
code: 1,
|
|
57
|
+
error: 'PERMISSION_DENIED',
|
|
58
|
+
message: 'Location permission was denied.',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('GeolocationCurrentPosition throws when the shell has no location to give', async () => {
|
|
63
|
+
await expect(GeolocationCurrentPosition({ params: {} })).rejects.toThrow(
|
|
64
|
+
'GeolocationCurrentPosition is not available on this phone yet: the shell has no device location. In Lowdefy Dev, inject a location.'
|
|
65
|
+
);
|
|
66
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
import parseStoredValue from '@lowdefy/actions-core/localStorage/parseStoredValue.js';
|
|
19
|
+
import validateStorageKey from '@lowdefy/actions-core/localStorage/validateStorageKey.js';
|
|
20
|
+
|
|
21
|
+
// The native body of actions-core's GetLocalStorage: reads the app's key from the device store
|
|
22
|
+
// (expo-sqlite's key-value store), where the web reads localStorage.
|
|
23
|
+
function GetLocalStorage({ globals, methods, params }) {
|
|
24
|
+
if (!type.isObject(params)) {
|
|
25
|
+
throw new Error('GetLocalStorage params should be an object.');
|
|
26
|
+
}
|
|
27
|
+
const { key } = params;
|
|
28
|
+
validateStorageKey({ actionType: 'GetLocalStorage', key });
|
|
29
|
+
const item = globals.storage.get(methods.getAppStorageKey(key));
|
|
30
|
+
if (type.isNone(item)) {
|
|
31
|
+
return params.default;
|
|
32
|
+
}
|
|
33
|
+
return parseStoredValue(item);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default GetLocalStorage;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
import validateStorageKey from '@lowdefy/actions-core/localStorage/validateStorageKey.js';
|
|
19
|
+
|
|
20
|
+
// The native body of actions-core's RemoveLocalStorage: removes the app's key from the device
|
|
21
|
+
// store (expo-sqlite's key-value store), where the web removes it from localStorage.
|
|
22
|
+
function RemoveLocalStorage({ globals, methods, params }) {
|
|
23
|
+
if (!type.isObject(params)) {
|
|
24
|
+
throw new Error('RemoveLocalStorage params should be an object.');
|
|
25
|
+
}
|
|
26
|
+
const { key } = params;
|
|
27
|
+
validateStorageKey({ actionType: 'RemoveLocalStorage', key });
|
|
28
|
+
globals.storage.remove(methods.getAppStorageKey(key));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default RemoveLocalStorage;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright 2020-2026 Lowdefy, Inc
|
|
3
|
+
|
|
4
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
you may not use this file except in compliance with the License.
|
|
6
|
+
You may obtain a copy of the License at
|
|
7
|
+
|
|
8
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
|
|
10
|
+
Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
See the License for the specific language governing permissions and
|
|
14
|
+
limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type } from '@lowdefy/helpers';
|
|
18
|
+
|
|
19
|
+
import measureInScrollView from './measureInScrollView.js';
|
|
20
|
+
|
|
21
|
+
// Where the block's top lands, by scrollIntoView's `block` option. 'nearest' needs the current
|
|
22
|
+
// scroll position, which a ScrollView does not report, so it scrolls as 'start' does.
|
|
23
|
+
function getScrollTop({ align, element, viewport }) {
|
|
24
|
+
switch (align) {
|
|
25
|
+
case 'center':
|
|
26
|
+
return element.top - (viewport.height - element.height) / 2;
|
|
27
|
+
case 'end':
|
|
28
|
+
return element.top - viewport.height + element.height;
|
|
29
|
+
default:
|
|
30
|
+
return element.top;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// The native body of actions-core's ScrollTo: scrolls the page's ScrollView to a block's measured
|
|
35
|
+
// position, or to `top` and `left` as window.scrollTo does. A block not on the page is left alone,
|
|
36
|
+
// as on the web.
|
|
37
|
+
async function ScrollTo({ globals, methods, params }) {
|
|
38
|
+
if (!type.isObject(params)) {
|
|
39
|
+
throw new Error('Invalid ScrollTo, check action params.');
|
|
40
|
+
}
|
|
41
|
+
const pageId = methods.getPageId();
|
|
42
|
+
const scrollView = globals.elements.find({ kind: 'scroll', pageId });
|
|
43
|
+
if (scrollView === null) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (type.isNone(params.blockId)) {
|
|
47
|
+
scrollView.scrollTo({
|
|
48
|
+
animated: params.behavior === 'smooth',
|
|
49
|
+
x: params.left ?? 0,
|
|
50
|
+
y: params.top ?? 0,
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const view = globals.elements.find({ blockId: params.blockId, kind: 'view', pageId });
|
|
55
|
+
if (view === null) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const { element, viewport } = await measureInScrollView({ scrollView, view });
|
|
59
|
+
scrollView.scrollTo({
|
|
60
|
+
animated: params.options?.behavior === 'smooth',
|
|
61
|
+
x: 0,
|
|
62
|
+
y: Math.max(0, getScrollTop({ align: params.options?.block, element, viewport })),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export default ScrollTo;
|