@adatechnology/game-devtools 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/MIGRATION.md +209 -0
- package/README.md +121 -2
- package/dist/index.d.ts +270 -0
- package/dist/index.global.js +572 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +547 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -3
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var GameDevtools = (() => {
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
compass: () => compass,
|
|
25
|
+
create: () => create,
|
|
26
|
+
createScenarioRunner: () => createScenarioRunner,
|
|
27
|
+
escapeHtml: () => escapeHtml,
|
|
28
|
+
formatMarkLine: () => formatMarkLine,
|
|
29
|
+
locales: () => locales,
|
|
30
|
+
resolveStrings: () => resolveStrings,
|
|
31
|
+
serializeMarks: () => serializeMarks,
|
|
32
|
+
sortMarks: () => sortMarks
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// src/locale.ts
|
|
36
|
+
var locales = {
|
|
37
|
+
en: {
|
|
38
|
+
compass: ["N", "NE", "E", "SE", "S", "SW", "W", "NW"],
|
|
39
|
+
defaultTeam: "General",
|
|
40
|
+
fileName: "marks.txt",
|
|
41
|
+
markFormLabel: "Mark adjustment",
|
|
42
|
+
panelTitle: "Free mode",
|
|
43
|
+
markHereTitle: "Mark this place",
|
|
44
|
+
lookingAt: "looking",
|
|
45
|
+
teamLabel: "Team",
|
|
46
|
+
notePlaceholder: "What should be adjusted here?",
|
|
47
|
+
saveButton: "Save (Enter)",
|
|
48
|
+
cancelButton: "Cancel (Esc)",
|
|
49
|
+
yes: "yes",
|
|
50
|
+
no: "no",
|
|
51
|
+
placesTitle: "Test places",
|
|
52
|
+
marksTitle: (n) => `Marks (${n})`,
|
|
53
|
+
savedOnServer: "Saved on this page's server: Claude reads them directly, no need to copy.",
|
|
54
|
+
savedInBrowser: "Saved in this browser. To send them: Copy all and paste in the chat, or Download file.",
|
|
55
|
+
noNote: "(no note)",
|
|
56
|
+
goButton: "Go",
|
|
57
|
+
deleteButton: "Delete",
|
|
58
|
+
noMarks: "No marks yet.",
|
|
59
|
+
copyAll: "Copy all",
|
|
60
|
+
downloadFile: "Download file",
|
|
61
|
+
closeButton: "Close (Esc)",
|
|
62
|
+
copied: "Copied",
|
|
63
|
+
copySelected: "Selected: Ctrl+C",
|
|
64
|
+
savedServerMsg: "Mark saved. Claude can already read it",
|
|
65
|
+
serverFailMsg: "Could not save to the server, it stayed in this browser",
|
|
66
|
+
savedBrowserMsg: "Mark saved in this browser. Open the panel to copy",
|
|
67
|
+
hudLook: "look",
|
|
68
|
+
hudFlight: (speed) => `fly ${speed} m/s`,
|
|
69
|
+
hudMarks: (n) => `${n} ${n === 1 ? "mark" : "marks"}`
|
|
70
|
+
},
|
|
71
|
+
"pt-BR": {
|
|
72
|
+
compass: ["N", "NE", "L", "SE", "S", "SO", "O", "NO"],
|
|
73
|
+
defaultTeam: "Geral",
|
|
74
|
+
fileName: "marcas.txt",
|
|
75
|
+
markFormLabel: "Marcar ajuste",
|
|
76
|
+
panelTitle: "Modo livre",
|
|
77
|
+
markHereTitle: "Marcar este lugar",
|
|
78
|
+
lookingAt: "olhando",
|
|
79
|
+
teamLabel: "Equipe",
|
|
80
|
+
notePlaceholder: "O que ajustar aqui?",
|
|
81
|
+
saveButton: "Salvar (Enter)",
|
|
82
|
+
cancelButton: "Cancelar (Esc)",
|
|
83
|
+
yes: "sim",
|
|
84
|
+
no: "n\xE3o",
|
|
85
|
+
placesTitle: "Lugares de teste",
|
|
86
|
+
marksTitle: (n) => `Marcas (${n})`,
|
|
87
|
+
savedOnServer: "Salvas no servidor desta p\xE1gina: Claude l\xEA direto, n\xE3o precisa copiar.",
|
|
88
|
+
savedInBrowser: "Salvas neste navegador. Para mandar: Copiar tudo e colar no chat, ou Baixar arquivo.",
|
|
89
|
+
noNote: "(sem nota)",
|
|
90
|
+
goButton: "Ir",
|
|
91
|
+
deleteButton: "Apagar",
|
|
92
|
+
noMarks: "Nenhuma marca ainda.",
|
|
93
|
+
copyAll: "Copiar tudo",
|
|
94
|
+
downloadFile: "Baixar arquivo",
|
|
95
|
+
closeButton: "Fechar (Esc)",
|
|
96
|
+
copied: "Copiado",
|
|
97
|
+
copySelected: "Selecionado: Ctrl+C",
|
|
98
|
+
savedServerMsg: "Marca salva. Claude j\xE1 consegue ler",
|
|
99
|
+
serverFailMsg: "N\xE3o deu para salvar no servidor, ficou neste navegador",
|
|
100
|
+
savedBrowserMsg: "Marca salva neste navegador. Abra o painel para copiar",
|
|
101
|
+
hudLook: "olhar",
|
|
102
|
+
hudFlight: (speed) => `voo ${speed} m/s`,
|
|
103
|
+
hudMarks: (n) => `${n} marcas`
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var resolveStrings = (locale, overrides) => ({
|
|
107
|
+
...locales[locale] ?? locales.en,
|
|
108
|
+
...overrides
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// src/marks.ts
|
|
112
|
+
var escapeHtml = (s) => String(s ?? "").replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
113
|
+
function compass(yaw, headings) {
|
|
114
|
+
const g = (-yaw * 180 / Math.PI % 360 + 360) % 360;
|
|
115
|
+
return [headings[Math.round(g / 45) % 8], Math.round(g)];
|
|
116
|
+
}
|
|
117
|
+
function formatMarkLine(m, teams, strings) {
|
|
118
|
+
const team = (teams.find((e) => e[0] === m.team) || [0, m.team])[1];
|
|
119
|
+
return `[${team}] ${m.area} \xB7 x ${m.x} y ${m.y} z ${m.z} \xB7 ${strings.lookingAt} ${m.heading} ${m.degrees}\xB0 \xB7 ${m.time}${m.text ? " \xB7 " + m.text : ""}`;
|
|
120
|
+
}
|
|
121
|
+
var sortMarks = (marks) => [...marks].sort((a, b) => b.at - a.at);
|
|
122
|
+
function serializeMarks(marks, teams, strings) {
|
|
123
|
+
const text = sortMarks(marks).map((m) => formatMarkLine(m, teams, strings)).join("\n");
|
|
124
|
+
return { text, file: text + "\n\n" + JSON.stringify(marks) };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/scenarios.ts
|
|
128
|
+
function createScenarioRunner() {
|
|
129
|
+
let list = [];
|
|
130
|
+
let applyFn = () => {
|
|
131
|
+
};
|
|
132
|
+
let checkFn = () => ({});
|
|
133
|
+
let active = null;
|
|
134
|
+
return {
|
|
135
|
+
set(l, a, c) {
|
|
136
|
+
list = l;
|
|
137
|
+
applyFn = a;
|
|
138
|
+
checkFn = c;
|
|
139
|
+
},
|
|
140
|
+
get list() {
|
|
141
|
+
return list;
|
|
142
|
+
},
|
|
143
|
+
get active() {
|
|
144
|
+
return active;
|
|
145
|
+
},
|
|
146
|
+
run(id) {
|
|
147
|
+
const s = list.find((c) => c.id === id);
|
|
148
|
+
if (!s) return false;
|
|
149
|
+
applyFn(s);
|
|
150
|
+
active = s;
|
|
151
|
+
return true;
|
|
152
|
+
},
|
|
153
|
+
check(id) {
|
|
154
|
+
const s = list.find((c) => c.id === id) || active;
|
|
155
|
+
return s ? checkFn(s) : {};
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/devtools.ts
|
|
161
|
+
var noop = () => {
|
|
162
|
+
};
|
|
163
|
+
var CSS = `
|
|
164
|
+
.gd-pos{position:fixed;left:50%;top:calc(14px + env(safe-area-inset-top,0px));transform:translateX(-50%);background:var(--panel,rgba(12,14,14,.72));color:var(--ink,#e6e3d6);border:1px solid var(--line,rgba(230,227,214,.18));padding:5px 10px;font:12px var(--f-mono,ui-monospace,monospace);font-variant-numeric:tabular-nums;white-space:nowrap;pointer-events:none;z-index:3;max-width:calc(100vw - 32px);overflow:hidden;text-overflow:ellipsis}
|
|
165
|
+
.gd-pos b{color:var(--accent,#e3c341);font-weight:600} .gd-pos.flying{border-color:var(--accent,#e3c341)}
|
|
166
|
+
.gd-panel{position:fixed;left:50%;top:50%;transform:translate(-50%,-50%);width:min(520px,calc(100vw - 32px));max-height:calc(100% - 48px);overflow:auto;background:rgba(14,16,16,.96);color:var(--ink,#e6e3d6);border:1px solid var(--accent,#e3c341);padding:16px;display:grid;gap:10px;z-index:9;font:13px var(--f-mono,ui-monospace,monospace)}
|
|
167
|
+
.gd-panel h2{font-family:var(--f-display,inherit);font-weight:800;font-size:26px;margin:0;letter-spacing:.02em}
|
|
168
|
+
.gd-panel textarea,.gd-panel select{font:13px var(--f-mono,ui-monospace,monospace);background:#0b0d0d;color:inherit;border:1px solid var(--line,#444);padding:8px;width:100%}
|
|
169
|
+
.gd-panel textarea{min-height:84px;resize:vertical} .gd-panel select{width:auto;padding:6px}
|
|
170
|
+
.gd-panel button{font:600 13px var(--f-mono,ui-monospace,monospace);padding:7px 12px;cursor:pointer}
|
|
171
|
+
.gd-row{display:flex;gap:6px;flex-wrap:wrap} .gd-sub{color:var(--muted,#9a9a8c);font-size:12px;margin:0}
|
|
172
|
+
.gd-mark{display:grid;grid-template-columns:1fr auto;gap:4px 10px;border-top:1px dashed var(--line,#444);padding-top:8px}
|
|
173
|
+
.gd-mark .t{white-space:pre-wrap;overflow-wrap:anywhere} .gd-mark .w{color:var(--muted,#9a9a8c);font-size:11px} .gd-mark .b{grid-row:1/3;grid-column:2;display:flex;gap:6px;align-items:start}
|
|
174
|
+
.gd-panel .gd-copy{min-height:90px;font-size:11px}
|
|
175
|
+
.gd-labels{position:fixed;inset:0;pointer-events:none;z-index:2;overflow:hidden}
|
|
176
|
+
.gd-labels div{position:absolute;left:0;top:0;transform:translate(-50%,-100%);font:11px var(--f-mono,ui-monospace,monospace);white-space:nowrap;background:rgba(10,12,12,.72);padding:1px 5px;border-left:2px solid;display:none}`;
|
|
177
|
+
function create(THREE, options = {}) {
|
|
178
|
+
const S = resolveStrings(options.locale, options.strings);
|
|
179
|
+
const C = {
|
|
180
|
+
marksStorageKey: "game-devtools-marks",
|
|
181
|
+
collection: "marks",
|
|
182
|
+
fileName: S.fileName,
|
|
183
|
+
teams: [["general", S.defaultTeam]],
|
|
184
|
+
area: (() => ""),
|
|
185
|
+
time: (() => ""),
|
|
186
|
+
notify: noop,
|
|
187
|
+
pause: noop,
|
|
188
|
+
position: (() => ({ x: 0, y: 0, z: 0, yaw: 0, pitch: 0 })),
|
|
189
|
+
bounds: { x: [-1e4, 1e4], y: [0.4, 400], z: [-1e4, 1e4] },
|
|
190
|
+
speed: 14,
|
|
191
|
+
labelsMax: 48,
|
|
192
|
+
labelsDistance: 45,
|
|
193
|
+
...stripUndefined(options)
|
|
194
|
+
};
|
|
195
|
+
const dt0 = {
|
|
196
|
+
toggles: [],
|
|
197
|
+
actions: [],
|
|
198
|
+
times: [],
|
|
199
|
+
setTime: void 0,
|
|
200
|
+
placeGroups: (() => []),
|
|
201
|
+
labelsFn: (() => [])
|
|
202
|
+
};
|
|
203
|
+
const runner = createScenarioRunner();
|
|
204
|
+
const css = document.createElement("style");
|
|
205
|
+
css.textContent = CSS;
|
|
206
|
+
document.head.appendChild(css);
|
|
207
|
+
const el = (tag, cls, parent = document.body) => {
|
|
208
|
+
const e = document.createElement(tag);
|
|
209
|
+
if (cls) e.className = cls;
|
|
210
|
+
parent.appendChild(e);
|
|
211
|
+
return e;
|
|
212
|
+
};
|
|
213
|
+
const posEl = el("div", "gd-pos");
|
|
214
|
+
posEl.id = "gd-pos";
|
|
215
|
+
const form = el("section", "gd-panel");
|
|
216
|
+
form.hidden = true;
|
|
217
|
+
form.id = "gd-form";
|
|
218
|
+
form.setAttribute("aria-label", S.markFormLabel);
|
|
219
|
+
const list = el("section", "gd-panel");
|
|
220
|
+
list.hidden = true;
|
|
221
|
+
list.id = "gd-list";
|
|
222
|
+
list.setAttribute("aria-label", S.panelTitle);
|
|
223
|
+
const labelBox = el("div", "gd-labels");
|
|
224
|
+
labelBox.id = "gd-labels";
|
|
225
|
+
const LABELS = [];
|
|
226
|
+
for (let k = 0; k < C.labelsMax; k++) LABELS.push(el("div", "", labelBox));
|
|
227
|
+
const dev = {
|
|
228
|
+
flying: false,
|
|
229
|
+
x: 0,
|
|
230
|
+
y: 0,
|
|
231
|
+
z: 0,
|
|
232
|
+
speed: C.speed,
|
|
233
|
+
hud: true,
|
|
234
|
+
openPanel: null,
|
|
235
|
+
showLabels: false,
|
|
236
|
+
db: null,
|
|
237
|
+
marks: [],
|
|
238
|
+
goTo: null,
|
|
239
|
+
extras: (() => ""),
|
|
240
|
+
openGroups: [0]
|
|
241
|
+
};
|
|
242
|
+
Object.defineProperties(dev, {
|
|
243
|
+
scenarioList: { get: () => runner.list, enumerable: true },
|
|
244
|
+
activeScenario: { get: () => runner.active, enumerable: true }
|
|
245
|
+
});
|
|
246
|
+
dev.compass = (yaw) => compass(yaw, S.compass);
|
|
247
|
+
dev.here = () => {
|
|
248
|
+
const p = C.position();
|
|
249
|
+
const [heading, degrees] = dev.compass(p.yaw);
|
|
250
|
+
return {
|
|
251
|
+
x: +p.x.toFixed(1),
|
|
252
|
+
y: +p.y.toFixed(1),
|
|
253
|
+
z: +p.z.toFixed(1),
|
|
254
|
+
yaw: +p.yaw.toFixed(3),
|
|
255
|
+
pitch: +p.pitch.toFixed(3),
|
|
256
|
+
area: C.area(p.x, p.z),
|
|
257
|
+
heading,
|
|
258
|
+
degrees,
|
|
259
|
+
look: Math.round(-p.pitch * 180 / Math.PI),
|
|
260
|
+
time: C.time(),
|
|
261
|
+
flying: dev.flying
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
dev.line = (m) => formatMarkLine(m, C.teams, S);
|
|
265
|
+
dev.fly = (dt, yaw, pitch, k) => {
|
|
266
|
+
const sp = dev.speed * (k.fast ? 4 : 1) * dt;
|
|
267
|
+
const cy = Math.cos(pitch);
|
|
268
|
+
const sy = Math.sin(pitch);
|
|
269
|
+
const fx = -Math.sin(yaw);
|
|
270
|
+
const fz = -Math.cos(yaw);
|
|
271
|
+
const L = C.bounds;
|
|
272
|
+
dev.x += (fx * cy * k.forward - fz * k.strafe) * sp;
|
|
273
|
+
dev.z += (fz * cy * k.forward + fx * k.strafe) * sp;
|
|
274
|
+
dev.y += (-sy * k.forward + k.up) * sp;
|
|
275
|
+
dev.x = Math.min(L.x[1], Math.max(L.x[0], dev.x));
|
|
276
|
+
dev.y = Math.min(L.y[1], Math.max(L.y[0], dev.y));
|
|
277
|
+
dev.z = Math.min(L.z[1], Math.max(L.z[0], dev.z));
|
|
278
|
+
};
|
|
279
|
+
dev.flyCamera = (cam, yaw, pitch) => {
|
|
280
|
+
const cy = Math.cos(pitch);
|
|
281
|
+
cam.position.set(dev.x, dev.y, dev.z);
|
|
282
|
+
cam.lookAt(dev.x - Math.sin(yaw) * cy, dev.y - Math.sin(pitch), dev.z - Math.cos(yaw) * cy);
|
|
283
|
+
};
|
|
284
|
+
const onWheel = (e) => {
|
|
285
|
+
if (dev.flying && !dev.openPanel) dev.speed = Math.min(120, Math.max(2, dev.speed * (e.deltaY > 0 ? 0.8 : 1.25)));
|
|
286
|
+
};
|
|
287
|
+
window.addEventListener("wheel", onWheel, { passive: true });
|
|
288
|
+
dev.toggle = (id, label, get, set) => {
|
|
289
|
+
dt0.toggles.push({ id, label, get, set });
|
|
290
|
+
return dev;
|
|
291
|
+
};
|
|
292
|
+
dev.action = (id, label, fn) => {
|
|
293
|
+
dt0.actions.push({ id, label, fn });
|
|
294
|
+
return dev;
|
|
295
|
+
};
|
|
296
|
+
dev.timesOfDay = (l, set) => {
|
|
297
|
+
dt0.times = l;
|
|
298
|
+
dt0.setTime = set;
|
|
299
|
+
return dev;
|
|
300
|
+
};
|
|
301
|
+
dev.places = (fn) => {
|
|
302
|
+
dt0.placeGroups = fn;
|
|
303
|
+
return dev;
|
|
304
|
+
};
|
|
305
|
+
dev.labelsFor = (fn) => {
|
|
306
|
+
dt0.labelsFn = fn;
|
|
307
|
+
return dev;
|
|
308
|
+
};
|
|
309
|
+
dev.scenarios = (l, apply, check) => {
|
|
310
|
+
runner.set(l, apply, check);
|
|
311
|
+
return dev;
|
|
312
|
+
};
|
|
313
|
+
dev.scenario = (id) => runner.run(id);
|
|
314
|
+
dev.check = (id) => runner.check(id);
|
|
315
|
+
const lsRead = () => {
|
|
316
|
+
try {
|
|
317
|
+
return JSON.parse(localStorage.getItem(C.marksStorageKey) || "[]");
|
|
318
|
+
} catch (e) {
|
|
319
|
+
return [];
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
const lsWrite = (a) => {
|
|
323
|
+
try {
|
|
324
|
+
localStorage.setItem(C.marksStorageKey, JSON.stringify(a));
|
|
325
|
+
} catch (e) {
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
dev.marks = lsRead();
|
|
329
|
+
(async () => {
|
|
330
|
+
try {
|
|
331
|
+
if (!window.claude || !window.claude.use) return;
|
|
332
|
+
const db = await window.claude.use("db");
|
|
333
|
+
if (!db) return;
|
|
334
|
+
dev.db = db;
|
|
335
|
+
db.collection(C.collection).onSnapshot(
|
|
336
|
+
(s) => {
|
|
337
|
+
dev.marks = s.docs.map((d) => ({ id: d.id, ...d.data() }));
|
|
338
|
+
if (dev.openPanel === "list") render();
|
|
339
|
+
},
|
|
340
|
+
() => {
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
} catch (e) {
|
|
344
|
+
}
|
|
345
|
+
})();
|
|
346
|
+
async function saveMark(m) {
|
|
347
|
+
if (dev.db) {
|
|
348
|
+
try {
|
|
349
|
+
const u = await window.claude.use("user");
|
|
350
|
+
if (u) m.by = await u.id();
|
|
351
|
+
} catch (e) {
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
await dev.db.collection(C.collection).add(m);
|
|
355
|
+
C.notify(S.savedServerMsg, 2);
|
|
356
|
+
return;
|
|
357
|
+
} catch (e) {
|
|
358
|
+
C.notify(S.serverFailMsg, 3);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
m.id = "m" + m.at;
|
|
362
|
+
const a = lsRead();
|
|
363
|
+
a.push(m);
|
|
364
|
+
lsWrite(a);
|
|
365
|
+
dev.marks = a;
|
|
366
|
+
C.notify(S.savedBrowserMsg, 2.5);
|
|
367
|
+
}
|
|
368
|
+
async function deleteMark(id) {
|
|
369
|
+
if (dev.db && !id.startsWith("m")) {
|
|
370
|
+
try {
|
|
371
|
+
await dev.db.collection(C.collection).doc(id).delete();
|
|
372
|
+
} catch (e) {
|
|
373
|
+
}
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
const a = lsRead().filter((m) => m.id !== id);
|
|
377
|
+
lsWrite(a);
|
|
378
|
+
dev.marks = a;
|
|
379
|
+
render();
|
|
380
|
+
}
|
|
381
|
+
dev.goTo = null;
|
|
382
|
+
let here0 = null;
|
|
383
|
+
dev.open = (which) => {
|
|
384
|
+
if (dev.openPanel) return;
|
|
385
|
+
dev.openPanel = which;
|
|
386
|
+
C.pause(true);
|
|
387
|
+
if (which === "form") {
|
|
388
|
+
here0 = dev.here();
|
|
389
|
+
form.innerHTML = `<h2>${escapeHtml(S.markHereTitle)}</h2><p class="gd-sub">${escapeHtml(`${here0.area} \xB7 x ${here0.x} y ${here0.y} z ${here0.z} \xB7 ${S.lookingAt} ${here0.heading} \xB7 ${here0.time}`)}</p>
|
|
390
|
+
<label class="gd-sub">${escapeHtml(S.teamLabel)} <select id="gd-team">${C.teams.map(([k, n]) => `<option value="${escapeHtml(k)}">${escapeHtml(n)}</option>`).join("")}</select></label>
|
|
391
|
+
<textarea id="gd-note" placeholder="${escapeHtml(S.notePlaceholder)}"></textarea>
|
|
392
|
+
<div class="gd-row"><button type="button" data-f="save">${escapeHtml(S.saveButton)}</button><button type="button" class="ghost" data-f="close">${escapeHtml(S.cancelButton)}</button></div>`;
|
|
393
|
+
form.hidden = false;
|
|
394
|
+
setTimeout(() => form.querySelector("textarea").focus(), 30);
|
|
395
|
+
} else {
|
|
396
|
+
render();
|
|
397
|
+
list.hidden = false;
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
dev.close = () => {
|
|
401
|
+
if (!dev.openPanel) return;
|
|
402
|
+
dev.openPanel = null;
|
|
403
|
+
form.hidden = list.hidden = true;
|
|
404
|
+
C.pause(false);
|
|
405
|
+
};
|
|
406
|
+
form.addEventListener("keydown", (e) => {
|
|
407
|
+
e.stopPropagation();
|
|
408
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
409
|
+
e.preventDefault();
|
|
410
|
+
form.querySelector('[data-f="save"]').click();
|
|
411
|
+
}
|
|
412
|
+
if (e.key === "Escape") {
|
|
413
|
+
e.preventDefault();
|
|
414
|
+
dev.close();
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
form.addEventListener("click", (e) => {
|
|
418
|
+
const b = e.target.closest("button");
|
|
419
|
+
if (!b) return;
|
|
420
|
+
if (b.dataset.f === "save")
|
|
421
|
+
saveMark({
|
|
422
|
+
...here0,
|
|
423
|
+
team: form.querySelector("#gd-team").value,
|
|
424
|
+
text: form.querySelector("#gd-note").value.trim(),
|
|
425
|
+
at: Date.now()
|
|
426
|
+
});
|
|
427
|
+
dev.close();
|
|
428
|
+
});
|
|
429
|
+
let places = [];
|
|
430
|
+
function render() {
|
|
431
|
+
const ms = sortMarks(dev.marks);
|
|
432
|
+
places = dt0.placeGroups();
|
|
433
|
+
const toggleBtn = (c) => {
|
|
434
|
+
const on = !!c.get();
|
|
435
|
+
return `<button type="button" class="${on ? "" : "ghost"}" data-toggle="${escapeHtml(c.id)}">${escapeHtml(c.label)}: ${on ? S.yes : S.no}</button>`;
|
|
436
|
+
};
|
|
437
|
+
list.innerHTML = `<h2>${escapeHtml(S.panelTitle)}</h2>
|
|
438
|
+
<div class="gd-row">${dt0.toggles.map(toggleBtn).join("")}</div>
|
|
439
|
+
${dt0.actions.length ? `<div class="gd-row">${dt0.actions.map((a) => `<button type="button" class="ghost" data-action="${escapeHtml(a.id)}">${escapeHtml(typeof a.label === "function" ? a.label() : a.label)}</button>`).join("")}</div>` : ""}
|
|
440
|
+
<h2>${escapeHtml(S.placesTitle)}</h2>
|
|
441
|
+
${dt0.times.length ? `<div class="gd-row">${dt0.times.map((h, i) => `<button type="button" class="ghost" data-time="${i}">${escapeHtml(h[0])}</button>`).join("")}</div>` : ""}
|
|
442
|
+
${places.map(([n, ps], i) => `<details data-g="${i}"${dev.openGroups.includes(i) ? " open" : ""}><summary class="gd-sub">${escapeHtml(n)} (${ps.length})</summary><div class="gd-row" style="margin-top:6px">${ps.map((p, j) => `<button type="button" class="ghost" data-place="${i}:${j}">${escapeHtml(p[0])}</button>`).join("")}</div></details>`).join("")}
|
|
443
|
+
<h2>${escapeHtml(S.marksTitle(ms.length))}</h2>
|
|
444
|
+
<p class="gd-sub">${dev.db ? S.savedOnServer : S.savedInBrowser}</p>
|
|
445
|
+
${ms.map((m) => `<div class="gd-mark"><div class="t">${escapeHtml(m.text || S.noNote)}</div><div class="w">${escapeHtml(dev.line({ ...m, text: "" }))}</div><div class="b"><button type="button" data-goto="${escapeHtml(m.id)}">${escapeHtml(S.goButton)}</button><button type="button" class="ghost" data-delete="${escapeHtml(m.id)}">${escapeHtml(S.deleteButton)}</button></div></div>`).join("") || `<p class="gd-sub">${escapeHtml(S.noMarks)}</p>`}
|
|
446
|
+
<textarea class="gd-copy" id="gd-copy" readonly>${escapeHtml(ms.map(dev.line).join("\n"))}</textarea>
|
|
447
|
+
<div class="gd-row"><button type="button" data-f="copy">${escapeHtml(S.copyAll)}</button>${dev.db ? "" : `<button type="button" class="ghost" data-f="download" id="gd-download">${escapeHtml(S.downloadFile)}</button>`}<button type="button" class="ghost" data-f="close">${escapeHtml(S.closeButton)}</button></div>`;
|
|
448
|
+
}
|
|
449
|
+
dev.render = render;
|
|
450
|
+
list.addEventListener(
|
|
451
|
+
"toggle",
|
|
452
|
+
(e) => {
|
|
453
|
+
const d = e.target.closest("details");
|
|
454
|
+
if (!d) return;
|
|
455
|
+
const i = +d.dataset.g;
|
|
456
|
+
const a = new Set(dev.openGroups);
|
|
457
|
+
if (d.open) a.add(i);
|
|
458
|
+
else a.delete(i);
|
|
459
|
+
dev.openGroups = [...a];
|
|
460
|
+
},
|
|
461
|
+
true
|
|
462
|
+
);
|
|
463
|
+
list.addEventListener("click", async (e) => {
|
|
464
|
+
const b = e.target.closest("button");
|
|
465
|
+
if (!b) return;
|
|
466
|
+
const d = b.dataset;
|
|
467
|
+
if (d.f === "close") return dev.close();
|
|
468
|
+
if (d.f === "download") {
|
|
469
|
+
const a = document.createElement("a");
|
|
470
|
+
a.href = URL.createObjectURL(new Blob([serializeMarks(dev.marks, C.teams, S).file], { type: "text/plain" }));
|
|
471
|
+
a.download = C.fileName;
|
|
472
|
+
document.body.appendChild(a);
|
|
473
|
+
a.click();
|
|
474
|
+
a.remove();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (d.f === "copy") {
|
|
478
|
+
const t = list.querySelector("#gd-copy");
|
|
479
|
+
try {
|
|
480
|
+
await navigator.clipboard.writeText(t.value);
|
|
481
|
+
b.textContent = S.copied;
|
|
482
|
+
} catch (err) {
|
|
483
|
+
t.select();
|
|
484
|
+
b.textContent = S.copySelected;
|
|
485
|
+
}
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if (d.place) {
|
|
489
|
+
const [i, j] = d.place.split(":").map(Number);
|
|
490
|
+
dev.close();
|
|
491
|
+
places[i][1][j][1]();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (d.toggle) {
|
|
495
|
+
const c = dt0.toggles.find((c2) => c2.id === d.toggle);
|
|
496
|
+
const close = c.set(!c.get());
|
|
497
|
+
return close ? dev.close() : render();
|
|
498
|
+
}
|
|
499
|
+
if (d.action) {
|
|
500
|
+
const a = dt0.actions.find((a2) => a2.id === d.action);
|
|
501
|
+
const keep = a.fn();
|
|
502
|
+
return keep ? render() : dev.close();
|
|
503
|
+
}
|
|
504
|
+
if (d.time) {
|
|
505
|
+
dt0.setTime(dt0.times[+d.time][1]);
|
|
506
|
+
return render();
|
|
507
|
+
}
|
|
508
|
+
if (d.goto) {
|
|
509
|
+
const m = dev.marks.find((m2) => m2.id === d.goto);
|
|
510
|
+
if (m && dev.goTo) {
|
|
511
|
+
dev.close();
|
|
512
|
+
dev.goTo(m.x, m.y, m.z, m.yaw, m.pitch);
|
|
513
|
+
}
|
|
514
|
+
return;
|
|
515
|
+
}
|
|
516
|
+
if (d.delete) deleteMark(d.delete);
|
|
517
|
+
});
|
|
518
|
+
const onKeydown = (e) => {
|
|
519
|
+
if (dev.openPanel === "list" && e.key === "Escape" && !e.defaultPrevented) dev.close();
|
|
520
|
+
};
|
|
521
|
+
window.addEventListener("keydown", onKeydown);
|
|
522
|
+
let acc = 0;
|
|
523
|
+
const _v = new THREE.Vector3();
|
|
524
|
+
dev.extras = () => "";
|
|
525
|
+
dev.update = (dt, cam, visible = true) => {
|
|
526
|
+
let k = 0;
|
|
527
|
+
if (dev.showLabels && visible) {
|
|
528
|
+
const W = innerWidth;
|
|
529
|
+
const H = innerHeight;
|
|
530
|
+
for (const t of dt0.labelsFn(cam)) {
|
|
531
|
+
if (k >= LABELS.length) break;
|
|
532
|
+
_v.set(t.x, t.y, t.z).project(cam);
|
|
533
|
+
if (_v.z > 1 || _v.x < -1.05 || _v.x > 1.05 || _v.y < -1.05 || _v.y > 1.05) continue;
|
|
534
|
+
const d = LABELS[k++];
|
|
535
|
+
if (d.textContent !== t.text) d.textContent = t.text;
|
|
536
|
+
d.style.borderColor = d.style.color = t.color || "#9fd0ff";
|
|
537
|
+
d.style.left = ((_v.x + 1) / 2 * W).toFixed(0) + "px";
|
|
538
|
+
d.style.top = ((1 - _v.y) / 2 * H).toFixed(0) + "px";
|
|
539
|
+
d.style.display = "";
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
for (; k < LABELS.length; k++) if (LABELS[k].style.display !== "none") LABELS[k].style.display = "none";
|
|
543
|
+
posEl.hidden = !dev.hud || !visible;
|
|
544
|
+
posEl.classList.toggle("flying", dev.flying);
|
|
545
|
+
if ((acc += Math.max(0, dt || 0)) < 0.1) return;
|
|
546
|
+
acc = 0;
|
|
547
|
+
const a = dev.here();
|
|
548
|
+
posEl.innerHTML = `<b>${escapeHtml(a.area)}</b> \xB7 x ${a.x} \xB7 y ${a.y} \xB7 z ${a.z} \xB7 ${a.heading} ${a.degrees}\xB0 \xB7 ${S.hudLook} ${a.look}\xB0 \xB7 ${escapeHtml(a.time)}${dev.flying ? ` \xB7 <b>${S.hudFlight(dev.speed.toFixed(0))}</b>` : ""}${dev.extras()} \xB7 ${S.hudMarks(dev.marks.length)}`;
|
|
549
|
+
};
|
|
550
|
+
dev.expose = () => {
|
|
551
|
+
window.__scenario = dev.scenario;
|
|
552
|
+
window.__scenarioCheck = dev.check;
|
|
553
|
+
window.__scenarioList = runner.list;
|
|
554
|
+
window.__devtools = dev;
|
|
555
|
+
};
|
|
556
|
+
dev.dispose = () => {
|
|
557
|
+
window.removeEventListener("wheel", onWheel);
|
|
558
|
+
window.removeEventListener("keydown", onKeydown);
|
|
559
|
+
css.remove();
|
|
560
|
+
posEl.remove();
|
|
561
|
+
form.remove();
|
|
562
|
+
list.remove();
|
|
563
|
+
labelBox.remove();
|
|
564
|
+
};
|
|
565
|
+
return dev;
|
|
566
|
+
}
|
|
567
|
+
function stripUndefined(o) {
|
|
568
|
+
return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
|
|
569
|
+
}
|
|
570
|
+
return __toCommonJS(index_exports);
|
|
571
|
+
})();
|
|
572
|
+
//# sourceMappingURL=index.global.js.map
|