@motionstudies/web 0.1.0-alpha.3 → 0.1.0-alpha.30
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 +247 -2
- package/components/AirportBoard.d.ts +27 -0
- package/components/AirportBoard.js +28 -0
- package/components/AirportHeroCard.d.ts +60 -0
- package/components/AirportHeroCard.js +31 -0
- package/components/BusStopHeroCard.d.ts +37 -0
- package/components/BusStopHeroCard.js +14 -0
- package/components/DotMatrixBoard.d.ts +31 -0
- package/components/DotMatrixBoard.js +84 -0
- package/components/LazyButtonTooltips.d.ts +2 -0
- package/components/LazyButtonTooltips.js +8 -0
- package/components/NetworkVehicleHeroCard.d.ts +13 -0
- package/components/NetworkVehicleHeroCard.js +13 -0
- package/components/RailStationHeroCard.d.ts +46 -0
- package/components/RailStationHeroCard.js +20 -0
- package/components/SbbDepartureBoard.d.ts +26 -0
- package/components/SbbDepartureBoard.js +29 -0
- package/components/SplitFlapBoard.d.ts +25 -0
- package/components/SplitFlapBoard.js +58 -0
- package/components/StudyTimeline.d.ts +25 -0
- package/components/StudyTimeline.js +14 -0
- package/components/TimelineScrubber.d.ts +19 -0
- package/components/TimelineScrubber.js +26 -0
- package/components/TransportHeroBoard.d.ts +28 -0
- package/components/TransportHeroBoard.js +15 -0
- package/components/VehicleHeroCard.d.ts +51 -0
- package/components/VehicleHeroCard.js +21 -0
- package/components/airport-hero-card.css +68 -0
- package/components/dot-matrix-board.css +49 -0
- package/components/dot-matrix-font.d.ts +2 -0
- package/components/dot-matrix-font.js +61 -0
- package/components/sbb-departure-board.css +49 -0
- package/components/split-flap-board.css +45 -0
- package/components/study-timeline.css +8 -0
- package/components/timeline-scrubber.css +16 -0
- package/components/transport-hero-cards.css +39 -0
- package/components/vehicle-hero-card.css +64 -0
- package/fonts/DM-Mono-OFL.txt +93 -0
- package/fonts/Inter-OFL.txt +93 -0
- package/fonts/dm-mono-latin-300.woff2 +0 -0
- package/fonts/dm-mono-latin-400.woff2 +0 -0
- package/fonts/dm-mono-latin-500.woff2 +0 -0
- package/fonts/dm-mono-latin-ext-300.woff2 +0 -0
- package/fonts/dm-mono-latin-ext-400.woff2 +0 -0
- package/fonts/dm-mono-latin-ext-500.woff2 +0 -0
- package/fonts/inter-cyrillic-400-600.woff2 +0 -0
- package/fonts/inter-cyrillic-ext-400-600.woff2 +0 -0
- package/fonts/inter-greek-400-600.woff2 +0 -0
- package/fonts/inter-greek-ext-400-600.woff2 +0 -0
- package/fonts/inter-latin-400-600.woff2 +0 -0
- package/fonts/inter-latin-ext-400-600.woff2 +0 -0
- package/fonts/inter-vietnamese-400-600.woff2 +0 -0
- package/fonts.css +105 -0
- package/mount-motion-study.d.ts +7 -2
- package/mount-motion-study.js +2 -2
- package/package.json +94 -3
- package/study-layout.css +65 -0
- package/use-airport-feed.d.ts +9 -0
- package/use-airport-feed.js +72 -0
- package/use-browser-location.d.ts +13 -0
- package/use-browser-location.js +39 -0
- package/use-json-asset.d.ts +9 -0
- package/use-json-asset.js +45 -0
- package/use-now-clock.d.ts +11 -0
- package/use-now-clock.js +45 -0
- package/use-pattern-network-day.d.ts +17 -0
- package/use-pattern-network-day.js +41 -0
- package/use-progressive-air-day.d.ts +1 -0
- package/use-progressive-chunks.d.ts +3 -0
- package/use-progressive-chunks.js +13 -4
- package/use-progressive-network-day.d.ts +1 -0
- package/use-progressive-road-study.d.ts +1 -0
- package/use-transition-value.d.ts +12 -0
- package/use-transition-value.js +46 -0
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/fonts.css
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Generated by scripts/vendor-web-fonts.mjs; do not edit. Inter and DM Mono are licensed under the SIL Open Font License 1.1: see fonts/Inter-OFL.txt and fonts/DM-Mono-OFL.txt. */
|
|
2
|
+
@font-face {
|
|
3
|
+
font-family: 'DM Mono';
|
|
4
|
+
font-style: normal;
|
|
5
|
+
font-weight: 300;
|
|
6
|
+
font-display: swap;
|
|
7
|
+
src: url('./fonts/dm-mono-latin-ext-300.woff2') format('woff2');
|
|
8
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
9
|
+
}
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: 'DM Mono';
|
|
12
|
+
font-style: normal;
|
|
13
|
+
font-weight: 300;
|
|
14
|
+
font-display: swap;
|
|
15
|
+
src: url('./fonts/dm-mono-latin-300.woff2') format('woff2');
|
|
16
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
17
|
+
}
|
|
18
|
+
@font-face {
|
|
19
|
+
font-family: 'DM Mono';
|
|
20
|
+
font-style: normal;
|
|
21
|
+
font-weight: 400;
|
|
22
|
+
font-display: swap;
|
|
23
|
+
src: url('./fonts/dm-mono-latin-ext-400.woff2') format('woff2');
|
|
24
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
25
|
+
}
|
|
26
|
+
@font-face {
|
|
27
|
+
font-family: 'DM Mono';
|
|
28
|
+
font-style: normal;
|
|
29
|
+
font-weight: 400;
|
|
30
|
+
font-display: swap;
|
|
31
|
+
src: url('./fonts/dm-mono-latin-400.woff2') format('woff2');
|
|
32
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
33
|
+
}
|
|
34
|
+
@font-face {
|
|
35
|
+
font-family: 'DM Mono';
|
|
36
|
+
font-style: normal;
|
|
37
|
+
font-weight: 500;
|
|
38
|
+
font-display: swap;
|
|
39
|
+
src: url('./fonts/dm-mono-latin-ext-500.woff2') format('woff2');
|
|
40
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
41
|
+
}
|
|
42
|
+
@font-face {
|
|
43
|
+
font-family: 'DM Mono';
|
|
44
|
+
font-style: normal;
|
|
45
|
+
font-weight: 500;
|
|
46
|
+
font-display: swap;
|
|
47
|
+
src: url('./fonts/dm-mono-latin-500.woff2') format('woff2');
|
|
48
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
49
|
+
}
|
|
50
|
+
@font-face {
|
|
51
|
+
font-family: 'Inter';
|
|
52
|
+
font-style: normal;
|
|
53
|
+
font-weight: 400 600;
|
|
54
|
+
font-display: swap;
|
|
55
|
+
src: url('./fonts/inter-cyrillic-ext-400-600.woff2') format('woff2');
|
|
56
|
+
unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
|
|
57
|
+
}
|
|
58
|
+
@font-face {
|
|
59
|
+
font-family: 'Inter';
|
|
60
|
+
font-style: normal;
|
|
61
|
+
font-weight: 400 600;
|
|
62
|
+
font-display: swap;
|
|
63
|
+
src: url('./fonts/inter-cyrillic-400-600.woff2') format('woff2');
|
|
64
|
+
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
|
|
65
|
+
}
|
|
66
|
+
@font-face {
|
|
67
|
+
font-family: 'Inter';
|
|
68
|
+
font-style: normal;
|
|
69
|
+
font-weight: 400 600;
|
|
70
|
+
font-display: swap;
|
|
71
|
+
src: url('./fonts/inter-greek-ext-400-600.woff2') format('woff2');
|
|
72
|
+
unicode-range: U+1F00-1FFF;
|
|
73
|
+
}
|
|
74
|
+
@font-face {
|
|
75
|
+
font-family: 'Inter';
|
|
76
|
+
font-style: normal;
|
|
77
|
+
font-weight: 400 600;
|
|
78
|
+
font-display: swap;
|
|
79
|
+
src: url('./fonts/inter-greek-400-600.woff2') format('woff2');
|
|
80
|
+
unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
|
|
81
|
+
}
|
|
82
|
+
@font-face {
|
|
83
|
+
font-family: 'Inter';
|
|
84
|
+
font-style: normal;
|
|
85
|
+
font-weight: 400 600;
|
|
86
|
+
font-display: swap;
|
|
87
|
+
src: url('./fonts/inter-vietnamese-400-600.woff2') format('woff2');
|
|
88
|
+
unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
|
|
89
|
+
}
|
|
90
|
+
@font-face {
|
|
91
|
+
font-family: 'Inter';
|
|
92
|
+
font-style: normal;
|
|
93
|
+
font-weight: 400 600;
|
|
94
|
+
font-display: swap;
|
|
95
|
+
src: url('./fonts/inter-latin-ext-400-600.woff2') format('woff2');
|
|
96
|
+
unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
|
97
|
+
}
|
|
98
|
+
@font-face {
|
|
99
|
+
font-family: 'Inter';
|
|
100
|
+
font-style: normal;
|
|
101
|
+
font-weight: 400 600;
|
|
102
|
+
font-display: swap;
|
|
103
|
+
src: url('./fonts/inter-latin-400-600.woff2') format('woff2');
|
|
104
|
+
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
|
105
|
+
}
|
package/mount-motion-study.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
|
|
2
|
+
import type { VisualTheme } from '@motionstudies/core/theme';
|
|
3
|
+
/** Mounting needs presentation identity only; data catalogues belong to the application. */
|
|
4
|
+
export interface MotionStudyMountOptions {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly theme: VisualTheme;
|
|
7
|
+
}
|
|
8
|
+
export declare function mountMotionStudy(edition: MotionStudyMountOptions, application: ReactNode): void;
|
package/mount-motion-study.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StrictMode } from 'react';
|
|
3
3
|
import { createRoot } from 'react-dom/client';
|
|
4
|
-
import {
|
|
4
|
+
import { LazyButtonTooltips } from './components/LazyButtonTooltips.js';
|
|
5
5
|
import { applyVisualTheme } from './visual-theme.js';
|
|
6
6
|
export function mountMotionStudy(edition, application) {
|
|
7
7
|
const root = document.getElementById('root');
|
|
@@ -10,5 +10,5 @@ export function mountMotionStudy(edition, application) {
|
|
|
10
10
|
document.documentElement.classList.add('motion-study');
|
|
11
11
|
document.documentElement.dataset.edition = edition.id;
|
|
12
12
|
applyVisualTheme(edition.theme);
|
|
13
|
-
createRoot(root).render(_jsxs(StrictMode, { children: [application, _jsx(
|
|
13
|
+
createRoot(root).render(_jsxs(StrictMode, { children: [application, _jsx(LazyButtonTooltips, {})] }));
|
|
14
14
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motionstudies/web",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.30",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Browser controls and loaders for Motion Studies.",
|
|
@@ -20,6 +20,28 @@
|
|
|
20
20
|
"tag": "next"
|
|
21
21
|
},
|
|
22
22
|
"exports": {
|
|
23
|
+
"./use-now-clock": {
|
|
24
|
+
"types": "./use-now-clock.d.ts",
|
|
25
|
+
"import": "./use-now-clock.js",
|
|
26
|
+
"default": "./use-now-clock.js"
|
|
27
|
+
},
|
|
28
|
+
"./use-browser-location": {
|
|
29
|
+
"types": "./use-browser-location.d.ts",
|
|
30
|
+
"import": "./use-browser-location.js",
|
|
31
|
+
"default": "./use-browser-location.js"
|
|
32
|
+
},
|
|
33
|
+
"./components/SplitFlapBoard": {
|
|
34
|
+
"types": "./components/SplitFlapBoard.d.ts",
|
|
35
|
+
"import": "./components/SplitFlapBoard.js",
|
|
36
|
+
"default": "./components/SplitFlapBoard.js"
|
|
37
|
+
},
|
|
38
|
+
"./components/AirportHeroCard": {
|
|
39
|
+
"types": "./components/AirportHeroCard.d.ts",
|
|
40
|
+
"import": "./components/AirportHeroCard.js",
|
|
41
|
+
"default": "./components/AirportHeroCard.js"
|
|
42
|
+
},
|
|
43
|
+
"./split-flap-board.css": "./components/split-flap-board.css",
|
|
44
|
+
"./airport-hero-card.css": "./components/airport-hero-card.css",
|
|
23
45
|
"./components/MobilePicker": {
|
|
24
46
|
"types": "./components/MobilePicker.d.ts",
|
|
25
47
|
"import": "./components/MobilePicker.js",
|
|
@@ -65,6 +87,11 @@
|
|
|
65
87
|
"import": "./use-progressive-network-day.js",
|
|
66
88
|
"default": "./use-progressive-network-day.js"
|
|
67
89
|
},
|
|
90
|
+
"./use-pattern-network-day": {
|
|
91
|
+
"types": "./use-pattern-network-day.d.ts",
|
|
92
|
+
"import": "./use-pattern-network-day.js",
|
|
93
|
+
"default": "./use-pattern-network-day.js"
|
|
94
|
+
},
|
|
68
95
|
"./use-progressive-road-study": {
|
|
69
96
|
"types": "./use-progressive-road-study.d.ts",
|
|
70
97
|
"import": "./use-progressive-road-study.js",
|
|
@@ -77,7 +104,69 @@
|
|
|
77
104
|
"types": "./components/ButtonTooltips.d.ts",
|
|
78
105
|
"import": "./components/ButtonTooltips.js",
|
|
79
106
|
"default": "./components/ButtonTooltips.js"
|
|
80
|
-
}
|
|
107
|
+
},
|
|
108
|
+
"./use-airport-feed": {
|
|
109
|
+
"types": "./use-airport-feed.d.ts",
|
|
110
|
+
"import": "./use-airport-feed.js",
|
|
111
|
+
"default": "./use-airport-feed.js"
|
|
112
|
+
},
|
|
113
|
+
"./components/AirportBoard": {
|
|
114
|
+
"types": "./components/AirportBoard.d.ts",
|
|
115
|
+
"import": "./components/AirportBoard.js",
|
|
116
|
+
"default": "./components/AirportBoard.js"
|
|
117
|
+
},
|
|
118
|
+
"./components/DotMatrixBoard": {
|
|
119
|
+
"types": "./components/DotMatrixBoard.d.ts",
|
|
120
|
+
"import": "./components/DotMatrixBoard.js",
|
|
121
|
+
"default": "./components/DotMatrixBoard.js"
|
|
122
|
+
},
|
|
123
|
+
"./dot-matrix-board.css": "./components/dot-matrix-board.css",
|
|
124
|
+
"./components/BusStopHeroCard": {
|
|
125
|
+
"types": "./components/BusStopHeroCard.d.ts",
|
|
126
|
+
"import": "./components/BusStopHeroCard.js",
|
|
127
|
+
"default": "./components/BusStopHeroCard.js"
|
|
128
|
+
},
|
|
129
|
+
"./components/VehicleHeroCard": {
|
|
130
|
+
"types": "./components/VehicleHeroCard.d.ts",
|
|
131
|
+
"import": "./components/VehicleHeroCard.js",
|
|
132
|
+
"default": "./components/VehicleHeroCard.js"
|
|
133
|
+
},
|
|
134
|
+
"./vehicle-hero-card.css": "./components/vehicle-hero-card.css",
|
|
135
|
+
"./components/RailStationHeroCard": {
|
|
136
|
+
"types": "./components/RailStationHeroCard.d.ts",
|
|
137
|
+
"import": "./components/RailStationHeroCard.js",
|
|
138
|
+
"default": "./components/RailStationHeroCard.js"
|
|
139
|
+
},
|
|
140
|
+
"./transport-hero-cards.css": "./components/transport-hero-cards.css",
|
|
141
|
+
"./use-json-asset": {
|
|
142
|
+
"types": "./use-json-asset.d.ts",
|
|
143
|
+
"import": "./use-json-asset.js",
|
|
144
|
+
"default": "./use-json-asset.js"
|
|
145
|
+
},
|
|
146
|
+
"./use-transition-value": {
|
|
147
|
+
"types": "./use-transition-value.d.ts",
|
|
148
|
+
"import": "./use-transition-value.js",
|
|
149
|
+
"default": "./use-transition-value.js"
|
|
150
|
+
},
|
|
151
|
+
"./components/NetworkVehicleHeroCard": {
|
|
152
|
+
"types": "./components/NetworkVehicleHeroCard.d.ts",
|
|
153
|
+
"import": "./components/NetworkVehicleHeroCard.js",
|
|
154
|
+
"default": "./components/NetworkVehicleHeroCard.js"
|
|
155
|
+
},
|
|
156
|
+
"./study-layout.css": "./study-layout.css",
|
|
157
|
+
"./fonts.css": "./fonts.css",
|
|
158
|
+
"./components/StudyTimeline": {
|
|
159
|
+
"types": "./components/StudyTimeline.d.ts",
|
|
160
|
+
"import": "./components/StudyTimeline.js",
|
|
161
|
+
"default": "./components/StudyTimeline.js"
|
|
162
|
+
},
|
|
163
|
+
"./study-timeline.css": "./components/study-timeline.css",
|
|
164
|
+
"./components/TimelineScrubber": {
|
|
165
|
+
"types": "./components/TimelineScrubber.d.ts",
|
|
166
|
+
"import": "./components/TimelineScrubber.js",
|
|
167
|
+
"default": "./components/TimelineScrubber.js"
|
|
168
|
+
},
|
|
169
|
+
"./timeline-scrubber.css": "./components/timeline-scrubber.css"
|
|
81
170
|
},
|
|
82
171
|
"files": [
|
|
83
172
|
"**/*.js",
|
|
@@ -85,6 +174,8 @@
|
|
|
85
174
|
"**/*.d.ts",
|
|
86
175
|
"**/*.d.mts",
|
|
87
176
|
"**/*.css",
|
|
177
|
+
"**/*.woff2",
|
|
178
|
+
"**/*-OFL.txt",
|
|
88
179
|
"README.md",
|
|
89
180
|
"LICENSE"
|
|
90
181
|
],
|
|
@@ -92,7 +183,7 @@
|
|
|
92
183
|
"**/*.css"
|
|
93
184
|
],
|
|
94
185
|
"dependencies": {
|
|
95
|
-
"@motionstudies/core": "0.1.0-alpha.
|
|
186
|
+
"@motionstudies/core": "0.1.0-alpha.30"
|
|
96
187
|
},
|
|
97
188
|
"peerDependencies": {
|
|
98
189
|
"react": "^19.2.8",
|
package/study-layout.css
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/* Opt-in overlay layout. Clearances describe the edition's surrounding controls. */
|
|
2
|
+
.ms-study-layout {
|
|
3
|
+
--ms-layout-gap: 12px;
|
|
4
|
+
--ms-layout-gutter: clamp(16px, 3vw, 48px);
|
|
5
|
+
--ms-layout-top: 180px;
|
|
6
|
+
--ms-layout-bottom: 160px;
|
|
7
|
+
--ms-layout-end: 72px;
|
|
8
|
+
--ms-layer-panel: 6;
|
|
9
|
+
--ms-control-size: 36px;
|
|
10
|
+
--ms-control-radius: 2px;
|
|
11
|
+
--ms-control-border: var(--line, #c1ccff33);
|
|
12
|
+
--ms-control-surface: var(--panel, #070716ee);
|
|
13
|
+
--ms-control-ink: var(--ink, #f8f7ff);
|
|
14
|
+
--ms-control-accent: var(--cyan, #8dfaff);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.ms-study-layout .ms-study-panel {
|
|
18
|
+
--ms-panel-top: max(var(--ms-layout-top, 180px), env(safe-area-inset-top));
|
|
19
|
+
--ms-panel-bottom: max(var(--ms-layout-bottom, 160px), env(safe-area-inset-bottom));
|
|
20
|
+
--ms-panel-start: max(var(--ms-layout-gutter, 16px), env(safe-area-inset-left));
|
|
21
|
+
--ms-panel-end: max(var(--ms-layout-end, 72px), env(safe-area-inset-right));
|
|
22
|
+
position: absolute;
|
|
23
|
+
z-index: var(--ms-layer-panel, 6);
|
|
24
|
+
inset-block-start: var(--ms-panel-top);
|
|
25
|
+
inset-inline-start: var(--ms-panel-start);
|
|
26
|
+
box-sizing: border-box;
|
|
27
|
+
inline-size: min(var(--ms-panel-width, 560px), max(0px, calc(100% - var(--ms-panel-start) - var(--ms-panel-end))));
|
|
28
|
+
max-block-size: max(0px, calc(100% - var(--ms-panel-top) - var(--ms-panel-bottom)));
|
|
29
|
+
min-inline-size: 0;
|
|
30
|
+
overflow: auto;
|
|
31
|
+
overscroll-behavior: contain;
|
|
32
|
+
scroll-padding-block: var(--ms-layout-gap, 12px);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
[data-ms-chrome='hidden'] .ms-study-panel {
|
|
36
|
+
visibility: hidden;
|
|
37
|
+
pointer-events: none;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.ms-control {
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
min-inline-size: var(--ms-control-size, 36px);
|
|
43
|
+
min-block-size: var(--ms-control-size, 36px);
|
|
44
|
+
padding: 6px 10px;
|
|
45
|
+
border: 1px solid var(--ms-control-border, #c1ccff33);
|
|
46
|
+
border-radius: var(--ms-control-radius, 2px);
|
|
47
|
+
background: var(--ms-control-surface, #070716ee);
|
|
48
|
+
color: var(--ms-control-ink, #f8f7ff);
|
|
49
|
+
font: inherit;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
touch-action: manipulation;
|
|
52
|
+
}
|
|
53
|
+
.ms-control:focus-visible {
|
|
54
|
+
outline: 2px solid var(--ms-control-accent, #8dfaff);
|
|
55
|
+
outline-offset: 2px;
|
|
56
|
+
}
|
|
57
|
+
.ms-control[aria-pressed='true'] { border-color: var(--ms-control-accent, #8dfaff); }
|
|
58
|
+
.ms-control:disabled { opacity: .5; cursor: default; }
|
|
59
|
+
.ms-control-group { display: flex; flex-wrap: wrap; align-items: stretch; gap: 6px; }
|
|
60
|
+
.ms-control-strip { display: flex; max-inline-size: 100%; overflow-x: auto; overscroll-behavior-inline: contain; scroll-padding-inline: 4px; }
|
|
61
|
+
.ms-control-strip > * { flex-shrink: 0; }
|
|
62
|
+
.ms-panel-dismiss { position: sticky; inset-block-start: 0; float: inline-end; z-index: 2; margin: 0 0 8px 8px; }
|
|
63
|
+
@media (pointer: coarse) {
|
|
64
|
+
.ms-study-layout { --ms-control-size: 44px; }
|
|
65
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type AirportFeedResponse } from '@motionstudies/core/domain/live-airport';
|
|
2
|
+
export interface AirportFeedState {
|
|
3
|
+
readonly available: boolean;
|
|
4
|
+
readonly checking: boolean;
|
|
5
|
+
readonly loading: boolean;
|
|
6
|
+
readonly response?: AirportFeedResponse;
|
|
7
|
+
}
|
|
8
|
+
/** Fetch capabilities without paid work; poll boards only while Now is selected. */
|
|
9
|
+
export declare function useAirportFeed(baseUrl: string, edition: string, airport: string, enabled: boolean): AirportFeedState;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { isAirportFeedResponse } from '@motionstudies/core/domain/live-airport';
|
|
3
|
+
/** Fetch capabilities without paid work; poll boards only while Now is selected. */
|
|
4
|
+
export function useAirportFeed(baseUrl, edition, airport, enabled) {
|
|
5
|
+
const source = `${baseUrl.replace(/\/$/, '')}/api/airports/v1/${encodeURIComponent(edition)}`;
|
|
6
|
+
const identity = `${source}/${airport}/${enabled}`;
|
|
7
|
+
const empty = { available: false, checking: true, loading: false };
|
|
8
|
+
const [stored, setStored] = useState({ ...empty, identity });
|
|
9
|
+
const state = stored.identity === identity ? stored : empty;
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const controller = new AbortController();
|
|
12
|
+
let timer, failures = 0;
|
|
13
|
+
const update = (patch) => {
|
|
14
|
+
if (!controller.signal.aborted)
|
|
15
|
+
setStored((old) => ({ ...(old.identity === identity ? old : empty), ...patch, identity }));
|
|
16
|
+
};
|
|
17
|
+
update(empty);
|
|
18
|
+
const tick = async () => {
|
|
19
|
+
let delay = 60_000;
|
|
20
|
+
if (document.hidden) {
|
|
21
|
+
timer = window.setTimeout(() => void tick(), delay);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const options = { cache: 'no-store', signal: AbortSignal.any([controller.signal, AbortSignal.timeout(20_000)]) };
|
|
26
|
+
const capabilitiesResponse = await fetch(`${source}/capabilities`, options);
|
|
27
|
+
if (!capabilitiesResponse.ok)
|
|
28
|
+
throw new Error('Capabilities unavailable');
|
|
29
|
+
const capabilities = await capabilitiesResponse.json();
|
|
30
|
+
if (capabilities.version !== 1 || typeof capabilities.enabled !== 'boolean' || !Array.isArray(capabilities.airports))
|
|
31
|
+
throw new Error('Invalid capabilities');
|
|
32
|
+
const available = capabilities.enabled && capabilities.airports.includes(airport);
|
|
33
|
+
update({ available, checking: false });
|
|
34
|
+
if (!available) {
|
|
35
|
+
update({ loading: false, response: { version: 1, status: 'disabled', reason: 'disabled', retryAfterSeconds: 60 } });
|
|
36
|
+
}
|
|
37
|
+
else if (enabled) {
|
|
38
|
+
update({ loading: true });
|
|
39
|
+
const r = await fetch(`${source}/${encodeURIComponent(airport)}/board`, options);
|
|
40
|
+
const response = await r.json();
|
|
41
|
+
if (!isAirportFeedResponse(response))
|
|
42
|
+
throw new Error('Invalid board');
|
|
43
|
+
if (response.snapshot && (response.snapshot.airport.iata !== airport || response.snapshot.fetchedAt > Date.now() / 1000 + 60))
|
|
44
|
+
throw new Error('Mismatched board');
|
|
45
|
+
update({ response, loading: false, available: response.status !== 'disabled' });
|
|
46
|
+
// Check capabilities every minute even when the upstream circuit is paused.
|
|
47
|
+
}
|
|
48
|
+
failures = 0;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
failures += 1;
|
|
52
|
+
delay = Math.min(300_000, 30_000 * 2 ** Math.min(failures, 4));
|
|
53
|
+
if (!controller.signal.aborted)
|
|
54
|
+
setStored((old) => {
|
|
55
|
+
const snapshot = old.identity === identity ? old.response?.snapshot : undefined;
|
|
56
|
+
return { identity, available: old.identity === identity && old.available, checking: false, loading: false,
|
|
57
|
+
response: { version: 1, status: snapshot && snapshot.expiresAt > Date.now() / 1000 ? 'stale' : 'unavailable', reason: 'provider', retryAfterSeconds: delay / 1000,
|
|
58
|
+
...(snapshot && snapshot.expiresAt > Date.now() / 1000 ? { snapshot } : {}) } };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
if (!controller.signal.aborted)
|
|
63
|
+
timer = window.setTimeout(() => void tick(), delay);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
void tick();
|
|
67
|
+
return () => { controller.abort(); window.clearTimeout(timer); };
|
|
68
|
+
// empty is deliberately reconstructed only when the source effect restarts.
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [identity, source, airport, enabled]);
|
|
71
|
+
return state;
|
|
72
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BrowserLocation {
|
|
2
|
+
readonly longitude: number;
|
|
3
|
+
readonly latitude: number;
|
|
4
|
+
readonly accuracy: number;
|
|
5
|
+
}
|
|
6
|
+
export type BrowserLocationStatus = 'idle' | 'locating' | 'ready' | 'denied' | 'timeout' | 'unavailable';
|
|
7
|
+
/** One explicit request, kept in memory. Clearing or unmounting ignores late replies. */
|
|
8
|
+
export declare function useBrowserLocation(): {
|
|
9
|
+
location: BrowserLocation | undefined;
|
|
10
|
+
status: BrowserLocationStatus;
|
|
11
|
+
locate: () => void;
|
|
12
|
+
clear: () => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
2
|
+
/** One explicit request, kept in memory. Clearing or unmounting ignores late replies. */
|
|
3
|
+
export function useBrowserLocation() {
|
|
4
|
+
const [location, setLocation] = useState();
|
|
5
|
+
const [status, setStatus] = useState('idle');
|
|
6
|
+
const request = useRef(0);
|
|
7
|
+
useEffect(() => () => { request.current += 1; }, []);
|
|
8
|
+
const clear = useCallback(() => {
|
|
9
|
+
request.current += 1;
|
|
10
|
+
setLocation(undefined);
|
|
11
|
+
setStatus('idle');
|
|
12
|
+
}, []);
|
|
13
|
+
const locate = useCallback(() => {
|
|
14
|
+
const id = ++request.current;
|
|
15
|
+
setLocation(undefined);
|
|
16
|
+
if (!navigator.geolocation) {
|
|
17
|
+
setStatus('unavailable');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
setStatus('locating');
|
|
21
|
+
navigator.geolocation.getCurrentPosition(({ coords }) => {
|
|
22
|
+
if (request.current !== id)
|
|
23
|
+
return;
|
|
24
|
+
if (!Number.isFinite(coords.longitude) || !Number.isFinite(coords.latitude) ||
|
|
25
|
+
Math.abs(coords.longitude) > 180 || Math.abs(coords.latitude) > 90 ||
|
|
26
|
+
!Number.isFinite(coords.accuracy) || coords.accuracy < 0) {
|
|
27
|
+
setStatus('unavailable');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
setLocation({ longitude: coords.longitude, latitude: coords.latitude, accuracy: coords.accuracy });
|
|
31
|
+
setStatus('ready');
|
|
32
|
+
}, (error) => {
|
|
33
|
+
if (request.current !== id)
|
|
34
|
+
return;
|
|
35
|
+
setStatus(error.code === 1 ? 'denied' : error.code === 3 ? 'timeout' : 'unavailable');
|
|
36
|
+
}, { enableHighAccuracy: false, maximumAge: 0, timeout: 10_000 });
|
|
37
|
+
}, []);
|
|
38
|
+
return { location, status, locate, clear };
|
|
39
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** A single optional asset, cached until its URL/parser changes or retry is called.
|
|
2
|
+
* Keep parse stable. Source compatibility and schema checks belong in that parser. */
|
|
3
|
+
export declare function useJsonAsset<T>(url: string | undefined, enabled?: boolean, parse?: (value: unknown) => T, optional?: boolean): {
|
|
4
|
+
data: T | undefined;
|
|
5
|
+
error: boolean;
|
|
6
|
+
unavailable: boolean;
|
|
7
|
+
loading: boolean;
|
|
8
|
+
retry: () => void;
|
|
9
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
const identity = (value) => value;
|
|
3
|
+
/** A single optional asset, cached until its URL/parser changes or retry is called.
|
|
4
|
+
* Keep parse stable. Source compatibility and schema checks belong in that parser. */
|
|
5
|
+
export function useJsonAsset(url, enabled = true, parse = identity, optional = false) {
|
|
6
|
+
const [attempt, setAttempt] = useState(0);
|
|
7
|
+
const [stored, setStored] = useState({ url, parse, attempt });
|
|
8
|
+
const state = stored.url === url && stored.parse === parse && stored.attempt === attempt
|
|
9
|
+
? stored : { url, parse, attempt };
|
|
10
|
+
if (stored !== state)
|
|
11
|
+
setStored(state);
|
|
12
|
+
const { data, error, unavailable } = state;
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
if (!enabled || !url || data !== undefined || unavailable)
|
|
15
|
+
return;
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const save = (result) => {
|
|
18
|
+
if (!controller.signal.aborted)
|
|
19
|
+
setStored(current => current.url === url && current.parse === parse && current.attempt === attempt
|
|
20
|
+
? { ...current, ...result } : current);
|
|
21
|
+
};
|
|
22
|
+
void (async () => {
|
|
23
|
+
try {
|
|
24
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
25
|
+
if (optional && response.status === 404) {
|
|
26
|
+
save({ unavailable: true, error: false });
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (!response.ok)
|
|
30
|
+
throw new Error(`Asset returned ${response.status}`);
|
|
31
|
+
const next = parse(await response.json());
|
|
32
|
+
if (next === undefined)
|
|
33
|
+
throw new Error('Asset parser returned no data');
|
|
34
|
+
save({ data: next, error: false });
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
save({ error: true });
|
|
38
|
+
}
|
|
39
|
+
})();
|
|
40
|
+
return () => controller.abort();
|
|
41
|
+
}, [url, enabled, parse, optional, attempt, data, unavailable]);
|
|
42
|
+
const retry = useCallback(() => setAttempt(value => value + 1), []);
|
|
43
|
+
return { data, error: Boolean(error), unavailable: Boolean(unavailable),
|
|
44
|
+
loading: Boolean(enabled && url && data === undefined && !error && !unavailable), retry };
|
|
45
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** The consumer maps an instant into its own service date, timezone and data window.
|
|
2
|
+
* The consumer may explicitly choose a representative timetable for the present.
|
|
3
|
+
* Return null when its data cannot meaningfully represent that instant.
|
|
4
|
+
*/
|
|
5
|
+
export declare function useNowClock(resolveTime: (instant: Date) => number | null): {
|
|
6
|
+
active: boolean;
|
|
7
|
+
time: number | null;
|
|
8
|
+
unavailable: boolean;
|
|
9
|
+
start: () => void;
|
|
10
|
+
stop: () => void;
|
|
11
|
+
};
|
package/use-now-clock.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
|
|
2
|
+
/** The consumer maps an instant into its own service date, timezone and data window.
|
|
3
|
+
* The consumer may explicitly choose a representative timetable for the present.
|
|
4
|
+
* Return null when its data cannot meaningfully represent that instant.
|
|
5
|
+
*/
|
|
6
|
+
export function useNowClock(resolveTime) {
|
|
7
|
+
const [active, setActive] = useState(false);
|
|
8
|
+
const [time, setTime] = useState(null);
|
|
9
|
+
const [unavailable, setUnavailable] = useState(false);
|
|
10
|
+
const running = useRef(false);
|
|
11
|
+
const resolver = useRef(resolveTime);
|
|
12
|
+
useLayoutEffect(() => { resolver.current = resolveTime; }, [resolveTime]);
|
|
13
|
+
const update = useCallback(() => {
|
|
14
|
+
const next = resolver.current(new Date());
|
|
15
|
+
if (next === null || !Number.isFinite(next)) {
|
|
16
|
+
running.current = false;
|
|
17
|
+
setActive(false);
|
|
18
|
+
setUnavailable(true);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
setTime(next);
|
|
22
|
+
return true;
|
|
23
|
+
}, []);
|
|
24
|
+
const start = useCallback(() => {
|
|
25
|
+
setUnavailable(false);
|
|
26
|
+
running.current = update();
|
|
27
|
+
setActive(running.current);
|
|
28
|
+
}, [update]);
|
|
29
|
+
const stop = useCallback(() => {
|
|
30
|
+
running.current = false;
|
|
31
|
+
setActive(false);
|
|
32
|
+
}, []);
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (!active)
|
|
35
|
+
return;
|
|
36
|
+
let frame;
|
|
37
|
+
const tick = () => {
|
|
38
|
+
if (running.current && update())
|
|
39
|
+
frame = requestAnimationFrame(tick);
|
|
40
|
+
};
|
|
41
|
+
frame = requestAnimationFrame(tick);
|
|
42
|
+
return () => cancelAnimationFrame(frame);
|
|
43
|
+
}, [active, update]);
|
|
44
|
+
return { active, time, unavailable, start, stop };
|
|
45
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { PatternChunkOptions } from '@motionstudies/core/domain/network-patterns';
|
|
2
|
+
import type { NetworkDayChunk, NetworkDayChunkDescriptor, NetworkDayManifest } from '@motionstudies/core/domain/network';
|
|
3
|
+
import type { DataUrlResolver } from './data-url.ts';
|
|
4
|
+
export interface PatternNetworkManifest extends NetworkDayManifest {
|
|
5
|
+
readonly format: string;
|
|
6
|
+
}
|
|
7
|
+
export declare function verifiedPatternNetworkChunk(response: Response, descriptor: NetworkDayChunkDescriptor, options?: PatternChunkOptions): Promise<NetworkDayChunk>;
|
|
8
|
+
/** Fetch and decode only the current and adjacent chunks; keep the manifest independent. */
|
|
9
|
+
export declare function usePatternNetworkDay(manifestFile: string, active: boolean, time: number, resolveAssetUrl: DataUrlResolver, options?: PatternChunkOptions): {
|
|
10
|
+
manifest: PatternNetworkManifest | undefined;
|
|
11
|
+
chunkReady: boolean;
|
|
12
|
+
loading: boolean;
|
|
13
|
+
unavailable: boolean;
|
|
14
|
+
error: boolean;
|
|
15
|
+
retry: () => void;
|
|
16
|
+
network: import("@motionstudies/core/domain/network").NetworkSnapshot | undefined;
|
|
17
|
+
};
|