fauna-api 0.0.0-stage → 1.0.676
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/dist/brands.d.ts +2 -0
- package/dist/brands.js +2 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +121 -0
- package/dist/src/ant/chat.d.ts +130 -0
- package/dist/src/ant/chat.js +467 -0
- package/dist/src/ant/format.d.ts +40 -0
- package/dist/src/ant/format.js +143 -0
- package/dist/src/ant/graph.d.ts +130 -0
- package/dist/src/ant/graph.js +449 -0
- package/dist/src/ant/index.d.ts +16 -0
- package/dist/src/ant/index.js +61 -0
- package/dist/src/ant/persona.d.ts +151 -0
- package/dist/src/ant/persona.js +136 -0
- package/dist/src/ant/timeline.d.ts +283 -0
- package/dist/src/ant/timeline.js +1025 -0
- package/dist/src/authentification.d.ts +45 -0
- package/dist/src/authentification.js +194 -0
- package/dist/src/billing.d.ts +135 -0
- package/dist/src/billing.js +128 -0
- package/dist/src/calendar.d.ts +83 -0
- package/dist/src/calendar.js +133 -0
- package/dist/src/chats.d.ts +113 -0
- package/dist/src/chats.js +100 -0
- package/dist/src/cicada/contacts.d.ts +31 -0
- package/dist/src/cicada/contacts.js +28 -0
- package/dist/src/cicada/conversations.d.ts +73 -0
- package/dist/src/cicada/conversations.js +236 -0
- package/dist/src/cicada/inboxes.d.ts +48 -0
- package/dist/src/cicada/inboxes.js +82 -0
- package/dist/src/cicada/index.d.ts +16 -0
- package/dist/src/cicada/index.js +61 -0
- package/dist/src/cicada/phone.d.ts +48 -0
- package/dist/src/cicada/phone.js +306 -0
- package/dist/src/drive.d.ts +80 -0
- package/dist/src/drive.js +472 -0
- package/dist/src/extensions/index.d.ts +4 -0
- package/dist/src/extensions/index.js +5 -0
- package/dist/src/extensions/web/index.d.ts +6 -0
- package/dist/src/extensions/web/index.js +7 -0
- package/dist/src/extensions/web/session.d.ts +106 -0
- package/dist/src/extensions/web/session.js +469 -0
- package/dist/src/extensions/web/tools.d.ts +13 -0
- package/dist/src/extensions/web/tools.js +72 -0
- package/dist/src/friends.d.ts +39 -0
- package/dist/src/friends.js +119 -0
- package/dist/src/groups.d.ts +82 -0
- package/dist/src/groups.js +269 -0
- package/dist/src/inbox.d.ts +11 -0
- package/dist/src/inbox.js +35 -0
- package/dist/src/integrations.d.ts +52 -0
- package/dist/src/integrations.js +205 -0
- package/dist/src/magpie/explorer.d.ts +14 -0
- package/dist/src/magpie/explorer.js +36 -0
- package/dist/src/magpie/index.d.ts +16 -0
- package/dist/src/magpie/index.js +61 -0
- package/dist/src/magpie/portfolio.d.ts +52 -0
- package/dist/src/magpie/portfolio.js +179 -0
- package/dist/src/magpie/quotes.d.ts +35 -0
- package/dist/src/magpie/quotes.js +47 -0
- package/dist/src/magpie/room.d.ts +61 -0
- package/dist/src/magpie/room.js +238 -0
- package/dist/src/maps.d.ts +159 -0
- package/dist/src/maps.js +171 -0
- package/dist/src/marmot/browser.d.ts +139 -0
- package/dist/src/marmot/browser.js +389 -0
- package/dist/src/marmot/index.d.ts +20 -0
- package/dist/src/marmot/index.js +65 -0
- package/dist/src/marmot/media.d.ts +56 -0
- package/dist/src/marmot/media.js +265 -0
- package/dist/src/marmot/research.d.ts +337 -0
- package/dist/src/marmot/research.js +1138 -0
- package/dist/src/marmot/scraper.d.ts +59 -0
- package/dist/src/marmot/scraper.js +94 -0
- package/dist/src/marmot/web.d.ts +402 -0
- package/dist/src/marmot/web.js +2586 -0
- package/dist/src/marmot/workspace.d.ts +81 -0
- package/dist/src/marmot/workspace.js +263 -0
- package/dist/src/parrot/agent.d.ts +53 -0
- package/dist/src/parrot/agent.js +93 -0
- package/dist/src/parrot/chat.d.ts +84 -0
- package/dist/src/parrot/chat.js +237 -0
- package/dist/src/parrot/format.d.ts +128 -0
- package/dist/src/parrot/format.js +639 -0
- package/dist/src/parrot/generative.d.ts +261 -0
- package/dist/src/parrot/generative.js +185 -0
- package/dist/src/parrot/index.d.ts +22 -0
- package/dist/src/parrot/index.js +67 -0
- package/dist/src/parrot/room.d.ts +225 -0
- package/dist/src/parrot/room.js +466 -0
- package/dist/src/parrot/routing.d.ts +90 -0
- package/dist/src/parrot/routing.js +78 -0
- package/dist/src/parrot/share.d.ts +126 -0
- package/dist/src/parrot/share.js +261 -0
- package/dist/src/parrot/socket.d.ts +41 -0
- package/dist/src/parrot/socket.js +478 -0
- package/dist/src/payments.d.ts +73 -0
- package/dist/src/payments.js +107 -0
- package/dist/src/private/cards.css +75 -0
- package/dist/src/rooms.d.ts +76 -0
- package/dist/src/rooms.js +168 -0
- package/dist/src/shared/schema.d.ts +24 -0
- package/dist/src/shared/schema.js +1 -0
- package/dist/src/shared/server.d.ts +65 -0
- package/dist/src/shared/server.js +302 -0
- package/dist/src/termite/environment.d.ts +110 -0
- package/dist/src/termite/environment.js +263 -0
- package/dist/src/termite/index.d.ts +14 -0
- package/dist/src/termite/index.js +59 -0
- package/dist/src/termite/os.d.ts +326 -0
- package/dist/src/termite/os.js +572 -0
- package/dist/src/termite/versioning.d.ts +94 -0
- package/dist/src/termite/versioning.js +217 -0
- package/dist/src/tickets.d.ts +43 -0
- package/dist/src/tickets.js +138 -0
- package/dist/src/travel.d.ts +85 -0
- package/dist/src/travel.js +44 -0
- package/dist/src/ui/authentification.d.ts +24 -0
- package/dist/src/ui/authentification.js +177 -0
- package/dist/src/ui/brands.css +11 -0
- package/dist/src/ui/brands.d.ts +7 -0
- package/dist/src/ui/brands.js +14 -0
- package/dist/src/ui/browser.d.ts +74 -0
- package/dist/src/ui/browser.js +470 -0
- package/dist/src/ui/chat.d.ts +183 -0
- package/dist/src/ui/chat.js +2379 -0
- package/dist/src/ui/chatPanel.d.ts +109 -0
- package/dist/src/ui/chatPanel.js +768 -0
- package/dist/src/ui/chatSequence.d.ts +36 -0
- package/dist/src/ui/chatSequence.js +42 -0
- package/dist/src/ui/crown.d.ts +19 -0
- package/dist/src/ui/crown.js +199 -0
- package/dist/src/ui/cursor.d.ts +73 -0
- package/dist/src/ui/cursor.js +175 -0
- package/dist/src/ui/device.d.ts +33 -0
- package/dist/src/ui/device.js +49 -0
- package/dist/src/ui/drive.d.ts +69 -0
- package/dist/src/ui/drive.js +1243 -0
- package/dist/src/ui/generative.d.ts +39 -0
- package/dist/src/ui/generative.js +313 -0
- package/dist/src/ui/hover.d.ts +6 -0
- package/dist/src/ui/hover.js +210 -0
- package/dist/src/ui/index.d.ts +42 -0
- package/dist/src/ui/index.js +87 -0
- package/dist/src/ui/integrations.d.ts +13 -0
- package/dist/src/ui/integrations.js +175 -0
- package/dist/src/ui/maps.d.ts +87 -0
- package/dist/src/ui/maps.js +547 -0
- package/dist/src/ui/roster.d.ts +32 -0
- package/dist/src/ui/roster.js +163 -0
- package/dist/src/ui/talk.d.ts +12 -0
- package/dist/src/ui/talk.js +560 -0
- package/dist/src/ui/tickets.d.ts +13 -0
- package/dist/src/ui/tickets.js +966 -0
- package/dist/src/ui/toast.d.ts +7 -0
- package/dist/src/ui/toast.js +258 -0
- package/dist/src/ui/tokenText.d.ts +18 -0
- package/dist/src/ui/tokenText.js +96 -0
- package/dist/src/ui/tokenized.d.ts +42 -0
- package/dist/src/ui/tokenized.js +622 -0
- package/dist/src/ui/ui.css +4763 -0
- package/dist/src/ui/view.d.ts +23 -0
- package/dist/src/ui/view.js +584 -0
- package/dist/src/view.d.ts +113 -0
- package/dist/src/view.js +386 -0
- package/dist/src/webhooks.d.ts +66 -0
- package/dist/src/webhooks.js +78 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/package.json +89 -6
- package/README.md +0 -3
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
|
+
import { createRoot } from "react-dom/client";
|
|
4
|
+
import maplibregl from "maplibre-gl";
|
|
5
|
+
import { gsap } from "gsap";
|
|
6
|
+
import { Fauna } from "fauna-api";
|
|
7
|
+
function reducedMotion() {
|
|
8
|
+
try {
|
|
9
|
+
return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function proxied(source, path, init) {
|
|
16
|
+
const response = await fetch(`${source.proxy.replace(/\/+$/, "")}${path}`, {
|
|
17
|
+
method: init.method,
|
|
18
|
+
headers: {
|
|
19
|
+
...(init.body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
20
|
+
...source.headers,
|
|
21
|
+
},
|
|
22
|
+
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok)
|
|
25
|
+
throw new Error(await response.text());
|
|
26
|
+
return await response.json();
|
|
27
|
+
}
|
|
28
|
+
function requireSource(source) {
|
|
29
|
+
if (!source.proxy && !source.authorization)
|
|
30
|
+
throw new Error("Maps need authorization or a proxy.");
|
|
31
|
+
}
|
|
32
|
+
async function buyPass(source, theme, tiles) {
|
|
33
|
+
requireSource(source);
|
|
34
|
+
if (source.proxy) {
|
|
35
|
+
const pass = await proxied(source, "/maps/tiles/pass", { method: "POST", body: { theme, tiles } });
|
|
36
|
+
return { ...pass, expires: new Date(pass.expires) };
|
|
37
|
+
}
|
|
38
|
+
return Fauna.Maps.createTilePass(source.authorization, { theme, tiles }, source.group);
|
|
39
|
+
}
|
|
40
|
+
function tileTemplate(source, pass) {
|
|
41
|
+
if (source.proxy)
|
|
42
|
+
return `${source.proxy.replace(/\/+$/, "")}/maps/tile?pass=${encodeURIComponent(pass.id)}&z={z}&x={x}&y={y}`;
|
|
43
|
+
return Fauna.Maps.getTileURL(pass);
|
|
44
|
+
}
|
|
45
|
+
async function attributionFor(source, pass, zoom, bounds) {
|
|
46
|
+
if (source.proxy) {
|
|
47
|
+
const query = new URLSearchParams({ pass: pass.id, zoom: String(zoom), north: String(bounds.north), south: String(bounds.south), east: String(bounds.east), west: String(bounds.west) });
|
|
48
|
+
return proxied(source, `/maps/tiles/attribution?${query}`, { method: "GET" });
|
|
49
|
+
}
|
|
50
|
+
return Fauna.Maps.getAttribution(pass, zoom, bounds);
|
|
51
|
+
}
|
|
52
|
+
async function suggest(source, text, near) {
|
|
53
|
+
requireSource(source);
|
|
54
|
+
if (source.proxy)
|
|
55
|
+
return proxied(source, "/maps/places/autocomplete", { method: "POST", body: { text, near } });
|
|
56
|
+
return Fauna.Maps.autocomplete(source.authorization, text, { near }, source.group);
|
|
57
|
+
}
|
|
58
|
+
async function resolvePlace(source, place) {
|
|
59
|
+
requireSource(source);
|
|
60
|
+
const places = source.proxy
|
|
61
|
+
? await proxied(source, "/maps/geocode", { method: "POST", body: { query: { place } } })
|
|
62
|
+
: await Fauna.Maps.geocode(source.authorization, { place }, source.group);
|
|
63
|
+
return places[0];
|
|
64
|
+
}
|
|
65
|
+
function cssColor(element, name, fallback) {
|
|
66
|
+
return getComputedStyle(element).getPropertyValue(name).trim() || fallback;
|
|
67
|
+
}
|
|
68
|
+
function routeFeatures(routes) {
|
|
69
|
+
return {
|
|
70
|
+
type: "FeatureCollection",
|
|
71
|
+
features: routes.filter((route) => route.path.length > 1).map((route) => ({
|
|
72
|
+
type: "Feature",
|
|
73
|
+
properties: { mode: route.mode },
|
|
74
|
+
geometry: { type: "LineString", coordinates: route.path.map((point) => [point.longitude, point.latitude]) },
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function markerElement(marker, onClick) {
|
|
79
|
+
const element = document.createElement("button");
|
|
80
|
+
element.type = "button";
|
|
81
|
+
element.className = `fauna-map-marker${marker.tone === "muted" ? " is-muted" : ""}`;
|
|
82
|
+
element.setAttribute("aria-label", marker.label ?? "Marker");
|
|
83
|
+
const pin = document.createElement("span");
|
|
84
|
+
pin.className = "fauna-map-pin";
|
|
85
|
+
element.appendChild(pin);
|
|
86
|
+
if (marker.label) {
|
|
87
|
+
const label = document.createElement("span");
|
|
88
|
+
label.className = "fauna-map-label";
|
|
89
|
+
label.textContent = marker.label;
|
|
90
|
+
element.appendChild(label);
|
|
91
|
+
}
|
|
92
|
+
if (onClick)
|
|
93
|
+
element.addEventListener("click", () => onClick(marker));
|
|
94
|
+
else
|
|
95
|
+
element.tabIndex = -1;
|
|
96
|
+
return element;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A map drawn from prepaid tile passes, with markers and route lines. A pass that
|
|
100
|
+
* runs out is replaced, up to `maxPasses`. The attribution the pass carries is
|
|
101
|
+
* always shown, as the tile provider's terms require.
|
|
102
|
+
*/
|
|
103
|
+
export function createMap(container, initial) {
|
|
104
|
+
let options = { fit: true, interactive: true, tiles: 150, maxPasses: 4, theme: "light", ...initial };
|
|
105
|
+
let map;
|
|
106
|
+
let pass;
|
|
107
|
+
let passes = 0;
|
|
108
|
+
let renewing = false;
|
|
109
|
+
let destroyed = false;
|
|
110
|
+
let markers = [];
|
|
111
|
+
let attributionTimer;
|
|
112
|
+
container.classList.add("fauna-map");
|
|
113
|
+
const canvas = document.createElement("div");
|
|
114
|
+
canvas.className = "fauna-map-canvas";
|
|
115
|
+
const credit = document.createElement("div");
|
|
116
|
+
credit.className = "fauna-map-attribution";
|
|
117
|
+
const logo = document.createElement("img");
|
|
118
|
+
logo.alt = "";
|
|
119
|
+
const text = document.createElement("span");
|
|
120
|
+
credit.append(logo, text);
|
|
121
|
+
const notice = document.createElement("div");
|
|
122
|
+
notice.className = "fauna-map-notice";
|
|
123
|
+
container.append(canvas, credit, notice);
|
|
124
|
+
const showAttribution = (value) => {
|
|
125
|
+
logo.src = value.logo;
|
|
126
|
+
text.textContent = value.text;
|
|
127
|
+
};
|
|
128
|
+
const showNotice = (message) => {
|
|
129
|
+
notice.textContent = message;
|
|
130
|
+
notice.classList.toggle("is-shown", Boolean(message));
|
|
131
|
+
};
|
|
132
|
+
const refreshAttribution = () => {
|
|
133
|
+
clearTimeout(attributionTimer);
|
|
134
|
+
attributionTimer = setTimeout(() => {
|
|
135
|
+
if (!map || !pass || destroyed)
|
|
136
|
+
return;
|
|
137
|
+
const bounds = map.getBounds();
|
|
138
|
+
const current = pass;
|
|
139
|
+
attributionFor(options, current, Math.round(map.getZoom()), {
|
|
140
|
+
north: bounds.getNorth(),
|
|
141
|
+
south: bounds.getSouth(),
|
|
142
|
+
east: bounds.getEast(),
|
|
143
|
+
west: bounds.getWest(),
|
|
144
|
+
}).then((value) => { if (current === pass && !destroyed)
|
|
145
|
+
showAttribution(value); }).catch(() => { });
|
|
146
|
+
}, 400);
|
|
147
|
+
};
|
|
148
|
+
const syncMarkers = () => {
|
|
149
|
+
if (!map)
|
|
150
|
+
return;
|
|
151
|
+
for (const marker of markers)
|
|
152
|
+
marker.remove();
|
|
153
|
+
markers = (options.markers ?? []).map((marker) => new maplibregl.Marker({ element: markerElement(marker, options.onMarkerClick), anchor: "bottom" })
|
|
154
|
+
.setLngLat([marker.coordinates.longitude, marker.coordinates.latitude])
|
|
155
|
+
.addTo(map));
|
|
156
|
+
};
|
|
157
|
+
const syncRoutes = () => {
|
|
158
|
+
if (!map || !map.isStyleLoaded())
|
|
159
|
+
return;
|
|
160
|
+
const data = routeFeatures(options.routes ?? []);
|
|
161
|
+
const source = map.getSource("fauna-routes");
|
|
162
|
+
if (source) {
|
|
163
|
+
source.setData(data);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
map.addSource("fauna-routes", { type: "geojson", data });
|
|
167
|
+
const color = (mode, fallback) => cssColor(container, `--maps-route-${mode}`, fallback);
|
|
168
|
+
const width = Number.parseFloat(cssColor(container, "--maps-route-width", "5")) || 5;
|
|
169
|
+
map.addLayer({
|
|
170
|
+
id: "fauna-routes-casing",
|
|
171
|
+
type: "line",
|
|
172
|
+
source: "fauna-routes",
|
|
173
|
+
layout: { "line-join": "round", "line-cap": "round" },
|
|
174
|
+
paint: { "line-color": cssColor(container, "--maps-route-casing", "#ffffff"), "line-width": width + 3, "line-opacity": 0.9 },
|
|
175
|
+
});
|
|
176
|
+
map.addLayer({
|
|
177
|
+
id: "fauna-routes-line",
|
|
178
|
+
type: "line",
|
|
179
|
+
source: "fauna-routes",
|
|
180
|
+
layout: { "line-join": "round", "line-cap": "round" },
|
|
181
|
+
paint: {
|
|
182
|
+
"line-color": ["match", ["get", "mode"],
|
|
183
|
+
"drive", color("drive", "#AE1B28"),
|
|
184
|
+
"walk", color("walk", "#8A8580"),
|
|
185
|
+
"transit", color("transit", "#2F6FDE"),
|
|
186
|
+
color("flight", "#161617")],
|
|
187
|
+
"line-width": width,
|
|
188
|
+
"line-dasharray": ["match", ["get", "mode"], "walk", ["literal", [0.6, 1.6]], ["literal", [1, 0]]],
|
|
189
|
+
},
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
const fitView = () => {
|
|
193
|
+
if (!map || options.fit === false)
|
|
194
|
+
return;
|
|
195
|
+
const points = [
|
|
196
|
+
...(options.markers ?? []).map((marker) => marker.coordinates),
|
|
197
|
+
...(options.routes ?? []).flatMap((route) => route.path),
|
|
198
|
+
];
|
|
199
|
+
if (points.length === 0)
|
|
200
|
+
return;
|
|
201
|
+
if (points.length === 1) {
|
|
202
|
+
map.easeTo({ center: [points[0].longitude, points[0].latitude], zoom: options.zoom ?? 13, duration: reducedMotion() ? 0 : 600 });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const bounds = new maplibregl.LngLatBounds();
|
|
206
|
+
for (const point of points)
|
|
207
|
+
bounds.extend([point.longitude, point.latitude]);
|
|
208
|
+
map.fitBounds(bounds, { padding: 48, maxZoom: 15, duration: reducedMotion() ? 0 : 700 });
|
|
209
|
+
};
|
|
210
|
+
const renew = async () => {
|
|
211
|
+
if (renewing || destroyed || !map)
|
|
212
|
+
return;
|
|
213
|
+
if (passes >= (options.maxPasses ?? 4)) {
|
|
214
|
+
showNotice("This map has used its tiles for now.");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
renewing = true;
|
|
218
|
+
try {
|
|
219
|
+
pass = await buyPass(options, options.theme ?? "light", options.tiles ?? 150);
|
|
220
|
+
passes++;
|
|
221
|
+
const source = map.getSource("fauna-base");
|
|
222
|
+
source?.setTiles([tileTemplate(options, pass)]);
|
|
223
|
+
showAttribution(pass.attribution);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
showNotice(err instanceof Error ? err.message : "The map could not load.");
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
renewing = false;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
const start = async () => {
|
|
233
|
+
try {
|
|
234
|
+
pass = await buyPass(options, options.theme ?? "light", options.tiles ?? 150);
|
|
235
|
+
passes++;
|
|
236
|
+
}
|
|
237
|
+
catch (err) {
|
|
238
|
+
showNotice(err instanceof Error ? err.message : "The map could not load.");
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
if (destroyed)
|
|
242
|
+
return;
|
|
243
|
+
showAttribution(pass.attribution);
|
|
244
|
+
const center = options.center ?? options.markers?.[0]?.coordinates ?? options.routes?.[0]?.path[0] ?? { latitude: 39.5, longitude: -98.35 };
|
|
245
|
+
map = new maplibregl.Map({
|
|
246
|
+
container: canvas,
|
|
247
|
+
style: {
|
|
248
|
+
version: 8,
|
|
249
|
+
sources: {
|
|
250
|
+
"fauna-base": { type: "raster", tiles: [tileTemplate(options, pass)], tileSize: pass.size, maxzoom: pass.maxZoom },
|
|
251
|
+
},
|
|
252
|
+
layers: [{ id: "fauna-base", type: "raster", source: "fauna-base" }],
|
|
253
|
+
},
|
|
254
|
+
center: [center.longitude, center.latitude],
|
|
255
|
+
zoom: options.zoom ?? (options.center || options.markers?.length ? 12 : 3),
|
|
256
|
+
interactive: options.interactive !== false,
|
|
257
|
+
attributionControl: false,
|
|
258
|
+
dragRotate: false,
|
|
259
|
+
pitchWithRotate: false,
|
|
260
|
+
});
|
|
261
|
+
map.touchZoomRotate.disableRotation();
|
|
262
|
+
map.on("load", () => {
|
|
263
|
+
syncRoutes();
|
|
264
|
+
syncMarkers();
|
|
265
|
+
fitView();
|
|
266
|
+
if (!reducedMotion())
|
|
267
|
+
gsap.fromTo(canvas, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.4, ease: "power2.out" });
|
|
268
|
+
});
|
|
269
|
+
map.on("moveend", refreshAttribution);
|
|
270
|
+
map.on("error", (event) => {
|
|
271
|
+
if (event.error?.status === 402)
|
|
272
|
+
void renew();
|
|
273
|
+
else
|
|
274
|
+
console.warn("[maps]", event.error);
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
void start();
|
|
278
|
+
return {
|
|
279
|
+
get map() { return map; },
|
|
280
|
+
update(next) {
|
|
281
|
+
const before = options;
|
|
282
|
+
options = { ...options, ...next };
|
|
283
|
+
if (!map)
|
|
284
|
+
return;
|
|
285
|
+
if (next.markers !== undefined && next.markers !== before.markers)
|
|
286
|
+
syncMarkers();
|
|
287
|
+
if (next.routes !== undefined && next.routes !== before.routes)
|
|
288
|
+
syncRoutes();
|
|
289
|
+
if ((next.markers !== undefined && next.markers !== before.markers) || (next.routes !== undefined && next.routes !== before.routes))
|
|
290
|
+
fitView();
|
|
291
|
+
else if (next.center && (next.center.latitude !== before.center?.latitude || next.center.longitude !== before.center?.longitude)) {
|
|
292
|
+
map.easeTo({ center: [next.center.longitude, next.center.latitude], zoom: next.zoom ?? map.getZoom(), duration: reducedMotion() ? 0 : 600 });
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
destroy() {
|
|
296
|
+
destroyed = true;
|
|
297
|
+
clearTimeout(attributionTimer);
|
|
298
|
+
for (const marker of markers)
|
|
299
|
+
marker.remove();
|
|
300
|
+
map?.remove();
|
|
301
|
+
container.classList.remove("fauna-map");
|
|
302
|
+
canvas.remove();
|
|
303
|
+
credit.remove();
|
|
304
|
+
notice.remove();
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/** A map for React. The source and theme are fixed for its life; markers, routes and the view follow props. */
|
|
309
|
+
export function Map(props) {
|
|
310
|
+
const { className, style, ...options } = props;
|
|
311
|
+
const ref = useRef(null);
|
|
312
|
+
const instance = useRef(null);
|
|
313
|
+
const latest = useRef(options);
|
|
314
|
+
latest.current = options;
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
if (!ref.current)
|
|
317
|
+
return;
|
|
318
|
+
const created = createMap(ref.current, latest.current);
|
|
319
|
+
instance.current = created;
|
|
320
|
+
return () => {
|
|
321
|
+
created.destroy();
|
|
322
|
+
instance.current = null;
|
|
323
|
+
};
|
|
324
|
+
}, [props.proxy, props.authorization, props.group?.id, props.theme]);
|
|
325
|
+
useEffect(() => {
|
|
326
|
+
instance.current?.update({ markers: options.markers, routes: options.routes, center: options.center, zoom: options.zoom, onMarkerClick: options.onMarkerClick });
|
|
327
|
+
}, [options.markers, options.routes, options.center?.latitude, options.center?.longitude, options.zoom, options.onMarkerClick]);
|
|
328
|
+
return _jsx("div", { ref: ref, className: className ? `fauna-map ${className}` : "fauna-map", style: style });
|
|
329
|
+
}
|
|
330
|
+
// Directions
|
|
331
|
+
const MODE_ORDER = ["drive", "transit", "walk", "flight"];
|
|
332
|
+
const MODE_LABELS = { drive: "Drive", transit: "Transit", walk: "Walk", flight: "Fly" };
|
|
333
|
+
const MODE_ICONS = { drive: "directions_car", transit: "directions_transit", walk: "directions_walk", flight: "flight" };
|
|
334
|
+
const VEHICLE_ICONS = { bus: "directions_bus", subway: "subway", train: "train", tram: "tram", ferry: "directions_boat" };
|
|
335
|
+
export function formatDuration(seconds) {
|
|
336
|
+
const minutes = Math.max(1, Math.round(seconds / 60));
|
|
337
|
+
if (minutes < 60)
|
|
338
|
+
return `${minutes} min`;
|
|
339
|
+
const hours = Math.floor(minutes / 60);
|
|
340
|
+
const rest = minutes % 60;
|
|
341
|
+
return rest ? `${hours} h ${rest} min` : `${hours} h`;
|
|
342
|
+
}
|
|
343
|
+
export function formatDistance(meters, units = "metric") {
|
|
344
|
+
if (units === "imperial") {
|
|
345
|
+
const miles = meters / 1609.344;
|
|
346
|
+
if (miles < 0.1)
|
|
347
|
+
return `${Math.round(meters * 3.28084)} ft`;
|
|
348
|
+
return `${miles < 10 ? miles.toFixed(1) : Math.round(miles)} mi`;
|
|
349
|
+
}
|
|
350
|
+
if (meters < 1000)
|
|
351
|
+
return `${Math.round(meters)} m`;
|
|
352
|
+
const km = meters / 1000;
|
|
353
|
+
return `${km < 10 ? km.toFixed(1) : Math.round(km)} km`;
|
|
354
|
+
}
|
|
355
|
+
export function formatMoney(amount, currency) {
|
|
356
|
+
try {
|
|
357
|
+
return new Intl.NumberFormat(undefined, { style: "currency", currency, maximumFractionDigits: amount % 1 === 0 ? 0 : 2 }).format(amount);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
return `${amount} ${currency}`;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function formatTime(value) {
|
|
364
|
+
if (!value)
|
|
365
|
+
return "";
|
|
366
|
+
const date = typeof value === "string" ? new Date(value) : value;
|
|
367
|
+
return Number.isNaN(date.getTime()) ? "" : date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
368
|
+
}
|
|
369
|
+
/** Open where to book: a link, or a form posted in a new tab when the offer carries one. */
|
|
370
|
+
export function openOffer(offer) {
|
|
371
|
+
if (!offer.form) {
|
|
372
|
+
window.open(offer.url, "_blank", "noopener");
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
const form = document.createElement("form");
|
|
376
|
+
form.method = "POST";
|
|
377
|
+
form.action = offer.url;
|
|
378
|
+
form.target = "_blank";
|
|
379
|
+
form.rel = "noopener";
|
|
380
|
+
for (const [name, value] of new URLSearchParams(offer.form)) {
|
|
381
|
+
const input = document.createElement("input");
|
|
382
|
+
input.type = "hidden";
|
|
383
|
+
input.name = name;
|
|
384
|
+
input.value = value;
|
|
385
|
+
form.appendChild(input);
|
|
386
|
+
}
|
|
387
|
+
document.body.appendChild(form);
|
|
388
|
+
form.submit();
|
|
389
|
+
form.remove();
|
|
390
|
+
}
|
|
391
|
+
function StepRow({ step, units }) {
|
|
392
|
+
const icon = step.mode === "transit" ? VEHICLE_ICONS[step.vehicle ?? ""] ?? "directions_transit" : MODE_ICONS[step.mode];
|
|
393
|
+
const times = step.departure && step.arrival ? `${formatTime(step.departure)} to ${formatTime(step.arrival)}` : "";
|
|
394
|
+
return (_jsxs("li", { className: `fauna-directions-step is-${step.mode}`, children: [_jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: icon }), _jsxs("div", { className: "fauna-directions-step-text", children: [_jsx("span", { children: step.instruction }), _jsx("span", { className: "fauna-directions-muted", children: [
|
|
395
|
+
step.line && step.from ? `${step.line} from ${step.from}` : step.line,
|
|
396
|
+
step.stops ? `${step.stops} stop${step.stops === 1 ? "" : "s"}` : "",
|
|
397
|
+
times,
|
|
398
|
+
step.distance ? formatDistance(step.distance, units) : "",
|
|
399
|
+
].filter(Boolean).join(", ") })] })] }));
|
|
400
|
+
}
|
|
401
|
+
function FlightRow({ flight, offers }) {
|
|
402
|
+
const [state, setState] = useState({ status: "idle", offers: [] });
|
|
403
|
+
const listRef = useRef(null);
|
|
404
|
+
useLayoutEffect(() => {
|
|
405
|
+
if (state.status === "open" && listRef.current && !reducedMotion()) {
|
|
406
|
+
gsap.fromTo(listRef.current.children, { y: 6, autoAlpha: 0 }, { y: 0, autoAlpha: 1, duration: 0.25, stagger: 0.04, ease: "power2.out" });
|
|
407
|
+
}
|
|
408
|
+
}, [state.status]);
|
|
409
|
+
const book = async () => {
|
|
410
|
+
if (!offers)
|
|
411
|
+
return;
|
|
412
|
+
if (state.status === "open") {
|
|
413
|
+
setState({ status: "idle", offers: state.offers });
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
setState({ status: "loading", offers: [] });
|
|
417
|
+
try {
|
|
418
|
+
setState({ status: "open", offers: await offers(flight) });
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
setState({ status: "failed", offers: [] });
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
return (_jsxs("li", { className: "fauna-directions-flight", children: [_jsxs("div", { className: "fauna-directions-flight-main", children: [flight.logo ? _jsx("img", { src: flight.logo, alt: "" }) : _jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "flight" }), _jsxs("div", { className: "fauna-directions-step-text", children: [_jsxs("span", { children: [formatTime(flight.departure), " to ", formatTime(flight.arrival)] }), _jsx("span", { className: "fauna-directions-muted", children: [flight.airlines.join(", "), formatDuration(flight.duration), flight.stops === 0 ? "Nonstop" : `${flight.stops} stop${flight.stops === 1 ? "" : "s"}`, `${flight.from} to ${flight.to}`].join(", ") })] }), _jsx("strong", { className: "fauna-directions-price", children: formatMoney(flight.price.amount, flight.price.currency) }), offers && (_jsx("button", { type: "button", className: "fauna-directions-book", onClick: book, disabled: state.status === "loading", children: state.status === "loading" ? "Finding" : state.status === "open" ? "Hide" : "Book" }))] }), state.status === "failed" && _jsx("p", { className: "fauna-directions-muted", children: "Booking options are not available right now." }), state.status === "open" && (_jsxs("ul", { ref: listRef, className: "fauna-directions-offers", children: [state.offers.length === 0 && _jsx("li", { className: "fauna-directions-muted", children: "No one is selling this flight right now." }), state.offers.map((offer, index) => (_jsx("li", { children: _jsxs("button", { type: "button", onClick: () => openOffer(offer), children: [_jsx("span", { children: offer.seller }), offer.price && _jsx("strong", { children: formatMoney(offer.price.amount, offer.price.currency) }), _jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "open_in_new" })] }) }, `${offer.seller}${index}`)))] }))] }));
|
|
425
|
+
}
|
|
426
|
+
/** Ways to get somewhere: a tab per mode, the time, distance and cost, and the steps or flights. */
|
|
427
|
+
export function Directions({ routes, flights = [], units = "metric", mode, recommended, onMode, offers, className }) {
|
|
428
|
+
const modes = MODE_ORDER.filter((value) => value === "flight" ? flights.length > 0 : routes.some((route) => route.mode === value));
|
|
429
|
+
const [selected, setSelected] = useState(mode ?? recommended ?? modes[0]);
|
|
430
|
+
const current = selected && modes.includes(selected) ? selected : modes[0];
|
|
431
|
+
const bodyRef = useRef(null);
|
|
432
|
+
const tabsRef = useRef(null);
|
|
433
|
+
const markRef = useRef(null);
|
|
434
|
+
useEffect(() => {
|
|
435
|
+
if (mode)
|
|
436
|
+
setSelected(mode);
|
|
437
|
+
}, [mode]);
|
|
438
|
+
useLayoutEffect(() => {
|
|
439
|
+
const tab = tabsRef.current?.querySelector(`[data-mode="${current}"]`);
|
|
440
|
+
const mark = markRef.current;
|
|
441
|
+
if (!tab || !mark)
|
|
442
|
+
return;
|
|
443
|
+
const to = { x: tab.offsetLeft, width: tab.offsetWidth };
|
|
444
|
+
if (reducedMotion())
|
|
445
|
+
gsap.set(mark, to);
|
|
446
|
+
else
|
|
447
|
+
gsap.to(mark, { ...to, duration: 0.35, ease: "power3.out" });
|
|
448
|
+
if (bodyRef.current && !reducedMotion())
|
|
449
|
+
gsap.fromTo(bodyRef.current, { autoAlpha: 0, y: 6 }, { autoAlpha: 1, y: 0, duration: 0.25, ease: "power2.out" });
|
|
450
|
+
}, [current, modes.length]);
|
|
451
|
+
if (modes.length === 0) {
|
|
452
|
+
return _jsx("div", { className: `fauna-directions${className ? ` ${className}` : ""}`, children: _jsx("p", { className: "fauna-directions-muted", children: "No way there was found." }) });
|
|
453
|
+
}
|
|
454
|
+
const route = routes.find((value) => value.mode === current);
|
|
455
|
+
const cheapest = flights.length ? flights.reduce((best, flight) => flight.price.amount < best.price.amount ? flight : best) : undefined;
|
|
456
|
+
const choose = (value) => {
|
|
457
|
+
setSelected(value);
|
|
458
|
+
onMode?.(value);
|
|
459
|
+
};
|
|
460
|
+
return (_jsxs("div", { className: `fauna-directions${className ? ` ${className}` : ""}`, children: [_jsxs("div", { ref: tabsRef, className: "fauna-directions-tabs", role: "tablist", children: [_jsx("span", { ref: markRef, className: "fauna-directions-mark", "aria-hidden": "true" }), modes.map((value) => (_jsxs("button", { type: "button", role: "tab", "data-mode": value, "aria-selected": value === current, className: value === current ? "is-current" : "", onClick: () => choose(value), children: [_jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: MODE_ICONS[value] }), MODE_LABELS[value], value === recommended && _jsx("span", { className: "fauna-directions-best", children: "Best" })] }, value)))] }), _jsx("div", { ref: bodyRef, className: "fauna-directions-body", role: "tabpanel", children: current === "flight" ? (_jsxs(_Fragment, { children: [cheapest && (_jsxs("div", { className: "fauna-directions-summary", children: [_jsxs("strong", { children: ["From ", formatMoney(cheapest.price.amount, cheapest.price.currency)] }), _jsxs("span", { className: "fauna-directions-muted", children: [flights.length, " flight", flights.length === 1 ? "" : "s"] })] })), _jsx("ul", { className: "fauna-directions-list", children: flights.map((flight) => _jsx(FlightRow, { flight: flight, offers: offers }, flight.id)) })] })) : route ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fauna-directions-summary", children: [_jsx("strong", { children: formatDuration(route.duration) }), _jsx("span", { className: "fauna-directions-muted", children: [
|
|
461
|
+
formatDistance(route.distance, units),
|
|
462
|
+
route.fare ? formatMoney(route.fare.amount, route.fare.currency) : "",
|
|
463
|
+
route.departure && route.arrival ? `Leave ${formatTime(route.departure)}, arrive ${formatTime(route.arrival)}` : "",
|
|
464
|
+
].filter(Boolean).join(", ") })] }), _jsx("ol", { className: "fauna-directions-list", children: route.steps.map((step, index) => _jsx(StepRow, { step: step, units: units }, index)) })] })) : null })] }));
|
|
465
|
+
}
|
|
466
|
+
/** A text box that suggests places as someone types and hands back the one they pick. */
|
|
467
|
+
export function PlaceSearch({ onSelect, near, placeholder = "Search for a place", defaultValue = "", autoFocus, className, ...source }) {
|
|
468
|
+
const [text, setText] = useState(defaultValue);
|
|
469
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
470
|
+
const [active, setActive] = useState(0);
|
|
471
|
+
const [open, setOpen] = useState(false);
|
|
472
|
+
const [busy, setBusy] = useState(false);
|
|
473
|
+
const asked = useRef(0);
|
|
474
|
+
const listRef = useRef(null);
|
|
475
|
+
useEffect(() => {
|
|
476
|
+
const query = text.trim();
|
|
477
|
+
if (query.length < 2 || !open) {
|
|
478
|
+
setSuggestions([]);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const ticket = ++asked.current;
|
|
482
|
+
const timer = setTimeout(() => {
|
|
483
|
+
suggest(source, query, near)
|
|
484
|
+
.then((found) => {
|
|
485
|
+
if (ticket !== asked.current)
|
|
486
|
+
return;
|
|
487
|
+
setSuggestions(found);
|
|
488
|
+
setActive(0);
|
|
489
|
+
})
|
|
490
|
+
.catch(() => { if (ticket === asked.current)
|
|
491
|
+
setSuggestions([]); });
|
|
492
|
+
}, 250);
|
|
493
|
+
return () => clearTimeout(timer);
|
|
494
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
495
|
+
}, [text, open, near?.latitude, near?.longitude, source.proxy, source.authorization]);
|
|
496
|
+
useLayoutEffect(() => {
|
|
497
|
+
if (listRef.current && suggestions.length && !reducedMotion()) {
|
|
498
|
+
gsap.fromTo(listRef.current, { autoAlpha: 0, y: -4 }, { autoAlpha: 1, y: 0, duration: 0.18, ease: "power2.out" });
|
|
499
|
+
}
|
|
500
|
+
}, [suggestions.length > 0]);
|
|
501
|
+
const pick = async (suggestion) => {
|
|
502
|
+
setOpen(false);
|
|
503
|
+
setText(suggestion.name);
|
|
504
|
+
setBusy(true);
|
|
505
|
+
try {
|
|
506
|
+
const place = await resolvePlace(source, suggestion.place);
|
|
507
|
+
if (place)
|
|
508
|
+
onSelect({ ...place, name: suggestion.name });
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
setBusy(false);
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
const onKey = (event) => {
|
|
515
|
+
if (!suggestions.length)
|
|
516
|
+
return;
|
|
517
|
+
if (event.key === "ArrowDown") {
|
|
518
|
+
event.preventDefault();
|
|
519
|
+
setActive((value) => (value + 1) % suggestions.length);
|
|
520
|
+
}
|
|
521
|
+
else if (event.key === "ArrowUp") {
|
|
522
|
+
event.preventDefault();
|
|
523
|
+
setActive((value) => (value - 1 + suggestions.length) % suggestions.length);
|
|
524
|
+
}
|
|
525
|
+
else if (event.key === "Enter") {
|
|
526
|
+
event.preventDefault();
|
|
527
|
+
void pick(suggestions[active]);
|
|
528
|
+
}
|
|
529
|
+
else if (event.key === "Escape") {
|
|
530
|
+
setOpen(false);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
const shown = open && suggestions.length > 0;
|
|
534
|
+
return (_jsxs("div", { className: `fauna-place-search${busy ? " is-busy" : ""}${className ? ` ${className}` : ""}`, children: [_jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "search" }), _jsx("input", { type: "text", value: text, placeholder: placeholder, autoFocus: autoFocus, autoComplete: "off", role: "combobox", "aria-expanded": shown, "aria-autocomplete": "list", onChange: (event) => {
|
|
535
|
+
setText(event.target.value);
|
|
536
|
+
setOpen(true);
|
|
537
|
+
}, onFocus: () => setOpen(true), onBlur: () => setTimeout(() => setOpen(false), 120), onKeyDown: onKey }), shown && (_jsx("ul", { ref: listRef, className: "fauna-place-search-list", role: "listbox", children: suggestions.map((suggestion, index) => (_jsxs("li", { role: "option", "aria-selected": index === active, className: index === active ? "is-active" : "", onMouseDown: (event) => event.preventDefault(), onMouseEnter: () => setActive(index), onClick: () => void pick(suggestion), children: [_jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "location_on" }), _jsxs("span", { className: "fauna-place-search-text", children: [_jsx("strong", { children: suggestion.name }), _jsx("span", { children: suggestion.description })] })] }, suggestion.place))) }))] }));
|
|
538
|
+
}
|
|
539
|
+
/** Place search for apps without React. */
|
|
540
|
+
export function mountPlaceSearch(element, props) {
|
|
541
|
+
const root = createRoot(element);
|
|
542
|
+
root.render(_jsx(PlaceSearch, { ...props }));
|
|
543
|
+
return {
|
|
544
|
+
update(next) { root.render(_jsx(PlaceSearch, { ...next })); },
|
|
545
|
+
destroy() { root.unmount(); },
|
|
546
|
+
};
|
|
547
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RoomAgent } from "../marmot/browser.js";
|
|
2
|
+
import type { Presence } from "../shared/server.js";
|
|
3
|
+
export type RosterProps = {
|
|
4
|
+
/** This tab's seat. */
|
|
5
|
+
self: Presence | null;
|
|
6
|
+
peers: Presence[];
|
|
7
|
+
/** The seat whose actions run when no agent holds the room. */
|
|
8
|
+
controller: string | null;
|
|
9
|
+
/** Set while an agent holds the room: it shows first and wears the ring. */
|
|
10
|
+
agent: RoomAgent | null;
|
|
11
|
+
/** Hand control to a seat, or take it yourself without one. Called after a confirm while an agent holds the room. */
|
|
12
|
+
onHand: (seat?: string) => void;
|
|
13
|
+
/** Bumped when someone without control reaches for the room, to point at who has it. */
|
|
14
|
+
nudge?: number;
|
|
15
|
+
};
|
|
16
|
+
/** Asks before stopping an agent: taking control (`control`) or shutting down (`power`). */
|
|
17
|
+
export declare function AgentConfirm({ kind, onConfirm, onCancel }: {
|
|
18
|
+
kind: "control" | "power";
|
|
19
|
+
onConfirm: () => void;
|
|
20
|
+
onCancel: () => void;
|
|
21
|
+
}): import("react").JSX.Element;
|
|
22
|
+
/**
|
|
23
|
+
* Who's in the room, who's in control, and a click to hand control to anyone.
|
|
24
|
+
* While an agent holds the room it comes first with the ring, everyone watches,
|
|
25
|
+
* and clicking a person asks before stopping the agent.
|
|
26
|
+
*/
|
|
27
|
+
export declare function Roster({ self, peers, controller, agent, onHand, nudge }: RosterProps): import("react").JSX.Element | null;
|
|
28
|
+
/** The roster for apps without React. Call `update` with new props as the room changes. */
|
|
29
|
+
export declare function mountRoster(el: HTMLElement, props: RosterProps): {
|
|
30
|
+
update(props: RosterProps): void;
|
|
31
|
+
destroy(): void;
|
|
32
|
+
};
|