entangled-worlds-orbiters-shared 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/textureConfig.d.ts +2 -15
- package/dist/config/textureConfig.js +1 -43
- package/dist/loaders/index.d.ts +0 -2
- package/dist/loaders/index.js +0 -1
- package/dist/loaders/ktx2.js +2 -2
- package/dist/shortcuts/chord.d.ts +24 -2
- package/dist/shortcuts/chord.js +86 -8
- package/dist/shortcuts/format.d.ts +7 -4
- package/dist/shortcuts/format.js +40 -14
- package/dist/shortcuts/index.d.ts +1 -1
- package/dist/shortcuts/index.js +4 -3
- package/dist/shortcuts/match.d.ts +3 -1
- package/dist/shortcuts/match.js +14 -8
- package/dist/sky/geometry.d.ts +31 -0
- package/dist/sky/geometry.js +84 -0
- package/dist/sky/index.d.ts +6 -0
- package/dist/sky/index.js +6 -0
- package/dist/sky/params.d.ts +44 -0
- package/dist/sky/params.js +52 -0
- package/dist/sky/shaders.d.ts +14 -0
- package/dist/sky/shaders.js +149 -0
- package/dist/sky/sky.d.ts +24 -0
- package/dist/sky/sky.js +268 -0
- package/dist/sky/worldSkyFile.d.ts +16 -0
- package/dist/sky/worldSkyFile.js +51 -0
- package/dist/star/bounds.d.ts +1 -1
- package/dist/star/bounds.js +1 -1
- package/dist/star/params.d.ts +1 -1
- package/dist/star/params.js +1 -1
- package/dist/star/starBody.d.ts +1 -1
- package/dist/star/starManager.d.ts +1 -1
- package/package.json +8 -2
- package/public/config/textures.json +0 -73
- package/src/config/index.ts +0 -2
- package/src/config/textureConfig.ts +2 -53
- package/src/loaders/index.ts +0 -2
- package/src/loaders/ktx2.ts +2 -2
- package/src/shortcuts/chord.ts +96 -6
- package/src/shortcuts/format.ts +44 -17
- package/src/shortcuts/index.ts +7 -2
- package/src/shortcuts/match.ts +13 -8
- package/src/sky/geometry.ts +99 -0
- package/src/sky/index.ts +16 -0
- package/src/sky/params.ts +89 -0
- package/src/sky/shaders.ts +159 -0
- package/src/sky/sky.ts +330 -0
- package/src/sky/worldSkyFile.ts +60 -0
- package/src/star/bounds.ts +1 -1
- package/src/star/params.ts +1 -1
- package/src/star/starBody.ts +1 -1
- package/src/star/starManager.ts +1 -1
- package/dist/loaders/cubeMapLoader.d.ts +0 -58
- package/dist/loaders/cubeMapLoader.js +0 -291
- package/src/loaders/cubeMapLoader.ts +0 -342
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ A shared library containing core components for 3D world rendering, moons system
|
|
|
6
6
|
|
|
7
7
|
- **World System**: Core world rendering with textured spheres, normal materials, GLB models, and cloud shells
|
|
8
8
|
- **Moons System**: Orbital moon generation and management
|
|
9
|
-
- **Loaders**: Texture
|
|
9
|
+
- **Loaders**: Texture and GLB loading utilities
|
|
10
10
|
- **Core Rendering**: Lighting, Fresnel effects, and material utilities
|
|
11
11
|
- **Configuration**: Texture and configuration management system
|
|
12
12
|
|
|
@@ -57,7 +57,6 @@ import * as EntangledWorlds from 'entangled-worlds-orbiters-shared';
|
|
|
57
57
|
- `moonsGeneration` - Moon generation logic
|
|
58
58
|
|
|
59
59
|
### Loaders
|
|
60
|
-
- `cubeMapLoader` - Skybox loading
|
|
61
60
|
- `glbLoader` - GLB loading utilities
|
|
62
61
|
- `bodyTextureLoader` - Texture loading
|
|
63
62
|
|
|
@@ -99,7 +98,7 @@ The capability registry (`createCapabilityRegistry`, `validate` / `assertValid`,
|
|
|
99
98
|
- `largestFunction`, `shrinkWasm` - The size check and the shrink step
|
|
100
99
|
|
|
101
100
|
### Keyboard shortcuts (`entangled-worlds-orbiters-shared/shortcuts`)
|
|
102
|
-
- `ShortcutAction` - One catalogue entry: `id`, `label` (+ `names` by language), `defaultKeys` (
|
|
101
|
+
- `ShortcutAction` - One catalogue entry: `id`, `label` (+ `names` by language), `defaultKeys` (named as a US keyboard names them, `event.code`; a press matches by the character it types, by its place only where it types no US character, so keys work on any keyboard), `scope`
|
|
103
102
|
- `matchShortcut(event, bindings)` - Which action a press is, or `null`: modifiers, a field being typed in and surface claims decided in one place
|
|
104
103
|
- Key strings: `Shift+KeyR`, `Mod+KeyZ` (Ctrl or ⌘), `Shift?+KeyQ` (Shift may be held); `parseChord`, `chordsOverlap`
|
|
105
104
|
- `resolveBindings(catalogue, overrides)`, `findClashes` - Defaults plus a person's overrides (`[{ id, keys }]` as their settings store them, `keys: []` unbinds), and the clash check
|
|
@@ -150,7 +149,7 @@ Defaults:
|
|
|
150
149
|
|
|
151
150
|
Effects:
|
|
152
151
|
- All URLs returned by `config/textureConfig` helpers are absolute.
|
|
153
|
-
-
|
|
152
|
+
- Moons generation resolves relative paths against the configured base.
|
|
154
153
|
- Cloud shell textures passed to `world.makeSphere` are resolved to absolute URLs.
|
|
155
154
|
|
|
156
155
|
## Peer Dependencies
|
package/dist/config/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture,
|
|
1
|
+
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture, getTextureFallbackUrl } from './textureConfig.js';
|
|
2
2
|
export type { TextureConfig, TextureCategory, TextureItem, TextureChoice, TextureConfigMetadata } from './textureConfig.js';
|
|
3
3
|
export { setPublicBase, getPublicBase, resolvePublicUrl } from './env.js';
|
package/dist/config/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Configuration exports
|
|
2
|
-
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture,
|
|
2
|
+
export { loadTextureConfig, getTextureOptions, getTextureChoices, getDefaultTexture, getTextureFallbackUrl } from './textureConfig.js';
|
|
3
3
|
// runtime base helpers
|
|
4
4
|
export { setPublicBase, getPublicBase, resolvePublicUrl } from './env.js';
|
|
@@ -9,12 +9,11 @@ export interface TextureItem {
|
|
|
9
9
|
fallback?: string;
|
|
10
10
|
tags?: string[];
|
|
11
11
|
}
|
|
12
|
-
/** One catalogue category (clouds, moons,
|
|
12
|
+
/** One catalogue category (clouds, moons, …). */
|
|
13
13
|
export interface TextureCategory {
|
|
14
14
|
category?: string;
|
|
15
15
|
basePath?: string;
|
|
16
16
|
defaultTexture?: string;
|
|
17
|
-
defaultSkybox?: string;
|
|
18
17
|
items?: TextureItem[];
|
|
19
18
|
}
|
|
20
19
|
/** The texture catalogue as loaded from the config URL. */
|
|
@@ -70,7 +69,7 @@ export type TextureChoice = {
|
|
|
70
69
|
export declare function getTextureChoices(category: string, config?: TextureConfig | null): Promise<TextureChoice[]>;
|
|
71
70
|
/**
|
|
72
71
|
* Get texture options for a specific category (clouds, moons, etc.)
|
|
73
|
-
* @param {string} category - Texture category (clouds, moons
|
|
72
|
+
* @param {string} category - Texture category (clouds, moons)
|
|
74
73
|
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
75
74
|
* @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
|
|
76
75
|
*/
|
|
@@ -114,18 +113,6 @@ export declare function getTextureUrl(category: string, textureId: string, confi
|
|
|
114
113
|
* @returns {Promise<TextureItem[]>} Array of matching texture definitions
|
|
115
114
|
*/
|
|
116
115
|
export declare function searchTexturesByTags(category: string, tags: string[], config?: TextureConfig | null): Promise<TextureItem[]>;
|
|
117
|
-
/**
|
|
118
|
-
* Get skybox options formatted for GUI dropdowns
|
|
119
|
-
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
120
|
-
* @returns {Promise<Record<string, string>>} `{ label: id }`
|
|
121
|
-
*/
|
|
122
|
-
export declare function getSkyboxOptions(config?: TextureConfig | null): Promise<Record<string, string>>;
|
|
123
|
-
/**
|
|
124
|
-
* Get default skybox ID
|
|
125
|
-
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
126
|
-
* @returns {Promise<string>} Default skybox ID
|
|
127
|
-
*/
|
|
128
|
-
export declare function getDefaultSkybox(config?: TextureConfig | null): Promise<string>;
|
|
129
116
|
/**
|
|
130
117
|
* Reload configuration (force refresh)
|
|
131
118
|
* @param {string} configUrl - Override config URL
|
|
@@ -117,7 +117,7 @@ export async function getTextureChoices(category, config) {
|
|
|
117
117
|
}
|
|
118
118
|
/**
|
|
119
119
|
* Get texture options for a specific category (clouds, moons, etc.)
|
|
120
|
-
* @param {string} category - Texture category (clouds, moons
|
|
120
|
+
* @param {string} category - Texture category (clouds, moons)
|
|
121
121
|
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
122
122
|
* @returns {Promise<Record<string, string>>} `{ label: url }`, suitable for dropdown option lists
|
|
123
123
|
*/
|
|
@@ -215,40 +215,6 @@ export async function searchTexturesByTags(category, tags, config) {
|
|
|
215
215
|
}
|
|
216
216
|
return categoryConfig.items.filter(item => item.tags && tags.some(tag => item.tags.includes(tag)));
|
|
217
217
|
}
|
|
218
|
-
/**
|
|
219
|
-
* Get skybox options formatted for GUI dropdowns
|
|
220
|
-
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
221
|
-
* @returns {Promise<Record<string, string>>} `{ label: id }`
|
|
222
|
-
*/
|
|
223
|
-
export async function getSkyboxOptions(config) {
|
|
224
|
-
const textureConfig = await resolveConfig(config);
|
|
225
|
-
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
226
|
-
if (!skyboxConfig) {
|
|
227
|
-
console.warn('[TextureConfig] Skyboxes category not found');
|
|
228
|
-
return {};
|
|
229
|
-
}
|
|
230
|
-
const options = {};
|
|
231
|
-
// Reads `items` unguarded, unlike the other readers: a skyboxes category without items throws here.
|
|
232
|
-
skyboxConfig.items.forEach(item => {
|
|
233
|
-
const label = item.label || item.id;
|
|
234
|
-
const id = item.id;
|
|
235
|
-
options[label] = id;
|
|
236
|
-
});
|
|
237
|
-
return options;
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Get default skybox ID
|
|
241
|
-
* @param {TextureConfig} config - Optional pre-loaded config object
|
|
242
|
-
* @returns {Promise<string>} Default skybox ID
|
|
243
|
-
*/
|
|
244
|
-
export async function getDefaultSkybox(config) {
|
|
245
|
-
const textureConfig = await resolveConfig(config);
|
|
246
|
-
const skyboxConfig = textureConfig.textures.skyboxes;
|
|
247
|
-
if (!skyboxConfig) {
|
|
248
|
-
return '001'; // fallback
|
|
249
|
-
}
|
|
250
|
-
return skyboxConfig.defaultSkybox || '001';
|
|
251
|
-
}
|
|
252
218
|
/**
|
|
253
219
|
* Hardcoded fallback configuration (minimal)
|
|
254
220
|
* Used when remote config loading fails
|
|
@@ -275,14 +241,6 @@ function getHardcodedFallback() {
|
|
|
275
241
|
items: [
|
|
276
242
|
{ id: "default-moon", filename: "moon-equirectangular.ktx2", label: "Default Moon", tags: ["lunar"] }
|
|
277
243
|
]
|
|
278
|
-
},
|
|
279
|
-
skyboxes: {
|
|
280
|
-
category: "environment",
|
|
281
|
-
basePath: "/skybox",
|
|
282
|
-
defaultSkybox: "001",
|
|
283
|
-
items: [
|
|
284
|
-
{ id: "001", label: "Deep Space", tags: ["space"] }
|
|
285
|
-
]
|
|
286
244
|
}
|
|
287
245
|
}
|
|
288
246
|
};
|
package/dist/loaders/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
|
|
2
1
|
export { loadGLB } from './glbLoader.js';
|
|
3
2
|
export { loadTexture } from './bodyTextureLoader.js';
|
|
4
3
|
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
|
5
|
-
export type { SkyboxResource } from './cubeMapLoader.js';
|
package/dist/loaders/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
// Loaders exports
|
|
2
|
-
export { loadCubeMap, setBackground, normalizeSkycubeName, buildSkycubefaceKeys } from './cubeMapLoader.js';
|
|
3
2
|
export { loadGLB } from './glbLoader.js';
|
|
4
3
|
export { loadTexture } from './bodyTextureLoader.js';
|
|
5
4
|
export { getKTX2LoaderForRenderer, disposeKTX2LoaderForRenderer } from './ktx2.js';
|
package/dist/loaders/ktx2.js
CHANGED
|
@@ -4,12 +4,12 @@ import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
|
4
4
|
*
|
|
5
5
|
* `KTX2Loader.detectSupport(renderer)` binds the loader's Basis transcoder worker pool to ONE WebGL
|
|
6
6
|
* context. A single shared loader therefore only decodes textures for the FIRST renderer it saw —
|
|
7
|
-
* every other view's planets
|
|
7
|
+
* every other view's planets silently fail and render untextured (dark). That breaks any
|
|
8
8
|
* multi-view page: N orbiters in one realm, or N Entangled World planets shown together.
|
|
9
9
|
*
|
|
10
10
|
* Keying the loader by renderer gives each view its own correctly-bound loader. Single-renderer apps
|
|
11
11
|
* (a single orbiter, today's Entangled World) get exactly ONE entry, so behaviour is byte-identical.
|
|
12
|
-
* (Was: a single `window.__EW_KTX2_LOADER` global shared by bodyTexture/glb
|
|
12
|
+
* (Was: a single `window.__EW_KTX2_LOADER` global shared by the bodyTexture/glb loaders.)
|
|
13
13
|
*/
|
|
14
14
|
const loadersByRenderer = new WeakMap();
|
|
15
15
|
function makeLoader(renderer) {
|
|
@@ -18,8 +18,30 @@ export interface ModifierState {
|
|
|
18
18
|
}
|
|
19
19
|
/** The chord a key string names, or `null` when it names none (an unknown modifier, a doubled one, no key). */
|
|
20
20
|
export declare function parseChord(keys: string): Chord | null;
|
|
21
|
-
/**
|
|
22
|
-
export
|
|
21
|
+
/** A press as a chord reads it: the modifiers, the key's place and, when the browser says, the character it typed. */
|
|
22
|
+
export interface ChordPress extends ModifierState {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly key?: string;
|
|
25
|
+
}
|
|
26
|
+
/** How a press is this chord: by the character it typed, by the key's place, or not at all. */
|
|
27
|
+
export type ChordMatch = 'character' | 'place' | null;
|
|
28
|
+
/**
|
|
29
|
+
* How a press is this chord. `character` beats `place`: on a Spanish keyboard Shift+7 types `/`, so it
|
|
30
|
+
* is the `/` key before it is the `7` key. A press with no `key` (an old caller) matches by place only.
|
|
31
|
+
*/
|
|
32
|
+
export declare function chordMatch(chord: Chord, press: ChordPress): ChordMatch;
|
|
33
|
+
/** Whether a press of `code` with these modifiers (and, when given, the character it typed) is this chord. */
|
|
34
|
+
export declare function chordMatches(chord: Chord, code: string, state: ModifierState & {
|
|
35
|
+
readonly key?: string;
|
|
36
|
+
}): boolean;
|
|
37
|
+
/**
|
|
38
|
+
* The key string a typed character names (`/` → `Slash`, `?` → `Shift+Slash`, `q` → `KeyQ`), for
|
|
39
|
+
* someone setting a key; `null` for a character a US keyboard does not have, or none.
|
|
40
|
+
*/
|
|
41
|
+
export declare function typedKey(key: string | undefined): {
|
|
42
|
+
readonly code: string;
|
|
43
|
+
readonly shift?: boolean;
|
|
44
|
+
} | null;
|
|
23
45
|
/** A chord that needs Ctrl or ⌘: a command, never a character typed. */
|
|
24
46
|
export declare function isCommandChord(chord: Chord): boolean;
|
|
25
47
|
/** Whether one press could be both chords — the test for a clash. */
|
package/dist/shortcuts/chord.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
// One key with its modifiers, written as a string: `Shift+KeyR`, `Mod+KeyZ`, `Shift?+Digit1`.
|
|
2
2
|
//
|
|
3
|
-
// The last part
|
|
4
|
-
//
|
|
3
|
+
// The last part names the key the way a US keyboard does (`KeyboardEvent.code`: `KeyQ`, `Slash`,
|
|
4
|
+
// `Digit1`). A press matches it by the CHARACTER it types wherever it types one, so `/` is the key
|
|
5
|
+
// that prints `/` on this person's keyboard (Shift+7 on a Spanish one), and `KeyQ` is the key that
|
|
6
|
+
// prints Q (top left on a French one). Only where the key types no character the US keyboard has
|
|
7
|
+
// (a Greek or Cyrillic letter, ⌥ on a Mac changing it, a dead key, no letter on a letter key) is the
|
|
8
|
+
// key matched by its place.
|
|
9
|
+
// Digits match by character first, then by place: a French keyboard types `&` there.
|
|
10
|
+
// Before the key, the modifiers:
|
|
5
11
|
//
|
|
6
12
|
// Shift, Alt, Ctrl, Meta held. One left out must NOT be held.
|
|
7
13
|
// Mod Ctrl or ⌘ (either, or both), the command key of every platform.
|
|
@@ -48,18 +54,90 @@ function parse(keys) {
|
|
|
48
54
|
return { code, ...rules };
|
|
49
55
|
}
|
|
50
56
|
const fits = (rule, down) => rule === 'any' || (rule === 'held') === Boolean(down);
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
/**
|
|
58
|
+
* The punctuation keys a chord may name, and what a US keyboard types there without and with Shift:
|
|
59
|
+
* what the chord means on every keyboard. With these, Shift is part of the character (`?` is
|
|
60
|
+
* `Shift+Slash`), so a keyboard that needs Shift, or AltGr, for a `/` still types the `/`.
|
|
61
|
+
*/
|
|
62
|
+
const PUNCTUATION = {
|
|
63
|
+
Minus: ['-', '_'], Equal: ['=', '+'], BracketLeft: ['[', '{'], BracketRight: [']', '}'], Backslash: ['\\', '|'],
|
|
64
|
+
Semicolon: [';', ':'], Quote: ["'", '"'], Backquote: ['`', '~'], Comma: [',', '<'], Period: ['.', '>'], Slash: ['/', '?'],
|
|
65
|
+
};
|
|
66
|
+
/** The code a US keyboard types each punctuation character at, and whether it takes Shift. */
|
|
67
|
+
const PUNCTUATION_PLACE = new Map(Object.entries(PUNCTUATION).flatMap(([code, [plain, shifted]]) => [[plain, [code, false]], [shifted, [code, true]]]));
|
|
68
|
+
/** The character a press typed, when it is one a US keyboard has (printable ASCII); else `null`. */
|
|
69
|
+
function typed(key) {
|
|
70
|
+
return typeof key === 'string' && key.length === 1 && key >= '!' && key <= '~' ? key : null;
|
|
71
|
+
}
|
|
72
|
+
function commandFits(chord, state) {
|
|
57
73
|
if (chord.mod === 'held')
|
|
58
74
|
return Boolean(state.ctrlKey || state.metaKey);
|
|
59
75
|
if (chord.mod === 'any')
|
|
60
76
|
return true;
|
|
61
77
|
return fits(chord.ctrl, state.ctrlKey) && fits(chord.meta, state.metaKey);
|
|
62
78
|
}
|
|
79
|
+
function placeMatch(chord, code, state) {
|
|
80
|
+
return chord.code === code && fits(chord.shift, state.shiftKey) && fits(chord.alt, state.altKey) && commandFits(chord, state);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* How a press is this chord. `character` beats `place`: on a Spanish keyboard Shift+7 types `/`, so it
|
|
84
|
+
* is the `/` key before it is the `7` key. A press with no `key` (an old caller) matches by place only.
|
|
85
|
+
*/
|
|
86
|
+
export function chordMatch(chord, press) {
|
|
87
|
+
const char = typed(press.key);
|
|
88
|
+
if (char === null)
|
|
89
|
+
return placeMatch(chord, press.code, press) ? 'place' : null;
|
|
90
|
+
const punctuation = PUNCTUATION[chord.code];
|
|
91
|
+
if (punctuation) {
|
|
92
|
+
// Shift is in the character (a Spanish `/` is Shift+7); with ⌘ some browsers report the key
|
|
93
|
+
// unshifted, so `⇧⌘,` is also a `,` with Shift held. Alt only when the chord asks for it (AltGr
|
|
94
|
+
// and ⌥ type characters).
|
|
95
|
+
const [plain, shifted] = punctuation;
|
|
96
|
+
const asShifted = char === shifted || (isCommandChord(chord) && char === plain && press.shiftKey);
|
|
97
|
+
const ok = chord.shift === 'held' ? asShifted : chord.shift === 'any' ? char === plain || asShifted : char === plain;
|
|
98
|
+
// A command (⇧⌘,) where this keyboard types another character there: the key's place, as menus do.
|
|
99
|
+
if (!ok)
|
|
100
|
+
return isCommandChord(chord) && placeMatch(chord, press.code, press) ? 'place' : null;
|
|
101
|
+
if (chord.alt === 'held' && !press.altKey)
|
|
102
|
+
return null;
|
|
103
|
+
// AltGr arrives as Ctrl+Alt on Windows: that Ctrl typed the character, it is no command.
|
|
104
|
+
const altGr = press.altKey && press.ctrlKey && !press.metaKey;
|
|
105
|
+
return commandFits(chord, { ctrlKey: press.ctrlKey && !altGr, metaKey: press.metaKey, shiftKey: press.shiftKey, altKey: press.altKey }) ? 'character' : null;
|
|
106
|
+
}
|
|
107
|
+
const letter = /^Key([A-Z])$/.exec(chord.code)?.[1];
|
|
108
|
+
if (letter) {
|
|
109
|
+
// A key that types no letter (`;` where Q is on a Greek keyboard) is read by its place.
|
|
110
|
+
if (!/^[a-zA-Z]$/.test(char))
|
|
111
|
+
return placeMatch(chord, press.code, press) ? 'place' : null;
|
|
112
|
+
if (char.toUpperCase() !== letter)
|
|
113
|
+
return null;
|
|
114
|
+
return fits(chord.shift, press.shiftKey) && fits(chord.alt, press.altKey) && commandFits(chord, press) ? 'character' : null;
|
|
115
|
+
}
|
|
116
|
+
const digit = /^Digit(\d)$/.exec(chord.code)?.[1];
|
|
117
|
+
// A digit typed; where the keyboard types something else there (`&` on a French one), its place.
|
|
118
|
+
if (digit && char === digit && fits(chord.shift, press.shiftKey) && fits(chord.alt, press.altKey) && commandFits(chord, press))
|
|
119
|
+
return 'character';
|
|
120
|
+
return placeMatch(chord, press.code, press) ? 'place' : null;
|
|
121
|
+
}
|
|
122
|
+
/** Whether a press of `code` with these modifiers (and, when given, the character it typed) is this chord. */
|
|
123
|
+
export function chordMatches(chord, code, state) {
|
|
124
|
+
// Field by field: a browser's KeyboardEvent keeps them on its prototype, where a spread does not reach.
|
|
125
|
+
const { shiftKey, altKey, ctrlKey, metaKey, key } = state;
|
|
126
|
+
return chordMatch(chord, { code, key, shiftKey, altKey, ctrlKey, metaKey }) !== null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* The key string a typed character names (`/` → `Slash`, `?` → `Shift+Slash`, `q` → `KeyQ`), for
|
|
130
|
+
* someone setting a key; `null` for a character a US keyboard does not have, or none.
|
|
131
|
+
*/
|
|
132
|
+
export function typedKey(key) {
|
|
133
|
+
const char = typed(key);
|
|
134
|
+
if (char === null)
|
|
135
|
+
return null;
|
|
136
|
+
if (/^[a-zA-Z]$/.test(char))
|
|
137
|
+
return { code: `Key${char.toUpperCase()}` };
|
|
138
|
+
const place = PUNCTUATION_PLACE.get(char);
|
|
139
|
+
return place ? { code: place[0], shift: place[1] } : null;
|
|
140
|
+
}
|
|
63
141
|
/** A chord that needs Ctrl or ⌘: a command, never a character typed. */
|
|
64
142
|
export function isCommandChord(chord) {
|
|
65
143
|
return chord.mod === 'held' || chord.ctrl === 'held' || chord.meta === 'held';
|
|
@@ -4,8 +4,8 @@ export type ShortcutPlatform = 'apple' | 'other';
|
|
|
4
4
|
export declare function shortcutPlatform(): ShortcutPlatform;
|
|
5
5
|
/**
|
|
6
6
|
* The character a key place prints on this person's keyboard, when the browser can say
|
|
7
|
-
* (`navigator.keyboard.getLayoutMap()`, Chromium only)
|
|
8
|
-
*
|
|
7
|
+
* (`navigator.keyboard.getLayoutMap()`, Chromium only). Only keys matched by their place use it:
|
|
8
|
+
* a letter, digit or punctuation key matches the character it names, so it shows that character.
|
|
9
9
|
*/
|
|
10
10
|
export type KeyLayout = ReadonlyMap<string, string>;
|
|
11
11
|
/** What a key place is called on a keyboard: `KeyQ` → `Q`, `Digit1` → `1`, `Numpad4` → `Num 4`. */
|
|
@@ -20,11 +20,14 @@ export declare function chordParts(keys: string, platform?: ShortcutPlatform, la
|
|
|
20
20
|
export declare function formatChord(keys: string, platform?: ShortcutPlatform, layout?: KeyLayout): string;
|
|
21
21
|
/**
|
|
22
22
|
* The key string a press makes, for someone setting a key: exactly the modifiers held, the command
|
|
23
|
-
* key of their platform written `Mod` (⌘ on Apple, Ctrl elsewhere) so the key works on both.
|
|
24
|
-
*
|
|
23
|
+
* key of their platform written `Mod` (⌘ on Apple, Ctrl elsewhere) so the key works on both. A letter
|
|
24
|
+
* or punctuation character typed is written as the key that types it on a US keyboard (`/` is
|
|
25
|
+
* `Slash` whichever keys typed it), the way the matcher reads it back. `null` while only modifiers
|
|
26
|
+
* are down, or for a press with no key place.
|
|
25
27
|
*/
|
|
26
28
|
export declare function chordFromEvent(event: {
|
|
27
29
|
code: string;
|
|
30
|
+
key?: string;
|
|
28
31
|
shiftKey: boolean;
|
|
29
32
|
altKey: boolean;
|
|
30
33
|
ctrlKey: boolean;
|
package/dist/shortcuts/format.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// A key string for people: how a chord is shown (`⌘⇧Z`, `Ctrl+Shift+Z`, `Q`), and the key string a
|
|
2
2
|
// press makes when someone sets a key ("press a key to change").
|
|
3
|
-
import { isModifierCode, parseChord } from './chord.js';
|
|
3
|
+
import { isModifierCode, parseChord, typedKey } from './chord.js';
|
|
4
4
|
/** The platform of this browser, by its own report; `other` outside a browser. */
|
|
5
5
|
export function shortcutPlatform() {
|
|
6
6
|
const nav = globalThis.navigator;
|
|
@@ -18,7 +18,8 @@ const NAMED = {
|
|
|
18
18
|
};
|
|
19
19
|
/** What a key place is called on a keyboard: `KeyQ` → `Q`, `Digit1` → `1`, `Numpad4` → `Num 4`. */
|
|
20
20
|
export function keyLabel(code, layout) {
|
|
21
|
-
const
|
|
21
|
+
const byCharacter = /^(Key[A-Z]|Digit\d)$/.test(code) || typedKey(NAMED[code])?.code === code;
|
|
22
|
+
const printed = byCharacter ? undefined : layout?.get(code);
|
|
22
23
|
if (printed && printed.trim())
|
|
23
24
|
return printed.length === 1 ? printed.toUpperCase() : printed;
|
|
24
25
|
if (/^Key[A-Z]$/.test(code))
|
|
@@ -29,6 +30,11 @@ export function keyLabel(code, layout) {
|
|
|
29
30
|
return `Num ${code.slice(6)}`;
|
|
30
31
|
return NAMED[code] ?? code;
|
|
31
32
|
}
|
|
33
|
+
/** What a US keyboard types with Shift at each punctuation key. */
|
|
34
|
+
const SHIFTED = {
|
|
35
|
+
Minus: '_', Equal: '+', BracketLeft: '{', BracketRight: '}', Backslash: '|', Semicolon: ':', Quote: '"',
|
|
36
|
+
Backquote: '~', Comma: '<', Period: '>', Slash: '?',
|
|
37
|
+
};
|
|
32
38
|
const SYMBOLS = { ctrl: '⌃', alt: '⌥', shift: '⇧', meta: '⌘' };
|
|
33
39
|
const WORDS = { ctrl: 'Ctrl', alt: 'Alt', shift: 'Shift', meta: 'Meta' };
|
|
34
40
|
/**
|
|
@@ -55,12 +61,16 @@ export function chordParts(keys, platform = shortcutPlatform(), layout) {
|
|
|
55
61
|
parts.push(names.ctrl);
|
|
56
62
|
if (held(chord.alt))
|
|
57
63
|
parts.push(platform === 'apple' ? names.alt : 'Alt');
|
|
58
|
-
|
|
64
|
+
// A punctuation key is the character it types, Shift included: `Shift+Slash` is `?`.
|
|
65
|
+
// A command (⇧⌘,) is shown as written: its Shift is a key to press, not part of a character.
|
|
66
|
+
const punctuation = !ctrl && !meta && typedKey(NAMED[chord.code])?.code === chord.code ? SHIFTED[chord.code] : undefined;
|
|
67
|
+
if (held(chord.shift) && !punctuation)
|
|
59
68
|
parts.push(names.shift);
|
|
60
69
|
if (meta)
|
|
61
70
|
parts.push(names.meta);
|
|
62
|
-
// The help key is `?` wherever it lets Shift be held (
|
|
63
|
-
|
|
71
|
+
// The help key is `?` wherever it lets Shift be held (`/` or `?`): show what people press.
|
|
72
|
+
const shifted = punctuation && (held(chord.shift) || (chord.code === 'Slash' && chord.shift === 'any'));
|
|
73
|
+
parts.push(shifted ? punctuation : keyLabel(chord.code, layout));
|
|
64
74
|
return parts;
|
|
65
75
|
}
|
|
66
76
|
/** One line for a chord: `⌘⇧Z` on Apple keyboards, `Ctrl+Shift+Z` elsewhere. */
|
|
@@ -74,28 +84,44 @@ export function formatChord(keys, platform = shortcutPlatform(), layout) {
|
|
|
74
84
|
}
|
|
75
85
|
/**
|
|
76
86
|
* The key string a press makes, for someone setting a key: exactly the modifiers held, the command
|
|
77
|
-
* key of their platform written `Mod` (⌘ on Apple, Ctrl elsewhere) so the key works on both.
|
|
78
|
-
*
|
|
87
|
+
* key of their platform written `Mod` (⌘ on Apple, Ctrl elsewhere) so the key works on both. A letter
|
|
88
|
+
* or punctuation character typed is written as the key that types it on a US keyboard (`/` is
|
|
89
|
+
* `Slash` whichever keys typed it), the way the matcher reads it back. `null` while only modifiers
|
|
90
|
+
* are down, or for a press with no key place.
|
|
79
91
|
*/
|
|
80
92
|
export function chordFromEvent(event, platform = shortcutPlatform()) {
|
|
81
93
|
if (!event.code || isModifierCode(event.code))
|
|
82
94
|
return null;
|
|
95
|
+
const typed = typedKey(event.key);
|
|
96
|
+
const punctuation = typed?.shift !== undefined;
|
|
97
|
+
// A punctuation character carries its own Shift, and the AltGr or ⌥ that typed it is no modifier.
|
|
98
|
+
const altGr = punctuation && event.altKey && event.ctrlKey && !event.metaKey;
|
|
99
|
+
// Field by field: a browser's KeyboardEvent keeps them on its prototype, where a spread does not reach.
|
|
100
|
+
const press = punctuation
|
|
101
|
+
? {
|
|
102
|
+
// With ⌘ or Ctrl some browsers report the key unshifted: the Shift held is then part of the chord.
|
|
103
|
+
shiftKey: Boolean(typed.shift) || (event.shiftKey && !altGr && (event.ctrlKey || event.metaKey)),
|
|
104
|
+
altKey: false,
|
|
105
|
+
ctrlKey: altGr ? false : event.ctrlKey,
|
|
106
|
+
metaKey: event.metaKey,
|
|
107
|
+
}
|
|
108
|
+
: event;
|
|
83
109
|
const parts = [];
|
|
84
110
|
// ⌘ (Ctrl elsewhere) is written Mod so the key works on both — unless Ctrl and ⌘ are both held,
|
|
85
111
|
// which Mod cannot say: then both by name.
|
|
86
|
-
if (
|
|
112
|
+
if (press.ctrlKey && press.metaKey)
|
|
87
113
|
parts.push('Ctrl', 'Meta');
|
|
88
|
-
else if (platform === 'apple' ?
|
|
114
|
+
else if (platform === 'apple' ? press.metaKey : press.ctrlKey)
|
|
89
115
|
parts.push('Mod');
|
|
90
|
-
else if (
|
|
116
|
+
else if (press.ctrlKey)
|
|
91
117
|
parts.push('Ctrl');
|
|
92
|
-
else if (
|
|
118
|
+
else if (press.metaKey)
|
|
93
119
|
parts.push('Meta');
|
|
94
|
-
if (
|
|
120
|
+
if (press.altKey)
|
|
95
121
|
parts.push('Alt');
|
|
96
|
-
if (
|
|
122
|
+
if (press.shiftKey)
|
|
97
123
|
parts.push('Shift');
|
|
98
|
-
parts.push(event.code);
|
|
124
|
+
parts.push(typed?.code ?? event.code);
|
|
99
125
|
const keys = parts.join('+');
|
|
100
126
|
return parseChord(keys) ? keys : null;
|
|
101
127
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { parseChord, parseKeys, keysOverlap, chordMatches, chordsOverlap, isCommandChord, type Chord, type ModifierRule, type ModifierState, } from './chord.js';
|
|
1
|
+
export { parseChord, parseKeys, keysOverlap, chordMatch, chordMatches, chordsOverlap, typedKey, isCommandChord, type Chord, type ModifierRule, type ModifierState, type ChordMatch, type ChordPress, } from './chord.js';
|
|
2
2
|
export { matchShortcut, createShortcutMatcher, SEQUENCE_WAIT_MS, resolveBindings, findClashes, claimKeys, isKeyClaimed, isTypingTarget, type ShortcutAction, type ShortcutNames, type ShortcutBindings, type ShortcutOverrides, type ShortcutEvent, } from './match.js';
|
|
3
3
|
export { shortcutPlatform, keyLabel, chordParts, formatChord, chordFromEvent, mergedKeyParts, recordedKeys, type ShortcutPlatform, type KeyLayout, } from './format.js';
|
|
4
4
|
export { EMPTY_CATALOGUE, parseCatalogue, localName, localAppName, localPartName, shortcutApps, sectionActions, findSection, type ShortcutCatalogue, type ShortcutSection, type ShortcutNamed, type ShortcutSheetColumn, type ShortcutSheetRow, } from './catalogue.js';
|
package/dist/shortcuts/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Keyboard shortcuts, one system for every app: the reader of the shortcut catalogue (every app's
|
|
2
2
|
// actions and default keys, served by the backend) and of a person's own keys, and one matcher that
|
|
3
|
-
// says which action a key press is. Keys are named
|
|
4
|
-
// (`KeyboardEvent.code`)
|
|
5
|
-
|
|
3
|
+
// says which action a key press is. Keys are named as a US keyboard names them
|
|
4
|
+
// (`KeyboardEvent.code`) and matched by the character a press types, so they work on any keyboard.
|
|
5
|
+
// Framework-free: no React, no DOM beyond reading a key event.
|
|
6
|
+
export { parseChord, parseKeys, keysOverlap, chordMatch, chordMatches, chordsOverlap, typedKey, isCommandChord, } from './chord.js';
|
|
6
7
|
export { matchShortcut, createShortcutMatcher, SEQUENCE_WAIT_MS, resolveBindings, findClashes, claimKeys, isKeyClaimed, isTypingTarget, } from './match.js';
|
|
7
8
|
export { shortcutPlatform, keyLabel, chordParts, formatChord, chordFromEvent, mergedKeyParts, recordedKeys, } from './format.js';
|
|
8
9
|
export { EMPTY_CATALOGUE, parseCatalogue, localName, localAppName, localPartName, shortcutApps, sectionActions, findSection, } from './catalogue.js';
|
|
@@ -58,7 +58,9 @@ export declare function isKeyClaimed(event: Pick<ShortcutEvent, 'key' | 'metaKey
|
|
|
58
58
|
export declare function isTypingTarget(target: unknown): boolean;
|
|
59
59
|
/**
|
|
60
60
|
* The action this press is, or `null`. Not one while the person is typing in a field, nor a key a
|
|
61
|
-
* surface holds; a widget that reads characters keeps its characters but not the ⌘ commands.
|
|
61
|
+
* surface holds; a widget that reads characters keeps its characters but not the ⌘ commands. A key
|
|
62
|
+
* that matches by the character typed wins over one that only matches by the key's place, whatever
|
|
63
|
+
* their order (Shift+7 types `/` on a Spanish keyboard: the `/` action, not the `7` one).
|
|
62
64
|
*/
|
|
63
65
|
export declare function matchShortcut(event: ShortcutEvent, bindings: ShortcutBindings): string | null;
|
|
64
66
|
/** How long the second key of a sequence (`G`, then `H`) may follow the first. */
|
package/dist/shortcuts/match.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Which action a key press is. Each app keeps what its actions DO; this only answers which one a
|
|
2
2
|
// press names, so every app reads keys the same way: the modifiers, a field being typed in, and a
|
|
3
3
|
// surface that holds letters of its own are decided here, once.
|
|
4
|
-
import { chordMatches, isCommandChord, isModifierCode, keysOverlap, parseChord, parseKeys } from './chord.js';
|
|
4
|
+
import { chordMatch, chordMatches, isCommandChord, isModifierCode, keysOverlap, parseChord, parseKeys } from './chord.js';
|
|
5
5
|
/**
|
|
6
6
|
* Every action's keys: its defaults, or the person's override (unknown actions and keys that name no
|
|
7
7
|
* chord are dropped). Catalogue order is kept, so the earlier catalogue wins where two match one press.
|
|
@@ -81,23 +81,29 @@ function readsCharacters(target) {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* The action this press is, or `null`. Not one while the person is typing in a field, nor a key a
|
|
84
|
-
* surface holds; a widget that reads characters keeps its characters but not the ⌘ commands.
|
|
84
|
+
* surface holds; a widget that reads characters keeps its characters but not the ⌘ commands. A key
|
|
85
|
+
* that matches by the character typed wins over one that only matches by the key's place, whatever
|
|
86
|
+
* their order (Shift+7 types `/` on a Spanish keyboard: the `/` action, not the `7` one).
|
|
85
87
|
*/
|
|
86
88
|
export function matchShortcut(event, bindings) {
|
|
87
89
|
if (!event.code || isTypingTarget(event.target) || isKeyClaimed(event))
|
|
88
90
|
return null;
|
|
89
91
|
const characters = readsCharacters(event.target);
|
|
92
|
+
let byPlace = null;
|
|
90
93
|
for (const [actionId, keys] of bindings) {
|
|
91
94
|
for (const key of keys) {
|
|
92
95
|
const chord = parseChord(key);
|
|
93
|
-
|
|
96
|
+
const how = chord && chordMatch(chord, event);
|
|
97
|
+
if (!chord || !how)
|
|
94
98
|
continue;
|
|
95
|
-
if (
|
|
96
|
-
return null;
|
|
97
|
-
|
|
99
|
+
if (how === 'character')
|
|
100
|
+
return characters && !isCommandChord(chord) ? null : actionId;
|
|
101
|
+
byPlace ??= { id: actionId, command: isCommandChord(chord) };
|
|
98
102
|
}
|
|
99
103
|
}
|
|
100
|
-
|
|
104
|
+
if (!byPlace || (characters && !byPlace.command))
|
|
105
|
+
return null;
|
|
106
|
+
return byPlace.id;
|
|
101
107
|
}
|
|
102
108
|
// ---------------------------------------------------------------------------------------------
|
|
103
109
|
// Sequences
|
|
@@ -137,7 +143,7 @@ export function createShortcutMatcher(waitMs = SEQUENCE_WAIT_MS, now = Date.now)
|
|
|
137
143
|
return id;
|
|
138
144
|
}
|
|
139
145
|
if (known.some(([, [one]]) => chordMatches(one, event.code, event))) {
|
|
140
|
-
first = { code: event.code, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey, at: now() };
|
|
146
|
+
first = { code: event.code, key: event.key, shiftKey: event.shiftKey, altKey: event.altKey, ctrlKey: event.ctrlKey, metaKey: event.metaKey, at: now() };
|
|
141
147
|
return null;
|
|
142
148
|
}
|
|
143
149
|
return matchShortcut(event, bindings);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
import type { WorldSky } from './worldSkyFile.js';
|
|
3
|
+
/** The band's frame: `basis` maps band space (x toward the core, y the band's pole) to the scene. */
|
|
4
|
+
export interface BandFrame {
|
|
5
|
+
basis: THREE.Matrix4;
|
|
6
|
+
core: THREE.Vector3;
|
|
7
|
+
}
|
|
8
|
+
/** An equatorial direction in the scene frame (y = celestial north). */
|
|
9
|
+
export declare function equatorial(raDeg: number, decDeg: number, out?: THREE.Vector3): THREE.Vector3;
|
|
10
|
+
/** A seeded band, tilted `tiltDeg` from the equator, its core at a seeded longitude. */
|
|
11
|
+
export declare function seededBandFrame(seed: number, tiltDeg: number): BandFrame;
|
|
12
|
+
/**
|
|
13
|
+
* The Milky Way: the galactic plane (J2000 north pole and centre). From every catalogued host it
|
|
14
|
+
* is the same plane to well under a degree, so it holds for every world sky.
|
|
15
|
+
*/
|
|
16
|
+
export declare function galacticBandFrame(): BandFrame;
|
|
17
|
+
/**
|
|
18
|
+
* The star buffer, exactly `count` stars (the device's star budget). A world sky first: its
|
|
19
|
+
* catalogue stars copied as they are (brightest first, so a small budget keeps the brightest), then
|
|
20
|
+
* its stand-ins, then seeded faint fill; a seeded sky is `count` seeded stars. Stars gather into the
|
|
21
|
+
* band by `band`.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildStarGeometry(opts: {
|
|
24
|
+
seed: number;
|
|
25
|
+
count: number;
|
|
26
|
+
band: number;
|
|
27
|
+
frame: BandFrame;
|
|
28
|
+
world?: WorldSky | null;
|
|
29
|
+
}): THREE.BufferGeometry;
|
|
30
|
+
/** The constellation figures of a world sky, straight from the file. */
|
|
31
|
+
export declare function buildLineGeometry(world: WorldSky): THREE.BufferGeometry;
|