@uzuhq/code-cli 0.5.7 → 0.5.8
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/cli.js +2484 -289
- package/dist/dev-server/sdk-server-shim.js +21 -0
- package/dist/harness/client-entry.js +2779 -74
- package/package.json +5 -4
- package/dist/auth/browser.js +0 -22
- package/dist/auth/config.js +0 -48
- package/dist/auth/env.js +0 -42
- package/dist/auth/jwt.js +0 -27
- package/dist/auth/login-flow.js +0 -44
- package/dist/auth/loopback.js +0 -94
- package/dist/auth/pkce.js +0 -11
- package/dist/auth/publish-token.js +0 -74
- package/dist/auth/token-cache.js +0 -70
- package/dist/auth/uzu-auth.js +0 -94
- package/dist/build-server-logic.js +0 -28
- package/dist/cf-images-upload.js +0 -52
- package/dist/create-2d-game.js +0 -51
- package/dist/dev-server/game-room.js +0 -631
- package/dist/dev-server/game-types.js +0 -11
- package/dist/dev-server/json-patch.js +0 -114
- package/dist/dev-server/load-logic.js +0 -70
- package/dist/dev-server/random.js +0 -35
- package/dist/dev-server/relay-room.js +0 -86
- package/dist/dev-server/server.js +0 -367
- package/dist/dev-server/sync-room.js +0 -270
- package/dist/dev.js +0 -235
- package/dist/harness/admin-client.js +0 -215
- package/dist/harness/dev-button.js +0 -258
- package/dist/harness/mount.js +0 -790
- package/dist/harness/page.js +0 -46
- package/dist/r2-upload.js +0 -93
- package/dist/rest-register.js +0 -52
- package/dist/sdk-version.js +0 -41
- package/dist/upload-session.js +0 -71
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* parent frame の `window.__uzu_dev` を dev-server 側の管理 channel (`/dev/admin`) に
|
|
3
|
-
* 繋ぐ薄いクライアント。 SDK の `attachDevHooks({...})` と同じ shape の hook を
|
|
4
|
-
* expose する。
|
|
5
|
-
*
|
|
6
|
-
* message shape は server.ts の handleAdminConnection と同じ。
|
|
7
|
-
* Client → Server: `{ type: 'call', id, method, args? }`
|
|
8
|
-
* `{ type: 'subscribe', id, kind: 'snapshot' | 'events' }`
|
|
9
|
-
* `{ type: 'unsubscribe', id }`
|
|
10
|
-
* Server → Client: `{ type: 'result', id, value }` / `{ type: 'error', id, error }`
|
|
11
|
-
* `{ type: 'event', id, value }` / `{ type: 'missing', id }`
|
|
12
|
-
*/
|
|
13
|
-
export function attachAdminClient(adminUrl) {
|
|
14
|
-
const pending = new Map();
|
|
15
|
-
const subscribers = new Map();
|
|
16
|
-
let idCounter = 0;
|
|
17
|
-
let ws = null;
|
|
18
|
-
let ready = null;
|
|
19
|
-
let latestSnapshot = null;
|
|
20
|
-
const localSnapshotSubs = new Set();
|
|
21
|
-
const localEventSubs = new Set();
|
|
22
|
-
const connect = () => {
|
|
23
|
-
if (ready)
|
|
24
|
-
return ready;
|
|
25
|
-
ready = new Promise((resolve, reject) => {
|
|
26
|
-
const socket = new WebSocket(adminUrl);
|
|
27
|
-
ws = socket;
|
|
28
|
-
socket.addEventListener('open', () => {
|
|
29
|
-
// 常時 snapshot subscription を張っておき、 waitForSnapshot / subscribeSnapshot
|
|
30
|
-
// の同期パス (getSnapshot() = latestSnapshot) を暖める。
|
|
31
|
-
void call('snapshot_subscribe', 'subscribe', {
|
|
32
|
-
kind: 'snapshot',
|
|
33
|
-
}).catch(() => {
|
|
34
|
-
/* ignore — server が未対応でも subscribeSnapshot は動く */
|
|
35
|
-
});
|
|
36
|
-
subscribers.set('snapshot_subscribe', (value) => {
|
|
37
|
-
latestSnapshot = value;
|
|
38
|
-
localSnapshotSubs.forEach((cb) => {
|
|
39
|
-
try {
|
|
40
|
-
cb(value);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
console.warn('[__uzu_dev] snapshot listener threw:', err);
|
|
44
|
-
}
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
void call('events_subscribe', 'subscribe', {
|
|
48
|
-
kind: 'events',
|
|
49
|
-
}).catch(() => {
|
|
50
|
-
/* ignore */
|
|
51
|
-
});
|
|
52
|
-
subscribers.set('events_subscribe', (value) => {
|
|
53
|
-
localEventSubs.forEach((cb) => {
|
|
54
|
-
try {
|
|
55
|
-
cb(value);
|
|
56
|
-
}
|
|
57
|
-
catch (err) {
|
|
58
|
-
console.warn('[__uzu_dev] events listener threw:', err);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
resolve();
|
|
63
|
-
});
|
|
64
|
-
socket.addEventListener('message', (ev) => {
|
|
65
|
-
let msg;
|
|
66
|
-
try {
|
|
67
|
-
msg = JSON.parse(ev.data);
|
|
68
|
-
}
|
|
69
|
-
catch {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
const id = msg.id;
|
|
73
|
-
if (msg.type === 'result') {
|
|
74
|
-
const p = pending.get(id);
|
|
75
|
-
if (p) {
|
|
76
|
-
p.resolve(msg.value);
|
|
77
|
-
pending.delete(id);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else if (msg.type === 'error') {
|
|
81
|
-
const p = pending.get(id);
|
|
82
|
-
if (p) {
|
|
83
|
-
p.reject(new Error(msg.error));
|
|
84
|
-
pending.delete(id);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
else if (msg.type === 'missing') {
|
|
88
|
-
const p = pending.get(id);
|
|
89
|
-
if (p) {
|
|
90
|
-
p.resolve(undefined);
|
|
91
|
-
pending.delete(id);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
else if (msg.type === 'event') {
|
|
95
|
-
const sub = subscribers.get(id);
|
|
96
|
-
if (sub)
|
|
97
|
-
sub(msg.value);
|
|
98
|
-
}
|
|
99
|
-
});
|
|
100
|
-
socket.addEventListener('error', () => reject(new Error('admin channel error')));
|
|
101
|
-
socket.addEventListener('close', () => {
|
|
102
|
-
ws = null;
|
|
103
|
-
ready = null;
|
|
104
|
-
// 全 pending を reject
|
|
105
|
-
pending.forEach((p) => p.reject(new Error('admin channel closed')));
|
|
106
|
-
pending.clear();
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
return ready;
|
|
110
|
-
};
|
|
111
|
-
const nextId = (prefix) => {
|
|
112
|
-
idCounter++;
|
|
113
|
-
return `${prefix}-${idCounter}`;
|
|
114
|
-
};
|
|
115
|
-
const call = (id, type, body) => {
|
|
116
|
-
return new Promise((resolve, reject) => {
|
|
117
|
-
if (!ws) {
|
|
118
|
-
reject(new Error('admin channel not connected'));
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
pending.set(id, { resolve, reject });
|
|
122
|
-
ws.send(JSON.stringify({ type, id, ...body }));
|
|
123
|
-
});
|
|
124
|
-
};
|
|
125
|
-
const callMethod = async (method, args = []) => {
|
|
126
|
-
await connect();
|
|
127
|
-
const id = nextId('call');
|
|
128
|
-
return call(id, 'call', { method, args });
|
|
129
|
-
};
|
|
130
|
-
const hooks = {
|
|
131
|
-
getSnapshot: () => latestSnapshot,
|
|
132
|
-
getRawState: () => latestSnapshot,
|
|
133
|
-
playerId: () => 'host',
|
|
134
|
-
subscribeSnapshot: (cb) => {
|
|
135
|
-
localSnapshotSubs.add(cb);
|
|
136
|
-
return () => {
|
|
137
|
-
localSnapshotSubs.delete(cb);
|
|
138
|
-
};
|
|
139
|
-
},
|
|
140
|
-
waitForSnapshot: (predicate, options) => {
|
|
141
|
-
const timeoutMs = options?.timeoutMs ?? 10_000;
|
|
142
|
-
return new Promise((resolve, reject) => {
|
|
143
|
-
if (latestSnapshot != null) {
|
|
144
|
-
try {
|
|
145
|
-
if (predicate(latestSnapshot)) {
|
|
146
|
-
resolve(latestSnapshot);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
catch (err) {
|
|
151
|
-
reject(err);
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
const timer = setTimeout(() => {
|
|
156
|
-
localSnapshotSubs.delete(listener);
|
|
157
|
-
reject(new Error(`[__uzu_dev] waitForSnapshot timed out after ${timeoutMs}ms`));
|
|
158
|
-
}, timeoutMs);
|
|
159
|
-
const listener = (snap) => {
|
|
160
|
-
try {
|
|
161
|
-
if (predicate(snap)) {
|
|
162
|
-
clearTimeout(timer);
|
|
163
|
-
localSnapshotSubs.delete(listener);
|
|
164
|
-
resolve(snap);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
clearTimeout(timer);
|
|
169
|
-
localSnapshotSubs.delete(listener);
|
|
170
|
-
reject(err);
|
|
171
|
-
}
|
|
172
|
-
};
|
|
173
|
-
localSnapshotSubs.add(listener);
|
|
174
|
-
});
|
|
175
|
-
},
|
|
176
|
-
setRawState: async (state) => {
|
|
177
|
-
await callMethod('setRawState', [state]);
|
|
178
|
-
},
|
|
179
|
-
mergeRawState: async (patch) => {
|
|
180
|
-
await callMethod('mergeRawState', [patch]);
|
|
181
|
-
},
|
|
182
|
-
patchRawState: async (ops) => {
|
|
183
|
-
await callMethod('patchRawState', [ops]);
|
|
184
|
-
},
|
|
185
|
-
send: async (args) => {
|
|
186
|
-
await callMethod('sendAction', [args]);
|
|
187
|
-
},
|
|
188
|
-
subscribeEvents: (cb) => {
|
|
189
|
-
localEventSubs.add(cb);
|
|
190
|
-
return () => {
|
|
191
|
-
localEventSubs.delete(cb);
|
|
192
|
-
};
|
|
193
|
-
},
|
|
194
|
-
pauseTick: () => {
|
|
195
|
-
void callMethod('pauseTick');
|
|
196
|
-
},
|
|
197
|
-
resumeTick: () => {
|
|
198
|
-
void callMethod('resumeTick');
|
|
199
|
-
},
|
|
200
|
-
stepTick: (n) => {
|
|
201
|
-
void callMethod('stepTick', [n ?? 1]);
|
|
202
|
-
},
|
|
203
|
-
// getSeed / getCurrentTick / isTickPaused は admin channel 越しだと非同期取得になり、
|
|
204
|
-
// 同期シグネチャでは値が届く前に初期値を返す嘘 API になるため remote proxy では公開しない。
|
|
205
|
-
// 状態追跡は subscribeSnapshot / waitForSnapshot を使う。
|
|
206
|
-
reset: (opts) => {
|
|
207
|
-
void callMethod('reset', [opts]);
|
|
208
|
-
},
|
|
209
|
-
};
|
|
210
|
-
// 初回接続を kick する (非同期で完了)。
|
|
211
|
-
void connect().catch((err) => {
|
|
212
|
-
console.warn('[__uzu_dev] admin channel connect failed:', err);
|
|
213
|
-
});
|
|
214
|
-
return hooks;
|
|
215
|
-
}
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UZU ボタンのタップメニュー (Emulator / State Inspector / Reset / 実機で開く) と、
|
|
3
|
-
* 共有の State Inspector overlay。
|
|
4
|
-
*
|
|
5
|
-
* 本番アプリ (Flutter GamePlayScreen) は各プレイヤー画面の左上に UZU ボタン +
|
|
6
|
-
* ActionBar を描画する。 harness も同じ仕様に合わせ、 UZU ボタンは各 iframe cell
|
|
7
|
-
* (mount.ts) 側に置き、 そのボタンへ本メニューを attach する。
|
|
8
|
-
* harness client bundle 内で browser 実行される (Node import 依存なし)。
|
|
9
|
-
*/
|
|
10
|
-
// State Inspector はどの cell の UZU メニューから開いても同じ 1 枚を使う (room は共有)。
|
|
11
|
-
let inspectorEl = null;
|
|
12
|
-
let inspectorTimer = null;
|
|
13
|
-
function isInspectorOpen() {
|
|
14
|
-
return inspectorEl != null;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* UZU ボタン (button 要素) にタップメニューを配線する。 メニューはボタン直下に出る。
|
|
18
|
-
*/
|
|
19
|
-
export function attachUzuMenu(button, opts = {}) {
|
|
20
|
-
const { stateGetter, playerCount, adminEnabled, resetGame, onOpenMobile, onOpenSingleView } = opts;
|
|
21
|
-
let popup = null;
|
|
22
|
-
const closePopup = () => {
|
|
23
|
-
popup?.remove();
|
|
24
|
-
popup = null;
|
|
25
|
-
};
|
|
26
|
-
button.addEventListener('click', (ev) => {
|
|
27
|
-
ev.stopPropagation();
|
|
28
|
-
if (popup) {
|
|
29
|
-
closePopup();
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
const el = document.createElement('div');
|
|
33
|
-
popup = el;
|
|
34
|
-
el.style.cssText = [
|
|
35
|
-
'position:fixed',
|
|
36
|
-
'z-index:100001',
|
|
37
|
-
'background:#2d3436',
|
|
38
|
-
'border-radius:8px',
|
|
39
|
-
'padding:12px',
|
|
40
|
-
'box-shadow:0 4px 16px rgba(0,0,0,0.4)',
|
|
41
|
-
'font-family:system-ui,sans-serif',
|
|
42
|
-
'min-width:200px',
|
|
43
|
-
'display:flex',
|
|
44
|
-
'flex-direction:column',
|
|
45
|
-
'gap:8px',
|
|
46
|
-
].join(';');
|
|
47
|
-
const addSeparator = () => {
|
|
48
|
-
if (!el.children.length)
|
|
49
|
-
return;
|
|
50
|
-
const sep = document.createElement('div');
|
|
51
|
-
sep.style.cssText = 'border-top:1px solid #44475a;';
|
|
52
|
-
el.appendChild(sep);
|
|
53
|
-
};
|
|
54
|
-
const addAction = (label, bg, hoverBg, onClick) => {
|
|
55
|
-
const b = document.createElement('button');
|
|
56
|
-
b.textContent = label;
|
|
57
|
-
b.style.cssText = [
|
|
58
|
-
'display:block',
|
|
59
|
-
'width:100%',
|
|
60
|
-
'padding:8px 24px',
|
|
61
|
-
'border:none',
|
|
62
|
-
'border-radius:4px',
|
|
63
|
-
`background:${bg}`,
|
|
64
|
-
'color:#fff',
|
|
65
|
-
'font-size:13px',
|
|
66
|
-
'cursor:pointer',
|
|
67
|
-
'font-family:system-ui,sans-serif',
|
|
68
|
-
].join(';');
|
|
69
|
-
b.onmouseenter = () => {
|
|
70
|
-
b.style.background = hoverBg;
|
|
71
|
-
b.style.color = '#2d3436';
|
|
72
|
-
};
|
|
73
|
-
b.onmouseleave = () => {
|
|
74
|
-
b.style.background = bg;
|
|
75
|
-
b.style.color = '#fff';
|
|
76
|
-
};
|
|
77
|
-
b.onclick = (e) => {
|
|
78
|
-
e.stopPropagation();
|
|
79
|
-
onClick();
|
|
80
|
-
};
|
|
81
|
-
el.appendChild(b);
|
|
82
|
-
return b;
|
|
83
|
-
};
|
|
84
|
-
if (onOpenSingleView) {
|
|
85
|
-
addAction('🖥 この画面だけ開く', '#0984e3', '#74b9ff', () => {
|
|
86
|
-
onOpenSingleView();
|
|
87
|
-
closePopup();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
if (onOpenMobile) {
|
|
91
|
-
addAction('📱 スマホで開く', '#0984e3', '#74b9ff', () => {
|
|
92
|
-
onOpenMobile();
|
|
93
|
-
closePopup();
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
addSeparator();
|
|
97
|
-
addAction('Emulator', '#e17055', '#fab1a0', () => {
|
|
98
|
-
const currentUrl = new URL(window.location.href);
|
|
99
|
-
const endpoint = currentUrl.origin + currentUrl.pathname;
|
|
100
|
-
const emulatorUrl = new URL('https://emulator.code.uzu-app.com/');
|
|
101
|
-
emulatorUrl.searchParams.set('endpoint', endpoint);
|
|
102
|
-
if (playerCount) {
|
|
103
|
-
emulatorUrl.searchParams.set('player_count', String(playerCount));
|
|
104
|
-
}
|
|
105
|
-
if (adminEnabled) {
|
|
106
|
-
emulatorUrl.searchParams.set('admin', '1');
|
|
107
|
-
}
|
|
108
|
-
window.open(emulatorUrl.toString(), '_blank');
|
|
109
|
-
});
|
|
110
|
-
if (stateGetter) {
|
|
111
|
-
addSeparator();
|
|
112
|
-
const stateBtn = addAction(isInspectorOpen() ? 'Hide State' : 'State', '#00b894', '#55efc4', () => {
|
|
113
|
-
if (isInspectorOpen()) {
|
|
114
|
-
closeInspector();
|
|
115
|
-
stateBtn.textContent = 'State';
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
openInspector(stateGetter);
|
|
119
|
-
stateBtn.textContent = 'Hide State';
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
if (resetGame) {
|
|
124
|
-
addSeparator();
|
|
125
|
-
addAction('Reset State', '#d63031', '#ff7675', () => {
|
|
126
|
-
resetGame();
|
|
127
|
-
closePopup();
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
document.body.appendChild(el);
|
|
131
|
-
const rect = button.getBoundingClientRect();
|
|
132
|
-
el.style.top = `${Math.round(rect.bottom + 6)}px`;
|
|
133
|
-
el.style.left = `${Math.round(rect.left)}px`;
|
|
134
|
-
const close = (e) => {
|
|
135
|
-
if (popup && !popup.contains(e.target) && !button.contains(e.target)) {
|
|
136
|
-
closePopup();
|
|
137
|
-
document.removeEventListener('click', close);
|
|
138
|
-
}
|
|
139
|
-
};
|
|
140
|
-
setTimeout(() => document.addEventListener('click', close), 0);
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
function openInspector(getter) {
|
|
144
|
-
if (inspectorEl)
|
|
145
|
-
return;
|
|
146
|
-
inspectorEl = document.createElement('div');
|
|
147
|
-
inspectorEl.style.cssText = [
|
|
148
|
-
'position:fixed',
|
|
149
|
-
'bottom:8px',
|
|
150
|
-
'right:8px',
|
|
151
|
-
'z-index:99998',
|
|
152
|
-
'width:360px',
|
|
153
|
-
'max-height:80vh',
|
|
154
|
-
'overflow:auto',
|
|
155
|
-
'background:#1e272e',
|
|
156
|
-
'border:1px solid #44475a',
|
|
157
|
-
'border-radius:8px',
|
|
158
|
-
'padding:0',
|
|
159
|
-
'box-shadow:0 4px 16px rgba(0,0,0,0.5)',
|
|
160
|
-
'font-family:monospace',
|
|
161
|
-
'font-size:11px',
|
|
162
|
-
'color:#dfe6e9',
|
|
163
|
-
'resize:both',
|
|
164
|
-
'min-width:200px',
|
|
165
|
-
'min-height:120px',
|
|
166
|
-
].join(';');
|
|
167
|
-
const header = document.createElement('div');
|
|
168
|
-
header.style.cssText = [
|
|
169
|
-
'display:flex',
|
|
170
|
-
'justify-content:space-between',
|
|
171
|
-
'align-items:center',
|
|
172
|
-
'padding:6px 10px',
|
|
173
|
-
'background:#2d3436',
|
|
174
|
-
'border-bottom:1px solid #44475a',
|
|
175
|
-
'border-radius:8px 8px 0 0',
|
|
176
|
-
'position:sticky',
|
|
177
|
-
'top:0',
|
|
178
|
-
'cursor:grab',
|
|
179
|
-
'user-select:none',
|
|
180
|
-
].join(';');
|
|
181
|
-
const headerTitle = document.createElement('span');
|
|
182
|
-
headerTitle.textContent = 'State Inspector';
|
|
183
|
-
headerTitle.style.cssText = 'color:#00b894;font-weight:bold;font-size:12px;';
|
|
184
|
-
const closeBtn = document.createElement('button');
|
|
185
|
-
closeBtn.textContent = '×';
|
|
186
|
-
closeBtn.style.cssText =
|
|
187
|
-
'background:none;border:none;color:#dfe6e9;font-size:18px;cursor:pointer;padding:0 4px;';
|
|
188
|
-
closeBtn.onclick = () => closeInspector();
|
|
189
|
-
header.appendChild(headerTitle);
|
|
190
|
-
header.appendChild(closeBtn);
|
|
191
|
-
inspectorEl.appendChild(header);
|
|
192
|
-
let dragOffsetX = 0;
|
|
193
|
-
let dragOffsetY = 0;
|
|
194
|
-
let overlay = null;
|
|
195
|
-
header.addEventListener('mousedown', (e) => {
|
|
196
|
-
if (e.target === closeBtn)
|
|
197
|
-
return;
|
|
198
|
-
const rect = inspectorEl.getBoundingClientRect();
|
|
199
|
-
dragOffsetX = e.clientX - rect.left;
|
|
200
|
-
dragOffsetY = e.clientY - rect.top;
|
|
201
|
-
header.style.cursor = 'grabbing';
|
|
202
|
-
e.preventDefault();
|
|
203
|
-
overlay = document.createElement('div');
|
|
204
|
-
overlay.style.cssText =
|
|
205
|
-
'position:fixed;top:0;left:0;width:100%;height:100%;z-index:99997;cursor:grabbing;';
|
|
206
|
-
document.body.appendChild(overlay);
|
|
207
|
-
const onMouseMove = (ev) => {
|
|
208
|
-
if (!inspectorEl)
|
|
209
|
-
return;
|
|
210
|
-
inspectorEl.style.left = ev.clientX - dragOffsetX + 'px';
|
|
211
|
-
inspectorEl.style.top = ev.clientY - dragOffsetY + 'px';
|
|
212
|
-
inspectorEl.style.right = 'auto';
|
|
213
|
-
inspectorEl.style.bottom = 'auto';
|
|
214
|
-
};
|
|
215
|
-
const onMouseUp = () => {
|
|
216
|
-
document.removeEventListener('mousemove', onMouseMove);
|
|
217
|
-
document.removeEventListener('mouseup', onMouseUp);
|
|
218
|
-
header.style.cursor = 'grab';
|
|
219
|
-
overlay?.remove();
|
|
220
|
-
overlay = null;
|
|
221
|
-
};
|
|
222
|
-
document.addEventListener('mousemove', onMouseMove);
|
|
223
|
-
document.addEventListener('mouseup', onMouseUp);
|
|
224
|
-
});
|
|
225
|
-
const pre = document.createElement('pre');
|
|
226
|
-
pre.style.cssText = 'margin:0;padding:10px;white-space:pre-wrap;word-break:break-all;';
|
|
227
|
-
inspectorEl.appendChild(pre);
|
|
228
|
-
document.body.appendChild(inspectorEl);
|
|
229
|
-
const update = () => {
|
|
230
|
-
const snap = getter();
|
|
231
|
-
if (!snap) {
|
|
232
|
-
pre.textContent = '(no state)';
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
pre.innerHTML = syntaxHighlight(snap.state);
|
|
236
|
-
};
|
|
237
|
-
update();
|
|
238
|
-
inspectorTimer = setInterval(update, 200);
|
|
239
|
-
}
|
|
240
|
-
function closeInspector() {
|
|
241
|
-
if (inspectorTimer) {
|
|
242
|
-
clearInterval(inspectorTimer);
|
|
243
|
-
inspectorTimer = null;
|
|
244
|
-
}
|
|
245
|
-
if (inspectorEl) {
|
|
246
|
-
inspectorEl.remove();
|
|
247
|
-
inspectorEl = null;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function syntaxHighlight(obj) {
|
|
251
|
-
const json = JSON.stringify(obj, null, 2);
|
|
252
|
-
return json
|
|
253
|
-
.replace(/("(?:\\.|[^"\\])*")\s*:/g, '<span style="color:#81ecec">$1</span>:')
|
|
254
|
-
.replace(/:\s*("(?:\\.|[^"\\])*")/g, ': <span style="color:#ffeaa7">$1</span>')
|
|
255
|
-
.replace(/:\s*(\d+\.?\d*)/g, ': <span style="color:#fd79a8">$1</span>')
|
|
256
|
-
.replace(/:\s*(true|false)/g, ': <span style="color:#a29bfe">$1</span>')
|
|
257
|
-
.replace(/:\s*(null)/g, ': <span style="color:#636e72">$1</span>');
|
|
258
|
-
}
|