@a2f0/skyline 0.2.60 → 0.2.62
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.
|
@@ -44,3 +44,13 @@ export interface SkylineColour {
|
|
|
44
44
|
refresh(): void;
|
|
45
45
|
}
|
|
46
46
|
export declare function createSkylineColour(models: readonly BuildingModel[]): SkylineColour;
|
|
47
|
+
/** A colour layer that is made on first use; see `deferSkylineColour`. */
|
|
48
|
+
export interface DeferredSkylineColour extends SkylineColour {
|
|
49
|
+
/**
|
|
50
|
+
* Makes the layer now, if it isn't made yet. Call it before anything rewrites the buildings'
|
|
51
|
+
* colours, such as a celebration's window illumination: the layer reads their greys once,
|
|
52
|
+
* when it is made.
|
|
53
|
+
*/
|
|
54
|
+
prepare(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function deferSkylineColour(models: readonly BuildingModel[]): DeferredSkylineColour;
|
|
@@ -506,3 +506,43 @@ export function createSkylineColour(models) {
|
|
|
506
506
|
},
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
+
// Making the layer reads every vertex of every building, a large share of the scene's start-up
|
|
510
|
+
// time, and changes nothing on screen until colour, a previewed time or a celebration needs it:
|
|
511
|
+
// its facade shaders install only then (colour-reflections.ts). So this one is made on first
|
|
512
|
+
// use, or by `prepare`, from the same buildings and with the sky and night it was given
|
|
513
|
+
// meanwhile, and acts from then on exactly as one made at once.
|
|
514
|
+
export function deferSkylineColour(models) {
|
|
515
|
+
let layer;
|
|
516
|
+
let sky, night = null;
|
|
517
|
+
const made = () => {
|
|
518
|
+
if (layer)
|
|
519
|
+
return layer;
|
|
520
|
+
layer = createSkylineColour(models);
|
|
521
|
+
if (sky !== undefined)
|
|
522
|
+
layer.setSky(sky);
|
|
523
|
+
if (night)
|
|
524
|
+
layer.setNight(night);
|
|
525
|
+
return layer;
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
get enabled() { return layer?.enabled ?? false; },
|
|
529
|
+
prepare() { made(); },
|
|
530
|
+
set(enabled) {
|
|
531
|
+
if (layer || enabled)
|
|
532
|
+
made().set(enabled);
|
|
533
|
+
},
|
|
534
|
+
setSky(colours) {
|
|
535
|
+
sky = colours;
|
|
536
|
+
layer?.setSky(colours);
|
|
537
|
+
},
|
|
538
|
+
setNight(lighting) {
|
|
539
|
+
night = lighting;
|
|
540
|
+
if (layer)
|
|
541
|
+
layer.setNight(lighting);
|
|
542
|
+
else if (lighting)
|
|
543
|
+
made();
|
|
544
|
+
},
|
|
545
|
+
// Until it is made, nothing shows colour or a previewed night for it to refresh.
|
|
546
|
+
refresh() { layer?.refresh(); },
|
|
547
|
+
};
|
|
548
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a2f0/skyline",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.62",
|
|
4
4
|
"description": "Interactive Chicago skyline viewer and reusable Three.js building models",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"access": "public"
|
|
12
12
|
},
|
|
13
13
|
"type": "module",
|
|
14
|
-
"packageManager": "bun@1.4.
|
|
14
|
+
"packageManager": "bun@1.4.4",
|
|
15
15
|
"sideEffects": [
|
|
16
16
|
"./site/**/*.js"
|
|
17
17
|
],
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
62
|
"dependencies": {
|
|
63
|
-
"@a2f0/ct-client": "0.1.
|
|
63
|
+
"@a2f0/ct-client": "0.1.7",
|
|
64
64
|
"@types/three": "0.186.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
@@ -94,9 +94,9 @@
|
|
|
94
94
|
}
|
|
95
95
|
},
|
|
96
96
|
"devDependencies": {
|
|
97
|
-
"@a2f0/agent-tool": "0.1.
|
|
97
|
+
"@a2f0/agent-tool": "0.1.40",
|
|
98
98
|
"@tsconfig/strictest": "^2.0.8",
|
|
99
|
-
"@types/bun": "^1.4.
|
|
99
|
+
"@types/bun": "^1.4.4",
|
|
100
100
|
"esbuild": "0.28.2",
|
|
101
101
|
"playwright": "1.64.1",
|
|
102
102
|
"three": "0.186.1",
|
|
@@ -506,3 +506,43 @@ export function createSkylineColour(models) {
|
|
|
506
506
|
},
|
|
507
507
|
};
|
|
508
508
|
}
|
|
509
|
+
// Making the layer reads every vertex of every building, a large share of the scene's start-up
|
|
510
|
+
// time, and changes nothing on screen until colour, a previewed time or a celebration needs it:
|
|
511
|
+
// its facade shaders install only then (colour-reflections.ts). So this one is made on first
|
|
512
|
+
// use, or by `prepare`, from the same buildings and with the sky and night it was given
|
|
513
|
+
// meanwhile, and acts from then on exactly as one made at once.
|
|
514
|
+
export function deferSkylineColour(models) {
|
|
515
|
+
let layer;
|
|
516
|
+
let sky, night = null;
|
|
517
|
+
const made = () => {
|
|
518
|
+
if (layer)
|
|
519
|
+
return layer;
|
|
520
|
+
layer = createSkylineColour(models);
|
|
521
|
+
if (sky !== undefined)
|
|
522
|
+
layer.setSky(sky);
|
|
523
|
+
if (night)
|
|
524
|
+
layer.setNight(night);
|
|
525
|
+
return layer;
|
|
526
|
+
};
|
|
527
|
+
return {
|
|
528
|
+
get enabled() { return layer?.enabled ?? false; },
|
|
529
|
+
prepare() { made(); },
|
|
530
|
+
set(enabled) {
|
|
531
|
+
if (layer || enabled)
|
|
532
|
+
made().set(enabled);
|
|
533
|
+
},
|
|
534
|
+
setSky(colours) {
|
|
535
|
+
sky = colours;
|
|
536
|
+
layer?.setSky(colours);
|
|
537
|
+
},
|
|
538
|
+
setNight(lighting) {
|
|
539
|
+
night = lighting;
|
|
540
|
+
if (layer)
|
|
541
|
+
layer.setNight(lighting);
|
|
542
|
+
else if (lighting)
|
|
543
|
+
made();
|
|
544
|
+
},
|
|
545
|
+
// Until it is made, nothing shows colour or a previewed night for it to refresh.
|
|
546
|
+
refresh() { layer?.refresh(); },
|
|
547
|
+
};
|
|
548
|
+
}
|
package/site/skyline-3d.js
CHANGED
|
@@ -4,7 +4,7 @@ import { chicagoDay, chicagoTime } from "./solar-time.js";
|
|
|
4
4
|
import { createTimeLighting } from "./time-of-day.js";
|
|
5
5
|
import { createGeographicRoads, geographicOrigin } from "./models/skyline-geography.js";
|
|
6
6
|
import { celebrations } from "./models/celebrations.js";
|
|
7
|
-
import { createDaylight,
|
|
7
|
+
import { createDaylight, daylightGround, deferSkylineColour } from "./models/colour-materials.js";
|
|
8
8
|
import { loadMarkup, loadStylesheet } from "./markup.js";
|
|
9
9
|
// The skyline viewer's full-screen 3D mode: every mapped building, seen through the
|
|
10
10
|
// drawing's own camera and framed as the viewer frames the drawing, so each tower stands
|
|
@@ -60,8 +60,9 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
60
60
|
enablePan: true,
|
|
61
61
|
minimumCameraHeight: 1,
|
|
62
62
|
});
|
|
63
|
-
// The colour trial's layer (models/colour-materials.ts), which the colour toggle turns on.
|
|
64
|
-
|
|
63
|
+
// The colour trial's layer (models/colour-materials.ts), which the colour toggle turns on. It
|
|
64
|
+
// is made on first use rather than before the first frame, which it does not change.
|
|
65
|
+
const colour = deferSkylineColour(models);
|
|
65
66
|
// A toolbar badge applies one celebration across the skyline, using each
|
|
66
67
|
// building's own supported wording. Context menus change only their building.
|
|
67
68
|
const illuminated = models.filter((model) => model.illumination);
|
|
@@ -69,6 +70,8 @@ export function startSkyline3d({ root, frame, assets, signal, colour: initiallyC
|
|
|
69
70
|
const celebrationAnnouncement = root.querySelector("#celebration-announcement");
|
|
70
71
|
const celebrationButtons = [...root.querySelectorAll("[data-celebration]")];
|
|
71
72
|
function setCelebration(targets, next) {
|
|
73
|
+
// The colour layer reads the buildings' greys before the lights rewrite them.
|
|
74
|
+
colour.prepare();
|
|
72
75
|
targets.forEach((model) => model.illumination.set(model.illumination.presets.some(({ id }) => id === next) ? next : null));
|
|
73
76
|
colour.refresh();
|
|
74
77
|
for (const button of celebrationButtons) {
|
package/site/vendor/ct-client.js
CHANGED
|
@@ -13,7 +13,7 @@ function safePage(value) {
|
|
|
13
13
|
const path = value.split(/[?#]/, 1)[0] ?? "/";
|
|
14
14
|
return /^\/[a-zA-Z0-9/:_.-]{0,199}$/.test(path) ? path : "/";
|
|
15
15
|
}
|
|
16
|
-
function clickTarget(event, capture) {
|
|
16
|
+
function clickTarget(event, capture, defaultApp = null) {
|
|
17
17
|
const path = event.composedPath();
|
|
18
18
|
const elements = path.filter((item) => item instanceof Element);
|
|
19
19
|
if (elements.some((element) => element.matches("[data-ct-ignore], input, textarea, select, [contenteditable]:not([contenteditable='false'])")))
|
|
@@ -23,8 +23,10 @@ function clickTarget(event, capture) {
|
|
|
23
23
|
if (!target)
|
|
24
24
|
return null;
|
|
25
25
|
const label = target.getAttribute("data-ct");
|
|
26
|
+
const app = elements.find((element) => element.hasAttribute("data-ct-app"))?.getAttribute("data-ct-app");
|
|
26
27
|
const tag = target.tagName.toLowerCase();
|
|
27
28
|
return {
|
|
29
|
+
app: app === undefined ? defaultApp : app && /^[a-z0-9][a-z0-9-]{0,63}$/.test(app) ? app : null,
|
|
28
30
|
target: /^[a-z][a-z0-9-]{0,31}$/.test(tag) ? tag : "unknown",
|
|
29
31
|
label: label && /^[a-zA-Z0-9_.:-]{1,80}$/.test(label) ? label : null
|
|
30
32
|
};
|
|
@@ -80,7 +82,11 @@ function createTracker(options) {
|
|
|
80
82
|
const response = await fetch(endpoint, {
|
|
81
83
|
method: "POST",
|
|
82
84
|
headers: { "Content-Type": "text/plain;charset=UTF-8" },
|
|
83
|
-
body: JSON.stringify({
|
|
85
|
+
body: JSON.stringify({
|
|
86
|
+
site: options.site,
|
|
87
|
+
environment: options.environment,
|
|
88
|
+
events: batch
|
|
89
|
+
}),
|
|
84
90
|
credentials: "omit",
|
|
85
91
|
referrerPolicy: "no-referrer",
|
|
86
92
|
keepalive: true,
|
|
@@ -122,7 +128,7 @@ function createTracker(options) {
|
|
|
122
128
|
function click(event) {
|
|
123
129
|
if (!event.isTrusted || blocked())
|
|
124
130
|
return;
|
|
125
|
-
const target = clickTarget(event, options.capture ?? "interactive");
|
|
131
|
+
const target = clickTarget(event, options.capture ?? "interactive", options.app ?? null);
|
|
126
132
|
if (!target || queue.length >= capacity)
|
|
127
133
|
return;
|
|
128
134
|
try {
|
|
@@ -162,6 +168,10 @@ function createTracker(options) {
|
|
|
162
168
|
return false;
|
|
163
169
|
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(options.site))
|
|
164
170
|
throw new Error("Invalid ct site identifier");
|
|
171
|
+
if (options.app !== undefined && !/^[a-z0-9][a-z0-9-]{0,63}$/.test(options.app))
|
|
172
|
+
throw new Error("Invalid ct app identifier");
|
|
173
|
+
if (options.environment !== undefined && !["production", "staging"].includes(options.environment))
|
|
174
|
+
throw new Error("Invalid ct environment");
|
|
165
175
|
const url = new URL(endpoint);
|
|
166
176
|
if (url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname)))
|
|
167
177
|
throw new Error("ct requires an HTTPS endpoint");
|