@glassly/miniapp 0.1.0-dev.99 → 0.2.8-dev.445
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 +218 -73
- package/dist/background/index.d.ts +14 -8
- package/dist/background/index.d.ts.map +1 -1
- package/dist/background/index.js +4 -0
- package/dist/background/index.js.map +1 -1
- package/dist/background/register.d.ts +1 -1
- package/dist/background/register.js +1 -1
- package/dist/display/index.d.ts +23 -0
- package/dist/display/index.d.ts.map +1 -0
- package/dist/display/index.js +17 -0
- package/dist/display/index.js.map +1 -0
- package/dist/display/measurer/TextMeasurer.d.ts +160 -0
- package/dist/display/measurer/TextMeasurer.d.ts.map +1 -0
- package/dist/display/measurer/TextMeasurer.js +272 -0
- package/dist/display/measurer/TextMeasurer.js.map +1 -0
- package/dist/display/measurer/index.d.ts +10 -0
- package/dist/display/measurer/index.d.ts.map +1 -0
- package/dist/display/measurer/index.js +11 -0
- package/dist/display/measurer/index.js.map +1 -0
- package/dist/display/measurer/script-detection.d.ts +53 -0
- package/dist/display/measurer/script-detection.d.ts.map +1 -0
- package/dist/display/measurer/script-detection.js +184 -0
- package/dist/display/measurer/script-detection.js.map +1 -0
- package/dist/display/pixelArt.d.ts +22 -0
- package/dist/display/pixelArt.d.ts.map +1 -0
- package/dist/display/pixelArt.js +126 -0
- package/dist/display/pixelArt.js.map +1 -0
- package/dist/display/profiles/g1.d.ts +33 -0
- package/dist/display/profiles/g1.d.ts.map +1 -0
- package/dist/display/profiles/g1.js +214 -0
- package/dist/display/profiles/g1.js.map +1 -0
- package/dist/display/profiles/g2.d.ts +13 -0
- package/dist/display/profiles/g2.d.ts.map +1 -0
- package/dist/display/profiles/g2.js +34 -0
- package/dist/display/profiles/g2.js.map +1 -0
- package/dist/display/profiles/index.d.ts +12 -0
- package/dist/display/profiles/index.d.ts.map +1 -0
- package/dist/display/profiles/index.js +15 -0
- package/dist/display/profiles/index.js.map +1 -0
- package/dist/display/profiles/nex.d.ts +10 -0
- package/dist/display/profiles/nex.d.ts.map +1 -0
- package/dist/display/profiles/nex.js +150 -0
- package/dist/display/profiles/nex.js.map +1 -0
- package/dist/display/profiles/types.d.ts +103 -0
- package/dist/display/profiles/types.d.ts.map +1 -0
- package/dist/display/profiles/types.js +2 -0
- package/dist/display/profiles/types.js.map +1 -0
- package/dist/display/profiles/z100.d.ts +33 -0
- package/dist/display/profiles/z100.d.ts.map +1 -0
- package/dist/display/profiles/z100.js +182 -0
- package/dist/display/profiles/z100.js.map +1 -0
- package/dist/display/scene/types.d.ts +292 -0
- package/dist/display/scene/types.d.ts.map +1 -0
- package/dist/display/scene/types.js +0 -0
- package/dist/display/scene/types.js.map +1 -0
- package/dist/display/wrapper/TextWrapper.d.ts +102 -0
- package/dist/display/wrapper/TextWrapper.d.ts.map +1 -0
- package/dist/display/wrapper/TextWrapper.js +469 -0
- package/dist/display/wrapper/TextWrapper.js.map +1 -0
- package/dist/display/wrapper/index.d.ts +12 -0
- package/dist/display/wrapper/index.d.ts.map +1 -0
- package/dist/display/wrapper/index.js +12 -0
- package/dist/display/wrapper/index.js.map +1 -0
- package/dist/display/wrapper/types.d.ts +75 -0
- package/dist/display/wrapper/types.d.ts.map +1 -0
- package/dist/display/wrapper/types.js +11 -0
- package/dist/display/wrapper/types.js.map +1 -0
- package/dist/globals.d.ts +5 -5
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +1 -1
- package/dist/globals.js.map +1 -1
- package/dist/index.d.ts +16 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knownApps.d.ts +26 -0
- package/dist/knownApps.d.ts.map +1 -0
- package/dist/knownApps.js +140 -0
- package/dist/knownApps.js.map +1 -0
- package/dist/mirror/canvas.d.ts +22 -0
- package/dist/mirror/canvas.d.ts.map +1 -0
- package/dist/mirror/canvas.js +311 -0
- package/dist/mirror/canvas.js.map +1 -0
- package/dist/mirror/geometry.d.ts +69 -0
- package/dist/mirror/geometry.d.ts.map +1 -0
- package/dist/mirror/geometry.js +75 -0
- package/dist/mirror/geometry.js.map +1 -0
- package/dist/mirror/index.d.ts +3 -0
- package/dist/mirror/index.d.ts.map +1 -0
- package/dist/mirror/index.js +2 -0
- package/dist/mirror/index.js.map +1 -0
- package/dist/mirror/types.d.ts +40 -0
- package/dist/mirror/types.d.ts.map +1 -0
- package/dist/mirror/types.js +2 -0
- package/dist/mirror/types.js.map +1 -0
- package/dist/modules/auth.d.ts +1 -1
- package/dist/modules/auth.d.ts.map +1 -1
- package/dist/modules/auth.js +2 -2
- package/dist/modules/auth.js.map +1 -1
- package/dist/modules/compass.d.ts +47 -0
- package/dist/modules/compass.d.ts.map +1 -0
- package/dist/modules/compass.js +39 -0
- package/dist/modules/compass.js.map +1 -0
- package/dist/modules/display.d.ts +189 -12
- package/dist/modules/display.d.ts.map +1 -1
- package/dist/modules/display.js +40 -3
- package/dist/modules/display.js.map +1 -1
- package/dist/modules/events.d.ts +30 -3
- package/dist/modules/events.d.ts.map +1 -1
- package/dist/modules/events.js.map +1 -1
- package/dist/modules/input.d.ts +34 -11
- package/dist/modules/input.d.ts.map +1 -1
- package/dist/modules/input.js +35 -11
- package/dist/modules/input.js.map +1 -1
- package/dist/modules/latest-render-scheduler.d.ts +19 -0
- package/dist/modules/latest-render-scheduler.d.ts.map +1 -0
- package/dist/modules/latest-render-scheduler.js +54 -0
- package/dist/modules/latest-render-scheduler.js.map +1 -0
- package/dist/modules/lightSensor.d.ts +44 -0
- package/dist/modules/lightSensor.d.ts.map +1 -0
- package/dist/modules/lightSensor.js +33 -0
- package/dist/modules/lightSensor.js.map +1 -0
- package/dist/modules/mic.d.ts +3 -2
- package/dist/modules/mic.d.ts.map +1 -1
- package/dist/modules/mic.js +3 -2
- package/dist/modules/mic.js.map +1 -1
- package/dist/modules/miniapps.d.ts +2 -0
- package/dist/modules/miniapps.d.ts.map +1 -1
- package/dist/modules/miniapps.js.map +1 -1
- package/dist/modules/mirror.d.ts +16 -0
- package/dist/modules/mirror.d.ts.map +1 -0
- package/dist/modules/mirror.js +40 -0
- package/dist/modules/mirror.js.map +1 -0
- package/dist/modules/mono-text-rows.d.ts +20 -0
- package/dist/modules/mono-text-rows.d.ts.map +1 -0
- package/dist/modules/mono-text-rows.js +26 -0
- package/dist/modules/mono-text-rows.js.map +1 -0
- package/dist/modules/navigation.d.ts.map +1 -1
- package/dist/modules/navigation.js +21 -14
- package/dist/modules/navigation.js.map +1 -1
- package/dist/modules/phone.d.ts +26 -1
- package/dist/modules/phone.d.ts.map +1 -1
- package/dist/modules/phone.js +28 -0
- package/dist/modules/phone.js.map +1 -1
- package/dist/modules/settings.d.ts +45 -0
- package/dist/modules/settings.d.ts.map +1 -0
- package/dist/modules/settings.js +54 -0
- package/dist/modules/settings.js.map +1 -0
- package/dist/modules/system.d.ts +21 -0
- package/dist/modules/system.d.ts.map +1 -1
- package/dist/modules/system.js +12 -0
- package/dist/modules/system.js.map +1 -1
- package/dist/modules/transcription.d.ts +3 -2
- package/dist/modules/transcription.d.ts.map +1 -1
- package/dist/modules/transcription.js +3 -2
- package/dist/modules/transcription.js.map +1 -1
- package/dist/protocol.d.ts +59 -5
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +60 -5
- package/dist/protocol.js.map +1 -1
- package/dist/react/MirrorCanvas.d.ts +10 -0
- package/dist/react/MirrorCanvas.d.ts.map +1 -0
- package/dist/react/MirrorCanvas.js +23 -0
- package/dist/react/MirrorCanvas.js.map +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +1 -0
- package/dist/react/index.js.map +1 -1
- package/dist/react/useColorScheme.d.ts +2 -3
- package/dist/react/useColorScheme.d.ts.map +1 -1
- package/dist/react/useColorScheme.js +15 -8
- package/dist/react/useColorScheme.js.map +1 -1
- package/dist/react/useRpc.js +2 -2
- package/dist/react/useRpc.js.map +1 -1
- package/dist/react/useSafeArea.d.ts.map +1 -1
- package/dist/react/useSafeArea.js +2 -2
- package/dist/react/useSafeArea.js.map +1 -1
- package/dist/session.d.ts +50 -4
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +35 -6
- package/dist/session.js.map +1 -1
- package/dist/transport/auto.d.ts +2 -2
- package/dist/transport/auto.js +2 -2
- package/dist/transport/dispatch.d.ts +1 -1
- package/dist/transport/dispatch.js +1 -1
- package/dist/transport/mock.d.ts +4 -4
- package/dist/transport/mock.d.ts.map +1 -1
- package/dist/transport/mock.js +31 -7
- package/dist/transport/mock.js.map +1 -1
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +1 -0
- package/dist/ui/index.js.map +1 -1
- package/package.json +15 -3
- package/src/background/index.ts +26 -4
- package/src/background/register.ts +1 -1
- package/src/display/index.ts +83 -0
- package/src/display/measurer/TextMeasurer.ts +346 -0
- package/src/display/measurer/index.ts +21 -0
- package/src/display/measurer/script-detection.ts +218 -0
- package/src/display/pixelArt.ts +141 -0
- package/src/display/profiles/g1.ts +237 -0
- package/src/display/profiles/g2.ts +39 -0
- package/src/display/profiles/index.ts +28 -0
- package/src/display/profiles/nex.ts +166 -0
- package/src/display/profiles/types.ts +132 -0
- package/src/display/profiles/z100.ts +198 -0
- package/src/display/scene/types.ts +335 -0
- package/src/display/wrapper/TextWrapper.ts +546 -0
- package/src/display/wrapper/index.ts +15 -0
- package/src/display/wrapper/types.ts +102 -0
- package/src/globals.ts +5 -5
- package/src/index.ts +34 -6
- package/src/knownApps.ts +170 -0
- package/src/mirror/canvas.ts +322 -0
- package/src/mirror/geometry.ts +128 -0
- package/src/mirror/index.ts +2 -0
- package/src/mirror/types.ts +38 -0
- package/src/modules/auth.ts +2 -2
- package/src/modules/compass.ts +69 -0
- package/src/modules/display.ts +219 -16
- package/src/modules/events.ts +26 -2
- package/src/modules/input.ts +44 -12
- package/src/modules/latest-render-scheduler.ts +55 -0
- package/src/modules/lightSensor.ts +61 -0
- package/src/modules/mic.ts +3 -2
- package/src/modules/miniapps.ts +2 -0
- package/src/modules/mirror.ts +42 -0
- package/src/modules/mono-text-rows.ts +30 -0
- package/src/modules/navigation.ts +20 -14
- package/src/modules/phone.ts +40 -1
- package/src/modules/settings.ts +64 -0
- package/src/modules/system.ts +30 -1
- package/src/modules/transcription.ts +3 -2
- package/src/protocol.ts +65 -5
- package/src/react/MirrorCanvas.tsx +35 -0
- package/src/react/index.ts +3 -0
- package/src/react/useColorScheme.ts +17 -8
- package/src/react/useRpc.ts +2 -2
- package/src/react/useSafeArea.ts +2 -2
- package/src/session.ts +78 -8
- package/src/transport/auto.ts +2 -2
- package/src/transport/dispatch.ts +1 -1
- package/src/transport/mock.ts +42 -11
- package/src/ui/index.ts +3 -0
- package/dist/modules/heading.d.ts +0 -16
- package/dist/modules/heading.d.ts.map +0 -1
- package/dist/modules/heading.js +0 -20
- package/dist/modules/heading.js.map +0 -1
- package/src/modules/heading.ts +0 -23
package/src/index.ts
CHANGED
|
@@ -16,14 +16,19 @@ import {installDevReloadListenerIfDevMode} from "./dev-reload"
|
|
|
16
16
|
installDevReloadListenerIfDevMode()
|
|
17
17
|
|
|
18
18
|
export {MiniappSession, NotConnectedError} from "./session"
|
|
19
|
+
export {monoTextRows} from "./modules/mono-text-rows"
|
|
20
|
+
export {LatestRenderScheduler} from "./modules/latest-render-scheduler"
|
|
19
21
|
export type {
|
|
20
22
|
AuthUpdatePayload,
|
|
21
23
|
ConnectAckPayload,
|
|
22
24
|
DisplayCapabilities,
|
|
23
25
|
GlassesCapabilities,
|
|
24
26
|
MiniappAuthState,
|
|
27
|
+
MiniappLaunchSource,
|
|
28
|
+
MiniappPreferences,
|
|
25
29
|
MiniappRequestError,
|
|
26
30
|
MiniappSessionOptions,
|
|
31
|
+
MiniappUnitSystem,
|
|
27
32
|
MiniappVisibility,
|
|
28
33
|
} from "./session"
|
|
29
34
|
export type {AuthFetchOptions, AuthModule} from "./modules/auth"
|
|
@@ -31,8 +36,8 @@ export type {AuthFetchOptions, AuthModule} from "./modules/auth"
|
|
|
31
36
|
export {makeRequestId, parseEnvelope, serializeEnvelope} from "./envelope"
|
|
32
37
|
export type {MiniappEnvelope} from "./envelope"
|
|
33
38
|
|
|
34
|
-
export {
|
|
35
|
-
export type {
|
|
39
|
+
export {getHostGlobals} from "./globals"
|
|
40
|
+
export type {HostGlobals, MiniappCapsuleMenuRect, MiniappColorScheme, MiniappSafeAreaInsets} from "./globals"
|
|
36
41
|
|
|
37
42
|
export {MiniappErrorCode, MiniappRequestType, MiniappResponseType, MiniappStreamType} from "./protocol"
|
|
38
43
|
export {CLOUD_STATUS_STREAM} from "./modules/cloud"
|
|
@@ -73,10 +78,18 @@ export type {
|
|
|
73
78
|
RenderBox,
|
|
74
79
|
RenderElement,
|
|
75
80
|
RenderOptions,
|
|
81
|
+
DisplayViewChange,
|
|
82
|
+
DisplayViewChangeReason,
|
|
83
|
+
ShowViewResult,
|
|
76
84
|
RenderRectStyle,
|
|
77
85
|
RenderResult,
|
|
78
86
|
RenderTextStyle,
|
|
79
87
|
ViewType,
|
|
88
|
+
RenderPoint,
|
|
89
|
+
RenderShapeStyle,
|
|
90
|
+
RenderStrokeStyle,
|
|
91
|
+
RenderEasing,
|
|
92
|
+
RenderTransition,
|
|
80
93
|
} from "./modules/display"
|
|
81
94
|
export type {
|
|
82
95
|
AccelData,
|
|
@@ -86,6 +99,7 @@ export type {
|
|
|
86
99
|
ConnectionData,
|
|
87
100
|
HeadingData,
|
|
88
101
|
HeadPositionData,
|
|
102
|
+
KeyboardEventData,
|
|
89
103
|
LocationData,
|
|
90
104
|
NotificationDismissedData,
|
|
91
105
|
PhoneNotificationData,
|
|
@@ -96,7 +110,7 @@ export type {
|
|
|
96
110
|
VadData,
|
|
97
111
|
WifiData,
|
|
98
112
|
} from "./modules/events"
|
|
99
|
-
export type {CalendarEvent, CalendarListOptions, CalendarListResult} from "./modules/phone"
|
|
113
|
+
export type {CalendarEvent, CalendarListOptions, CalendarListResult, NotificationAppInfo} from "./modules/phone"
|
|
100
114
|
export type {
|
|
101
115
|
PlayAudioOptions,
|
|
102
116
|
SpeakOptions,
|
|
@@ -139,6 +153,8 @@ export type {
|
|
|
139
153
|
DownloadResult,
|
|
140
154
|
ScanQrOptions,
|
|
141
155
|
ScanQrResult,
|
|
156
|
+
DisplayDiagnostic,
|
|
157
|
+
DisplayDiagnosticsResult,
|
|
142
158
|
} from "./modules/system"
|
|
143
159
|
export type {MiniappInfo, MiniappActionInfo, MiniappCompatibility, ListMiniappsOptions} from "./modules/miniapps"
|
|
144
160
|
export type {ActionContext, ActionDescriptor, ActionHandler, InvokeOptions} from "./modules/actions"
|
|
@@ -149,7 +165,6 @@ export type {DisplayManager} from "./modules/display"
|
|
|
149
165
|
export type {MiniappsModule} from "./modules/miniapps"
|
|
150
166
|
export type {ActionsModule} from "./modules/actions"
|
|
151
167
|
export type {GlassesModule} from "./modules/glasses"
|
|
152
|
-
export type {HeadingModule} from "./modules/heading"
|
|
153
168
|
export type {
|
|
154
169
|
AiModule,
|
|
155
170
|
AiProvider,
|
|
@@ -164,7 +179,7 @@ export type {
|
|
|
164
179
|
AiUsage,
|
|
165
180
|
} from "./modules/ai"
|
|
166
181
|
export type {ImuModule} from "./modules/imu"
|
|
167
|
-
export type {InputModule} from "./modules/input"
|
|
182
|
+
export type {InputModule, GestureClaimChange} from "./modules/input"
|
|
168
183
|
export type {KeysModule, ApiKeyProvider} from "./modules/keys"
|
|
169
184
|
export type {LocationModule} from "./modules/location"
|
|
170
185
|
export type {
|
|
@@ -207,10 +222,23 @@ export type {
|
|
|
207
222
|
TravelMode,
|
|
208
223
|
} from "./modules/navigation"
|
|
209
224
|
export type {PermissionsModule, PermissionErrorEvent} from "./modules/permissions"
|
|
210
|
-
export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule} from "./modules/phone"
|
|
225
|
+
export type {PhoneModule, PhoneNotificationsModule, PhoneCalendarModule, PhoneHeadingModule} from "./modules/phone"
|
|
226
|
+
export type {SettingsModule, SettingsChange} from "./modules/settings"
|
|
211
227
|
export type {TranscriptionModule, TranscriptionConfig, TranscriptionOptions} from "./modules/transcription"
|
|
212
228
|
export type {TranslationModule} from "./modules/translation"
|
|
213
229
|
export type {SpeakerModule} from "./modules/speaker"
|
|
214
230
|
|
|
215
231
|
// Permission types
|
|
216
232
|
export type {PermissionType, PermissionRecord} from "./session"
|
|
233
|
+
|
|
234
|
+
export type {LightSensorModule, LightSensorOptions, LightSensorData} from "./modules/lightSensor"
|
|
235
|
+
export type {
|
|
236
|
+
CompassModule,
|
|
237
|
+
CompassOptions,
|
|
238
|
+
CompassData,
|
|
239
|
+
CompassDiagnostics,
|
|
240
|
+
CompassCalibrationData,
|
|
241
|
+
} from "./modules/compass"
|
|
242
|
+
|
|
243
|
+
export type {MirrorModule} from "./modules/mirror"
|
|
244
|
+
export type {MirrorSnapshot, MirrorContent, MirrorElement} from "./mirror/types"
|
package/src/knownApps.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The apps the notification surfaces know by id: a display name for a bare
|
|
3
|
+
* bundle id or package name and, for the glasses card, which glyph stands in
|
|
4
|
+
* for the app's icon (iOS never exposes another app's icon, and one would not
|
|
5
|
+
* survive 24px of 4-bit gray anyway). The host seeds its notification app
|
|
6
|
+
* catalog from the iOS entries and fills in names on bare ids; miniapps map
|
|
7
|
+
* the glyph names onto their own pixel art.
|
|
8
|
+
*
|
|
9
|
+
* Ids keep their real case so the catalog can hand them back; lookups are
|
|
10
|
+
* case-insensitive.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type AppGlyph =
|
|
14
|
+
| "bubble"
|
|
15
|
+
| "envelope"
|
|
16
|
+
| "calendar"
|
|
17
|
+
| "phone"
|
|
18
|
+
| "camera"
|
|
19
|
+
| "note"
|
|
20
|
+
| "pin"
|
|
21
|
+
| "clock"
|
|
22
|
+
| "play"
|
|
23
|
+
| "cart"
|
|
24
|
+
| "car"
|
|
25
|
+
| "check"
|
|
26
|
+
| "photo"
|
|
27
|
+
| "heart"
|
|
28
|
+
| "dollar"
|
|
29
|
+
|
|
30
|
+
export interface KnownApp {
|
|
31
|
+
/** iOS bundle id or Android package name, real case. */
|
|
32
|
+
id: string
|
|
33
|
+
name: string
|
|
34
|
+
/** Absent: the generic message bubble. */
|
|
35
|
+
glyph?: AppGlyph
|
|
36
|
+
platform: "ios" | "android" | "both"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const ios = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "ios"})
|
|
40
|
+
const android = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "android"})
|
|
41
|
+
const both = (id: string, name: string, glyph?: AppGlyph): KnownApp => ({id, name, glyph, platform: "both"})
|
|
42
|
+
|
|
43
|
+
export const KNOWN_APPS: KnownApp[] = [
|
|
44
|
+
// Messaging
|
|
45
|
+
ios("com.apple.MobileSMS", "Messages", "bubble"),
|
|
46
|
+
android("com.google.android.apps.messaging", "Messages", "bubble"),
|
|
47
|
+
android("com.samsung.android.messaging", "Messages", "bubble"),
|
|
48
|
+
ios("net.whatsapp.WhatsApp", "WhatsApp", "bubble"),
|
|
49
|
+
android("com.whatsapp", "WhatsApp", "bubble"),
|
|
50
|
+
ios("ph.telegra.Telegraph", "Telegram", "bubble"),
|
|
51
|
+
android("org.telegram.messenger", "Telegram", "bubble"),
|
|
52
|
+
ios("org.whispersystems.signal", "Signal", "bubble"),
|
|
53
|
+
android("org.thoughtcrime.securesms", "Signal", "bubble"),
|
|
54
|
+
ios("com.facebook.Messenger", "Messenger", "bubble"),
|
|
55
|
+
android("com.facebook.orca", "Messenger", "bubble"),
|
|
56
|
+
ios("com.tinyspeck.chatlyio", "Slack", "bubble"),
|
|
57
|
+
android("com.Slack", "Slack", "bubble"),
|
|
58
|
+
ios("com.hammerandchisel.discord", "Discord", "bubble"),
|
|
59
|
+
android("com.discord", "Discord", "bubble"),
|
|
60
|
+
ios("com.microsoft.skype.teams", "Teams", "bubble"),
|
|
61
|
+
android("com.microsoft.teams", "Teams", "bubble"),
|
|
62
|
+
ios("com.tencent.xin", "WeChat", "bubble"),
|
|
63
|
+
android("com.tencent.mm", "WeChat", "bubble"),
|
|
64
|
+
ios("jp.naver.line", "LINE", "bubble"),
|
|
65
|
+
android("jp.naver.line.android", "LINE", "bubble"),
|
|
66
|
+
ios("com.toyopagroup.picaboo", "Snapchat", "bubble"),
|
|
67
|
+
android("com.snapchat.android", "Snapchat", "bubble"),
|
|
68
|
+
// Social
|
|
69
|
+
ios("com.facebook.Facebook", "Facebook"),
|
|
70
|
+
android("com.facebook.katana", "Facebook"),
|
|
71
|
+
ios("com.atebits.Tweetie2", "X"),
|
|
72
|
+
android("com.twitter.android", "X"),
|
|
73
|
+
ios("com.linkedin.LinkedIn", "LinkedIn"),
|
|
74
|
+
android("com.linkedin.android", "LinkedIn"),
|
|
75
|
+
// Mail
|
|
76
|
+
ios("com.apple.mobilemail", "Mail", "envelope"),
|
|
77
|
+
ios("com.google.Gmail", "Gmail", "envelope"),
|
|
78
|
+
android("com.google.android.gm", "Gmail", "envelope"),
|
|
79
|
+
both("com.microsoft.Office.Outlook", "Outlook", "envelope"),
|
|
80
|
+
ios("com.readdle.smartemail", "Spark", "envelope"),
|
|
81
|
+
android("com.readdle.spark", "Spark", "envelope"),
|
|
82
|
+
ios("ch.protonmail.protonmail", "Proton Mail", "envelope"),
|
|
83
|
+
android("ch.protonmail.android", "Proton Mail", "envelope"),
|
|
84
|
+
// Calendar
|
|
85
|
+
ios("com.apple.mobilecal", "Calendar", "calendar"),
|
|
86
|
+
ios("com.google.calendar", "Google Calendar", "calendar"),
|
|
87
|
+
android("com.google.android.calendar", "Google Calendar", "calendar"),
|
|
88
|
+
android("com.samsung.android.calendar", "Calendar", "calendar"),
|
|
89
|
+
// Phone
|
|
90
|
+
ios("com.apple.mobilephone", "Phone", "phone"),
|
|
91
|
+
ios("com.apple.facetime", "FaceTime", "phone"),
|
|
92
|
+
android("com.google.android.dialer", "Phone", "phone"),
|
|
93
|
+
android("com.samsung.android.dialer", "Phone", "phone"),
|
|
94
|
+
android("com.samsung.android.incallui", "Phone", "phone"),
|
|
95
|
+
// Tasks and reminders
|
|
96
|
+
ios("com.apple.reminders", "Reminders", "check"),
|
|
97
|
+
android("com.google.android.apps.tasks", "Google Tasks", "check"),
|
|
98
|
+
android("com.todoist", "Todoist", "check"),
|
|
99
|
+
ios("com.culturedcode.ThingsiPhone", "Things", "check"),
|
|
100
|
+
// Maps and rides
|
|
101
|
+
ios("com.apple.Maps", "Maps", "pin"),
|
|
102
|
+
ios("com.google.Maps", "Google Maps", "pin"),
|
|
103
|
+
android("com.google.android.apps.maps", "Google Maps", "pin"),
|
|
104
|
+
ios("com.waze.iphone", "Waze", "pin"),
|
|
105
|
+
android("com.waze", "Waze", "pin"),
|
|
106
|
+
ios("com.ubercab.UberClient", "Uber", "car"),
|
|
107
|
+
android("com.ubercab", "Uber", "car"),
|
|
108
|
+
ios("com.zimride.instant", "Lyft", "car"),
|
|
109
|
+
android("me.lyft.android", "Lyft", "car"),
|
|
110
|
+
// Shopping and delivery
|
|
111
|
+
ios("com.amazon.Amazon", "Amazon", "cart"),
|
|
112
|
+
android("com.amazon.mShop.android.shopping", "Amazon", "cart"),
|
|
113
|
+
ios("doordash.DoorDashConsumer", "DoorDash", "cart"),
|
|
114
|
+
android("com.dd.doordash", "DoorDash", "cart"),
|
|
115
|
+
ios("com.ubercab.UberEats", "Uber Eats", "cart"),
|
|
116
|
+
android("com.ubercab.eats", "Uber Eats", "cart"),
|
|
117
|
+
// Music and video
|
|
118
|
+
ios("com.apple.Music", "Music", "note"),
|
|
119
|
+
ios("com.spotify.client", "Spotify", "note"),
|
|
120
|
+
android("com.spotify.music", "Spotify", "note"),
|
|
121
|
+
ios("com.google.ios.youtubemusic", "YouTube Music", "note"),
|
|
122
|
+
android("com.google.android.apps.youtube.music", "YouTube Music", "note"),
|
|
123
|
+
ios("com.google.ios.youtube", "YouTube", "play"),
|
|
124
|
+
android("com.google.android.youtube", "YouTube", "play"),
|
|
125
|
+
ios("com.netflix.Netflix", "Netflix", "play"),
|
|
126
|
+
android("com.netflix.mediaclient", "Netflix", "play"),
|
|
127
|
+
ios("com.zhiliaoapp.musically", "TikTok", "play"),
|
|
128
|
+
android("com.ss.android.ugc.trill", "TikTok", "play"),
|
|
129
|
+
// Photos and camera
|
|
130
|
+
ios("com.apple.mobileslideshow", "Photos", "photo"),
|
|
131
|
+
android("com.google.android.apps.photos", "Google Photos", "photo"),
|
|
132
|
+
ios("com.apple.camera", "Camera", "camera"),
|
|
133
|
+
ios("com.burbn.instagram", "Instagram", "camera"),
|
|
134
|
+
android("com.instagram.android", "Instagram", "camera"),
|
|
135
|
+
// Clock and alarms
|
|
136
|
+
ios("com.apple.mobiletimer", "Clock", "clock"),
|
|
137
|
+
android("com.google.android.deskclock", "Clock", "clock"),
|
|
138
|
+
android("com.sec.android.app.clockpackage", "Clock", "clock"),
|
|
139
|
+
// Health and fitness
|
|
140
|
+
ios("com.apple.Health", "Health", "heart"),
|
|
141
|
+
ios("com.strava.stravaride", "Strava", "heart"),
|
|
142
|
+
android("com.strava", "Strava", "heart"),
|
|
143
|
+
android("com.google.android.apps.fitness", "Google Fit", "heart"),
|
|
144
|
+
ios("com.fitbit.FitbitMobile", "Fitbit", "heart"),
|
|
145
|
+
// Payments
|
|
146
|
+
ios("com.apple.Passbook", "Wallet", "dollar"),
|
|
147
|
+
ios("net.kortina.labs.Venmo", "Venmo", "dollar"),
|
|
148
|
+
ios("com.venmo.TouchFree", "Venmo", "dollar"),
|
|
149
|
+
android("com.venmo", "Venmo", "dollar"),
|
|
150
|
+
ios("com.yourcompany.PPClient", "PayPal", "dollar"),
|
|
151
|
+
android("com.paypal.android.p2pmobile", "PayPal", "dollar"),
|
|
152
|
+
both("com.squareup.cash", "Cash App", "dollar"),
|
|
153
|
+
both("io.heckel.ntfy", "Ntfy", "bubble"),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
const BY_ID: ReadonlyMap<string, KnownApp> = new Map(KNOWN_APPS.map((app) => [app.id.toLowerCase(), app]))
|
|
157
|
+
|
|
158
|
+
export function knownApp(packageName: string | undefined): KnownApp | null {
|
|
159
|
+
if (!packageName) return null
|
|
160
|
+
return BY_ID.get(packageName.trim().toLowerCase()) ?? null
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function appGlyph(packageName: string | undefined): AppGlyph | null {
|
|
164
|
+
return knownApp(packageName)?.glyph ?? null
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** The curated display name for a bare id, when the phone did not report one. */
|
|
168
|
+
export function knownAppName(packageName: string | undefined): string | null {
|
|
169
|
+
return knownApp(packageName)?.name ?? null
|
|
170
|
+
}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import {sceneGeometry, geometryEqual, tweenAt, type SceneGeometry} from "./geometry"
|
|
2
|
+
import type {MirrorElement, MirrorSnapshot} from "./types"
|
|
3
|
+
|
|
4
|
+
interface Pose {
|
|
5
|
+
element: MirrorElement
|
|
6
|
+
from: SceneGeometry
|
|
7
|
+
to: SceneGeometry
|
|
8
|
+
start: number
|
|
9
|
+
duration: number
|
|
10
|
+
rotationFrom: number
|
|
11
|
+
rotationTo: number
|
|
12
|
+
rotationStart: number
|
|
13
|
+
rotationDuration: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** CSS cubic-bezier timing, matching the glasses mirror's easing curves. */
|
|
17
|
+
export function mirrorEase(t: number, name?: string): number {
|
|
18
|
+
if (name === "linear" || t <= 0 || t >= 1) return t
|
|
19
|
+
const [x1, y1, x2, y2] =
|
|
20
|
+
name === "ease-in"
|
|
21
|
+
? [0.42, 0, 1, 1]
|
|
22
|
+
: name === "ease-out"
|
|
23
|
+
? [0, 0, 0.58, 1]
|
|
24
|
+
: name === "ease-in-out"
|
|
25
|
+
? [0.42, 0, 0.58, 1]
|
|
26
|
+
: [0.25, 0.1, 0.25, 1]
|
|
27
|
+
const curve = (u: number, a: number, b: number) => 3 * (1 - u) ** 2 * u * a + 3 * (1 - u) * u ** 2 * b + u ** 3
|
|
28
|
+
let low = 0,
|
|
29
|
+
high = 1
|
|
30
|
+
for (let i = 0; i < 16; i++) {
|
|
31
|
+
const mid = (low + high) / 2
|
|
32
|
+
if (curve(mid, x1, x2) < t) low = mid
|
|
33
|
+
else high = mid
|
|
34
|
+
}
|
|
35
|
+
return curve((low + high) / 2, y1, y2)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function poseAt(pose: Pose, now: number): SceneGeometry {
|
|
39
|
+
const geometry = tweenAt(
|
|
40
|
+
pose.from,
|
|
41
|
+
pose.to,
|
|
42
|
+
mirrorEase(pose.duration ? Math.min(1, (now - pose.start) / pose.duration) : 1, pose.element.transition?.easing),
|
|
43
|
+
)
|
|
44
|
+
const rotationT = mirrorEase(
|
|
45
|
+
pose.rotationDuration ? Math.min(1, (now - pose.rotationStart) / pose.rotationDuration) : 1,
|
|
46
|
+
pose.element.transition?.easing,
|
|
47
|
+
)
|
|
48
|
+
return {...geometry, rotation: pose.rotationFrom + (pose.rotationTo - pose.rotationFrom) * rotationT}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function canTween(previous: MirrorElement, next: MirrorElement): boolean {
|
|
52
|
+
return (
|
|
53
|
+
next.change !== "created" &&
|
|
54
|
+
previous.type === next.type &&
|
|
55
|
+
previous.text === next.text &&
|
|
56
|
+
previous.data === next.data &&
|
|
57
|
+
previous.d === next.d &&
|
|
58
|
+
previous.fillRule === next.fillRule &&
|
|
59
|
+
previous.style?.fill === next.style?.fill &&
|
|
60
|
+
previous.points?.length === next.points?.length &&
|
|
61
|
+
!(next.type === "image" && (previous.box.w !== next.box.w || previous.box.h !== next.box.h))
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Browser-only renderer. Feed snapshots from session.mirror through your UI channel. */
|
|
66
|
+
export class MirrorCanvasRenderer {
|
|
67
|
+
private frame: MirrorSnapshot | null = null
|
|
68
|
+
private poses = new Map<string, Pose>()
|
|
69
|
+
private images = new Map<string, HTMLCanvasElement | null>()
|
|
70
|
+
private pending = new Set<string>()
|
|
71
|
+
private raf: number | null = null
|
|
72
|
+
private disposed = false
|
|
73
|
+
private readonly ctx: CanvasRenderingContext2D
|
|
74
|
+
|
|
75
|
+
constructor(private readonly canvas: HTMLCanvasElement) {
|
|
76
|
+
const ctx = canvas.getContext("2d")
|
|
77
|
+
if (!ctx) throw new Error("Canvas 2D is unavailable")
|
|
78
|
+
this.ctx = ctx
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
update(frame: MirrorSnapshot): void {
|
|
82
|
+
if (this.disposed || (this.frame && frame.revision < this.frame.revision)) return
|
|
83
|
+
const previous = this.frame
|
|
84
|
+
const scene = frame.content.kind === "scene" ? frame.content : null
|
|
85
|
+
const prior = previous?.content.kind === "scene" ? previous.content : null
|
|
86
|
+
const reset =
|
|
87
|
+
!prior ||
|
|
88
|
+
!scene ||
|
|
89
|
+
previous?.model !== frame.model ||
|
|
90
|
+
previous.appId !== frame.appId ||
|
|
91
|
+
previous.view !== frame.view ||
|
|
92
|
+
prior.epoch !== scene.epoch ||
|
|
93
|
+
frame.revision > previous.revision + 1
|
|
94
|
+
const now = Date.now()
|
|
95
|
+
const poses = new Map<string, Pose>()
|
|
96
|
+
for (const element of scene?.elements ?? []) {
|
|
97
|
+
const to = sceneGeometry(element)
|
|
98
|
+
const prev = reset ? undefined : this.poses.get(element.id)
|
|
99
|
+
const animate = prev && canTween(prev.element, element)
|
|
100
|
+
const duration = element.transition?.durationMs ?? 0
|
|
101
|
+
const current = prev ? poseAt(prev, now) : to
|
|
102
|
+
const sameGeometry = animate && geometryEqual(prev.to, to)
|
|
103
|
+
const sameRotation =
|
|
104
|
+
animate && prev.to.rotation === to.rotation && prev.to.pivotX === to.pivotX && prev.to.pivotY === to.pivotY
|
|
105
|
+
poses.set(element.id, {
|
|
106
|
+
element,
|
|
107
|
+
to,
|
|
108
|
+
from: sameGeometry ? prev.from : animate ? current : to,
|
|
109
|
+
start: sameGeometry ? prev.start : now,
|
|
110
|
+
duration: sameGeometry ? prev.duration : animate ? Math.min(duration, 255 * 33) : 0,
|
|
111
|
+
rotationFrom: sameRotation ? prev.rotationFrom : animate ? current.rotation : to.rotation,
|
|
112
|
+
rotationTo: to.rotation,
|
|
113
|
+
rotationStart: sameRotation ? prev.rotationStart : now,
|
|
114
|
+
rotationDuration: sameRotation ? prev.rotationDuration : animate ? Math.min(duration, 65535) : 0,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
this.poses = poses
|
|
118
|
+
this.frame = frame
|
|
119
|
+
const used = new Set(scene?.elements.flatMap((e) => (e.data ? [e.data] : [])) ?? [])
|
|
120
|
+
if (frame.content.kind === "bitmap") used.add(frame.content.data)
|
|
121
|
+
for (const key of this.images.keys()) if (!used.has(key)) this.images.delete(key)
|
|
122
|
+
this.draw()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
dispose(): void {
|
|
126
|
+
this.disposed = true
|
|
127
|
+
if (this.raf !== null) cancelAnimationFrame(this.raf)
|
|
128
|
+
this.raf = null
|
|
129
|
+
this.poses.clear()
|
|
130
|
+
this.images.clear()
|
|
131
|
+
this.pending.clear()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private image(data: string): HTMLCanvasElement | null {
|
|
135
|
+
if (this.images.has(data)) return this.images.get(data) ?? null
|
|
136
|
+
if (this.pending.has(data)) return null
|
|
137
|
+
this.pending.add(data)
|
|
138
|
+
const image = new Image()
|
|
139
|
+
image.onload = () => {
|
|
140
|
+
this.pending.delete(data)
|
|
141
|
+
if (this.disposed) return
|
|
142
|
+
const content = this.frame?.content
|
|
143
|
+
if (
|
|
144
|
+
!(content?.kind === "bitmap" && content.data === data) &&
|
|
145
|
+
!(content?.kind === "scene" && content.elements.some((e) => e.data === data))
|
|
146
|
+
)
|
|
147
|
+
return
|
|
148
|
+
const surface = document.createElement("canvas")
|
|
149
|
+
surface.width = image.naturalWidth
|
|
150
|
+
surface.height = image.naturalHeight
|
|
151
|
+
const ctx = surface.getContext("2d", {willReadFrequently: true})
|
|
152
|
+
if (!ctx) return
|
|
153
|
+
ctx.drawImage(image, 0, 0)
|
|
154
|
+
const pixels = ctx.getImageData(0, 0, surface.width, surface.height)
|
|
155
|
+
for (let i = 0; i < pixels.data.length; i += 4) {
|
|
156
|
+
const alpha =
|
|
157
|
+
((0.2126 * pixels.data[i] + 0.7152 * pixels.data[i + 1] + 0.0722 * pixels.data[i + 2]) * pixels.data[i + 3]) /
|
|
158
|
+
255
|
|
159
|
+
pixels.data[i] = 0
|
|
160
|
+
pixels.data[i + 1] = 255
|
|
161
|
+
pixels.data[i + 2] = 136
|
|
162
|
+
pixels.data[i + 3] = alpha
|
|
163
|
+
}
|
|
164
|
+
ctx.putImageData(pixels, 0, 0)
|
|
165
|
+
this.images.set(data, surface)
|
|
166
|
+
this.draw()
|
|
167
|
+
}
|
|
168
|
+
image.onerror = () => {
|
|
169
|
+
this.pending.delete(data)
|
|
170
|
+
if (!this.disposed) this.images.set(data, null)
|
|
171
|
+
}
|
|
172
|
+
// Encoded images only: never fetch arbitrary URLs from scene data.
|
|
173
|
+
image.src = data.startsWith("data:image/")
|
|
174
|
+
? data
|
|
175
|
+
: `data:image/${data.startsWith("Qk") ? "bmp" : "png"};base64,${data}`
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private draw(): void {
|
|
180
|
+
if (this.disposed || !this.frame) return
|
|
181
|
+
if (this.raf !== null) cancelAnimationFrame(this.raf)
|
|
182
|
+
this.raf = null
|
|
183
|
+
const {canvas, content} = this.frame
|
|
184
|
+
const w = Math.max(1, Math.round(canvas?.width ?? 1)),
|
|
185
|
+
h = Math.max(1, Math.round(canvas?.height ?? 1))
|
|
186
|
+
if (this.canvas.width !== w) this.canvas.width = w
|
|
187
|
+
if (this.canvas.height !== h) this.canvas.height = h
|
|
188
|
+
const ctx = this.ctx
|
|
189
|
+
ctx.clearRect(0, 0, w, h)
|
|
190
|
+
const now = Date.now()
|
|
191
|
+
let animating = false
|
|
192
|
+
if (content.kind === "bitmap") {
|
|
193
|
+
const image = this.image(content.data)
|
|
194
|
+
if (image) {
|
|
195
|
+
const g1 = image.width === 576 && image.height === 135
|
|
196
|
+
const x = g1 ? 29 : 0,
|
|
197
|
+
y = g1 ? 21 : 0
|
|
198
|
+
ctx.drawImage(image, x, y, image.width - x, image.height - y, 0, 0, w, h)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
for (const pose of this.poses.values()) {
|
|
202
|
+
animating ||= now < pose.start + pose.duration || now < pose.rotationStart + pose.rotationDuration
|
|
203
|
+
this.paint(pose.element, poseAt(pose, now))
|
|
204
|
+
}
|
|
205
|
+
if (animating) this.raf = requestAnimationFrame(() => this.draw())
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private paint(element: MirrorElement, g: SceneGeometry): void {
|
|
209
|
+
const ctx = this.ctx
|
|
210
|
+
ctx.save()
|
|
211
|
+
try {
|
|
212
|
+
if (g.rotation) {
|
|
213
|
+
ctx.translate(g.pivotX, g.pivotY)
|
|
214
|
+
ctx.rotate((g.rotation * Math.PI) / 180)
|
|
215
|
+
ctx.translate(-g.pivotX, -g.pivotY)
|
|
216
|
+
}
|
|
217
|
+
if (element.type === "image") {
|
|
218
|
+
const image = element.data ? this.image(element.data) : null
|
|
219
|
+
ctx.globalAlpha = g.level
|
|
220
|
+
if (image) ctx.drawImage(image, g.x, g.y, g.w, g.h)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
if (element.type === "text") {
|
|
224
|
+
if (g.stroke > 0) {
|
|
225
|
+
const border = new Path2D()
|
|
226
|
+
border.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
|
|
227
|
+
this.path(border, false, g)
|
|
228
|
+
}
|
|
229
|
+
ctx.beginPath()
|
|
230
|
+
ctx.rect(g.x, g.y, g.w, g.h)
|
|
231
|
+
ctx.clip()
|
|
232
|
+
const mono = element.style?.font === "mono",
|
|
233
|
+
size = mono ? element.style?.fontSize || 20 : 20
|
|
234
|
+
const pitch = mono ? Math.round(size * 1.2) : 27
|
|
235
|
+
ctx.font = `${size}px ${mono ? "monospace" : "Arial, sans-serif"}`
|
|
236
|
+
const metrics = ctx.measureText("0"),
|
|
237
|
+
cell = Math.max(1, Math.round(metrics.width))
|
|
238
|
+
const ascent = metrics.fontBoundingBoxAscent || size * 0.8,
|
|
239
|
+
descent = metrics.fontBoundingBoxDescent || size * 0.2
|
|
240
|
+
const baseline = mono ? Math.round((pitch - ascent - descent) / 2 + ascent) : 22
|
|
241
|
+
ctx.fillStyle = `rgba(0,255,136,${g.level})`
|
|
242
|
+
;(element.text ?? "").split("\n").forEach((line, row) => {
|
|
243
|
+
if (row > 0 && (mono ? 2 + row * pitch >= g.h - 2 : (row + 1) * pitch > g.h)) return
|
|
244
|
+
let x = g.x + 2
|
|
245
|
+
for (const char of line) {
|
|
246
|
+
ctx.fillText(char, x, g.y + 2 + row * pitch + baseline)
|
|
247
|
+
x += mono ? cell : this.frame?.glyphWidths[char] ?? ctx.measureText(char).width
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
let path = new Path2D()
|
|
253
|
+
const pts = g.points,
|
|
254
|
+
cx = g.x + g.w / 2,
|
|
255
|
+
cy = g.y + g.h / 2,
|
|
256
|
+
r = Math.min(g.w, g.h) / 2
|
|
257
|
+
switch (element.type) {
|
|
258
|
+
case "rect":
|
|
259
|
+
path.roundRect(g.x, g.y, g.w, g.h, Math.max(0, Math.min(g.radius, g.w / 2, g.h / 2)))
|
|
260
|
+
break
|
|
261
|
+
case "circle":
|
|
262
|
+
path.arc(cx, cy, r, 0, Math.PI * 2)
|
|
263
|
+
break
|
|
264
|
+
case "arc":
|
|
265
|
+
case "pie": {
|
|
266
|
+
const sweep = g.endDeg - g.startDeg
|
|
267
|
+
if (!sweep) return
|
|
268
|
+
if (Math.abs(sweep) >= 360) path.arc(cx, cy, r, 0, Math.PI * 2)
|
|
269
|
+
else {
|
|
270
|
+
if (element.type === "pie") path.moveTo(cx, cy)
|
|
271
|
+
path.arc(cx, cy, r, (g.startDeg * Math.PI) / 180, (g.endDeg * Math.PI) / 180, sweep < 0)
|
|
272
|
+
if (element.type === "pie") path.closePath()
|
|
273
|
+
}
|
|
274
|
+
break
|
|
275
|
+
}
|
|
276
|
+
case "line":
|
|
277
|
+
case "triangle":
|
|
278
|
+
case "quad":
|
|
279
|
+
if (pts.length < 4) return
|
|
280
|
+
path.moveTo(pts[0], pts[1])
|
|
281
|
+
for (let i = 2; i < pts.length; i += 2) path.lineTo(pts[i], pts[i + 1])
|
|
282
|
+
if (element.type !== "line") path.closePath()
|
|
283
|
+
break
|
|
284
|
+
case "bezier":
|
|
285
|
+
path.moveTo(pts[0], pts[1])
|
|
286
|
+
if (pts.length === 6) path.quadraticCurveTo(pts[2], pts[3], pts[4], pts[5])
|
|
287
|
+
else if (pts.length === 8) path.bezierCurveTo(pts[2], pts[3], pts[4], pts[5], pts[6], pts[7])
|
|
288
|
+
else return
|
|
289
|
+
break
|
|
290
|
+
case "svg":
|
|
291
|
+
if (!element.d) return
|
|
292
|
+
path = new Path2D(element.d)
|
|
293
|
+
ctx.translate(g.x, g.y)
|
|
294
|
+
ctx.scale(g.pathScale, g.pathScale)
|
|
295
|
+
break
|
|
296
|
+
default:
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
const filled =
|
|
300
|
+
element.type === "svg" ||
|
|
301
|
+
element.type === "pie" ||
|
|
302
|
+
(["rect", "circle", "triangle", "quad"].includes(element.type) && element.style?.fill === true)
|
|
303
|
+
this.path(path, filled, g, element.fillRule === "evenodd" ? "evenodd" : "nonzero")
|
|
304
|
+
} finally {
|
|
305
|
+
ctx.restore()
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
private path(path: Path2D, fill: boolean, g: SceneGeometry, rule: CanvasFillRule = "nonzero"): void {
|
|
310
|
+
const ctx = this.ctx
|
|
311
|
+
ctx.lineWidth = Math.max(1, g.stroke)
|
|
312
|
+
// Replace the shape's covered pixels, including level-zero erasure.
|
|
313
|
+
ctx.globalCompositeOperation = "destination-out"
|
|
314
|
+
ctx.fillStyle = ctx.strokeStyle = "black"
|
|
315
|
+
if (fill) ctx.fill(path, rule)
|
|
316
|
+
else ctx.stroke(path)
|
|
317
|
+
ctx.globalCompositeOperation = "source-over"
|
|
318
|
+
ctx.fillStyle = ctx.strokeStyle = `rgba(0,255,136,${g.level})`
|
|
319
|
+
if (fill) ctx.fill(path, rule)
|
|
320
|
+
else ctx.stroke(path)
|
|
321
|
+
}
|
|
322
|
+
}
|