···
13
13
listIsOwned,
14
14
listEditors,
15
15
showEditorsModal,
16
16
+
showDevtools,
17
17
+
devtoolsPanelHeight,
16
18
} from "./state.js";
17
19
import { ListsView } from "./lists.js";
18
20
import { ListView } from "./list.js";
···
71
73
}
72
74
73
75
class App extends Component {
74
74
-
showDevtools = signal(false);
75
75
-
panelHeight = signal(280);
76
76
handle = signal("");
77
77
accountsPopoverRef = createRef();
78
78
···
137
137
<button
138
138
class="icon-button"
139
139
title="Toggle records panel"
140
140
-
onClick=${() => (this.showDevtools.value = !this.showDevtools.value)}
140
140
+
onClick=${() => (showDevtools.value = !showDevtools.value)}
141
141
>
142
142
<svg
143
143
width="14"
···
165
165
did=${session.value.did}
166
166
/>
167
167
${this.renderBody()}
168
168
-
${this.showDevtools.value &&
168
168
+
${showDevtools.value &&
169
169
html`
170
170
<div class="devtools-fixed-wrapper">
171
171
<div
···
173
173
onPointerDown=${(e) => {
174
174
e.currentTarget.setPointerCapture(e.pointerId);
175
175
const startY = e.clientY;
176
176
-
const startH = this.panelHeight.value;
176
176
+
const startH = devtoolsPanelHeight.value;
177
177
const el = e.currentTarget;
178
178
const onMove = (mv) => {
179
179
-
this.panelHeight.value = Math.max(
179
179
+
devtoolsPanelHeight.value = Math.max(
180
180
120,
181
181
Math.min(window.innerHeight - 60, startH + (startY - mv.clientY)),
182
182
);
···
194
194
<${DevToolsPanel}
195
195
did=${session.value.did}
196
196
listUri=${listUri.value}
197
197
-
style=${"height:" + this.panelHeight.value + "px"}
198
198
-
onClose=${() => (this.showDevtools.value = false)}
197
197
+
style=${"height:" + devtoolsPanelHeight.value + "px"}
198
198
+
onClose=${() => (showDevtools.value = false)}
199
199
/>
200
200
</div>
201
201
`}
···
97
97
for (const [did, recs] of Object.entries(byDid)) {
98
98
if (selRepo && did !== selRepo) continue;
99
99
for (const rec of recs) {
100
100
+
if (tab.id === "list" && listRkey && rec.uri.split("/").pop() !== listRkey) continue;
100
101
if (tab.id === "item" && listRkey && rec.value?.listId !== listRkey) continue;
101
102
if (itemRkeys && !itemRkeys.has(rec.value?.item)) continue;
102
103
out.push({ did, uri: rec.uri, value: rec.value });
···
131
132
132
133
return html`
133
134
<div class="devtools-panel" style=${this.props.style}>
134
134
-
<div class="devtools-header">
135
135
-
<div class="devtools-tabs">
136
136
-
${TABS.map(
137
137
-
(t) => html`
138
138
-
<button
139
139
-
key=${t.id}
140
140
-
class=${tab === t.id ? "active" : ""}
141
141
-
onClick=${() => this.switchTab(t.id)}
142
142
-
>
143
143
-
${t.label}
144
144
-
</button>
145
145
-
`
146
146
-
)}
135
135
+
<div class="devtools-zoom">
136
136
+
<div class="devtools-header">
137
137
+
<div class="devtools-tabs">
138
138
+
${TABS.map(
139
139
+
(t) => html`
140
140
+
<button
141
141
+
key=${t.id}
142
142
+
class=${tab === t.id ? "active" : ""}
143
143
+
onClick=${() => this.switchTab(t.id)}
144
144
+
>
145
145
+
${t.label}
146
146
+
</button>
147
147
+
`
148
148
+
)}
149
149
+
</div>
150
150
+
<button
151
151
+
class="icon-button devtools-action"
152
152
+
style="margin-left:auto"
153
153
+
title=${this.paused.value ? "Resume live updates" : "Pause live updates"}
154
154
+
disabled=${this.resuming.value || !devtoolsSubscription.value}
155
155
+
onClick=${this.#togglePause}
156
156
+
>
157
157
+
${this.paused.value
158
158
+
? html`<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><polygon points="3,2 13,8 3,14"/></svg> Play`
159
159
+
: html`<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><rect x="3" y="2" width="3.5" height="12" rx="1"/><rect x="9.5" y="2" width="3.5" height="12" rx="1"/></svg> Pause`}
160
160
+
</button>
161
161
+
<button class="icon-button devtools-action" title="Close" onClick=${this.props.onClose}>
162
162
+
<svg
163
163
+
width="12"
164
164
+
height="12"
165
165
+
viewBox="0 0 16 16"
166
166
+
fill="none"
167
167
+
stroke="currentColor"
168
168
+
stroke-width="2"
169
169
+
stroke-linecap="round"
170
170
+
>
171
171
+
<line x1="4" y1="4" x2="12" y2="12" />
172
172
+
<line x1="12" y1="4" x2="4" y2="12" />
173
173
+
</svg>
174
174
+
</button>
147
175
</div>
148
148
-
<button
149
149
-
class="icon-button devtools-action"
150
150
-
style="margin-left:auto"
151
151
-
title=${this.paused.value ? "Resume live updates" : "Pause live updates"}
152
152
-
disabled=${this.resuming.value || !devtoolsSubscription.value}
153
153
-
onClick=${this.#togglePause}
154
154
-
>
155
155
-
${this.paused.value
156
156
-
? html`<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><polygon points="3,2 13,8 3,14"/></svg> Play`
157
157
-
: html`<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor"><rect x="3" y="2" width="3.5" height="12" rx="1"/><rect x="9.5" y="2" width="3.5" height="12" rx="1"/></svg> Pause`}
158
158
-
</button>
159
159
-
<button class="icon-button devtools-action" title="Close" onClick=${this.props.onClose}>
160
160
-
<svg
161
161
-
width="12"
162
162
-
height="12"
163
163
-
viewBox="0 0 16 16"
164
164
-
fill="none"
165
165
-
stroke="currentColor"
166
166
-
stroke-width="2"
167
167
-
stroke-linecap="round"
168
168
-
>
169
169
-
<line x1="4" y1="4" x2="12" y2="12" />
170
170
-
<line x1="12" y1="4" x2="4" y2="12" />
171
171
-
</svg>
172
172
-
</button>
173
173
-
</div>
174
174
-
<div class="devtools-body">
175
175
-
<div class="devtools-tab-content">
176
176
-
${html`
177
177
-
<div class="devtools-subtabs">
178
178
-
<button
179
179
-
class=${!selRepo ? "active" : ""}
180
180
-
onClick=${() => {
181
181
-
this.selectedRepo.value = null;
182
182
-
this.selected.value = null;
183
183
-
}}
184
184
-
>
185
185
-
All
186
186
-
</button>
187
187
-
${dids.map(
188
188
-
(did) => html`
189
189
-
<button
190
190
-
key=${did}
191
191
-
class=${selRepo === did ? "active" : ""}
192
192
-
onClick=${() => {
193
193
-
this.selectedRepo.value = did;
194
194
-
this.selected.value = null;
195
195
-
}}
196
196
-
>
197
197
-
@${handles[did] || did.slice(8, 20) + "…"}
198
198
-
</button>
199
199
-
`
200
200
-
)}
201
201
-
</div>
202
202
-
`}
203
203
-
<div class=${`devtools-body-inner${selEntry ? " has-detail" : ""}`}>
204
204
-
<div class="devtools-timeline">
205
205
-
${dids.length === 0
206
206
-
? html`<p class="devtools-empty">Open a list to inspect records.</p>`
207
207
-
: entries.length === 0
208
208
-
? html`<p class="devtools-empty">No records.</p>`
209
209
-
: entries.map((entry, i) => {
210
210
-
const active = sel === entry.uri;
211
211
-
const rkey = entry.uri.split("/").pop();
212
212
-
const author = "@" + (handles[entry.did] || entry.did.slice(8, 20) + "…");
213
213
-
return html`
214
214
-
<div
215
215
-
class=${`devtools-entry${active ? " active" : ""}`}
216
216
-
key=${entry.uri}
217
217
-
onClick=${() => (this.selected.value = active ? null : entry.uri)}
218
218
-
>
219
219
-
<div class="devtools-entry-meta">
220
220
-
<span class="devtools-entry-index">${i + 1}</span>
221
221
-
<span class="devtools-entry-author">${author}</span>
222
222
-
<span class="devtools-entry-time">${rkey}</span>
176
176
+
<div class="devtools-body">
177
177
+
<div class="devtools-tab-content">
178
178
+
${html`
179
179
+
<div class="devtools-subtabs">
180
180
+
<button
181
181
+
class=${!selRepo ? "active" : ""}
182
182
+
onClick=${() => {
183
183
+
this.selectedRepo.value = null;
184
184
+
this.selected.value = null;
185
185
+
}}
186
186
+
>
187
187
+
All
188
188
+
</button>
189
189
+
${dids.map(
190
190
+
(did) => html`
191
191
+
<button
192
192
+
key=${did}
193
193
+
class=${selRepo === did ? "active" : ""}
194
194
+
onClick=${() => {
195
195
+
this.selectedRepo.value = did;
196
196
+
this.selected.value = null;
197
197
+
}}
198
198
+
>
199
199
+
@${handles[did] || did.slice(8, 20) + "…"}
200
200
+
</button>
201
201
+
`
202
202
+
)}
203
203
+
</div>
204
204
+
`}
205
205
+
<div class=${`devtools-body-inner${selEntry ? " has-detail" : ""}`}>
206
206
+
<div class="devtools-timeline">
207
207
+
${dids.length === 0
208
208
+
? html`<p class="devtools-empty">Open a list to inspect records.</p>`
209
209
+
: entries.length === 0
210
210
+
? html`<p class="devtools-empty">No records.</p>`
211
211
+
: entries.map((entry, i) => {
212
212
+
const active = sel === entry.uri;
213
213
+
const rkey = entry.uri.split("/").pop();
214
214
+
const author = "@" + (handles[entry.did] || entry.did.slice(8, 20) + "…");
215
215
+
return html`
216
216
+
<div
217
217
+
class=${`devtools-entry${active ? " active" : ""}`}
218
218
+
key=${entry.uri}
219
219
+
onClick=${() => (this.selected.value = active ? null : entry.uri)}
220
220
+
>
221
221
+
<div class="devtools-entry-meta">
222
222
+
<span class="devtools-entry-index">${i + 1}</span>
223
223
+
<span class="devtools-entry-author">${author}</span>
224
224
+
<span class="devtools-entry-time">${rkey}</span>
225
225
+
</div>
226
226
+
<div class="devtools-entry-uri">${entry.uri}</div>
227
227
+
<pre class="devtools-entry-json">${formatRecord(entry.value)}</pre>
223
228
</div>
224
224
-
<div class="devtools-entry-uri">${entry.uri}</div>
225
225
-
<pre class="devtools-entry-json">${formatRecord(entry.value)}</pre>
226
226
-
</div>
227
227
-
`;
228
228
-
})}
229
229
+
`;
230
230
+
})}
231
231
+
</div>
232
232
+
${selEntry ? this.renderDetail(selEntry) : null}
229
233
</div>
230
230
-
${selEntry ? this.renderDetail(selEntry) : null}
231
234
</div>
232
235
</div>
233
236
</div>
···
16
16
devtoolsRecords,
17
17
devtoolsDids,
18
18
devtoolsSubscription,
19
19
+
showDevtools,
20
20
+
devtoolsPanelHeight,
21
21
+
DEVTOOLS_HANDLE_HEIGHT,
19
22
} from "./state.js";
20
23
import {
21
24
listRecords,
···
201
204
};
202
205
203
206
render() {
207
207
+
const devtoolsHeight = showDevtools.value
208
208
+
? devtoolsPanelHeight.value + DEVTOOLS_HANDLE_HEIGHT
209
209
+
: 0;
210
210
+
204
211
return html`
205
205
-
<div class="modal-bg" onClick=${this.props.onClose}>
212
212
+
<div
213
213
+
class="modal-bg"
214
214
+
style=${devtoolsHeight ? `bottom:${devtoolsHeight}px` : ""}
215
215
+
onClick=${this.props.onClose}
216
216
+
>
206
217
<div class="modal" onClick=${(e) => e.stopPropagation()}>
207
218
<h3>Editors</h3>
208
219
${this.props.editors.length === 0
···
45
45
export const devtoolsDids = signal([]);
46
46
// { pause: () => void, resume: () => Promise<void> } while a list is active, null otherwise.
47
47
export const devtoolsSubscription = signal(null);
48
48
+
49
49
+
// UI state for the resizable devtools panel. Lives here (rather than as local
50
50
+
// component state) so other parts of the tree — like the editors modal — can
51
51
+
// react to whether it's open and how tall it is.
52
52
+
export const showDevtools = signal(false);
53
53
+
export const devtoolsPanelHeight = signal(280);
54
54
+
55
55
+
// Kept in sync with the resize handle's height in style.css so other
56
56
+
// components can compute the devtools panel's actual on-screen height
57
57
+
// without measuring the DOM. (The panel's internal zoom only affects its
58
58
+
// content, not its own box, so it isn't a factor here.)
59
59
+
export const DEVTOOLS_HANDLE_HEIGHT = 6;
···
1
1
:root {
2
2
color-scheme: light dark;
3
3
font-family: system-ui, -apple-system, sans-serif;
4
4
-
font-size: 15px;
4
4
+
font-size: 22.5px;
5
5
}
6
6
7
7
*, *::before, *::after { box-sizing: border-box; }
···
457
457
flex-direction: column;
458
458
border-top: 1px solid #ddd;
459
459
background: #fff;
460
460
+
}
461
461
+
462
462
+
/* Everything visible actually lives in here, not directly in .devtools-panel.
463
463
+
That keeps the panel's own box at its real, unzoomed drag-resized height
464
464
+
(so the resize handle keeps tracking the cursor 1:1) while the zoomed
465
465
+
content just scrolls internally if it doesn't fit. */
466
466
+
.devtools-zoom {
467
467
+
display: flex;
468
468
+
flex-direction: column;
469
469
+
flex: 1;
470
470
+
min-height: 0;
460
471
font-family: ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas,
461
472
"DejaVu Sans Mono", monospace;
462
473
font-size: 12px;
474
474
+
zoom: 2;
463
475
}
464
476
465
477
.devtools-header {