@jsenv/terminal-recorder 1.5.54 → 1.5.57
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/jsenv_terminal_recorder_node_modules.js +3 -2
- package/package.json +5 -5
- package/src/.jsenv/chrome@149.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/packages/internal/plugin-supervisor/src/client/supervisor.js +24 -3
- package/src/.jsenv/chrome@149.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/dependency_status/client/dependency_status.js +233 -2
- package/src/.jsenv/chrome@149.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/page_switcher/client/page_switcher.js +136 -13
- package/src/.jsenv/chrome@149.0.0/xterm.html +5 -1
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-fit/lib/addon-fit.mjs +0 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-fit/lib/addon-fit.mjs.map +28 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs +19 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-serialize/lib/addon-serialize.mjs.map +176 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-webgl/lib/addon-webgl.mjs +0 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-webgl/lib/addon-webgl.mjs.map +7 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/xterm/css/xterm.css +267 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/xterm/lib/xterm.mjs +53 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/xterm/lib/xterm.mjs.map +7 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/packages/backend/server/src/plugins/autoreload_on_server_restart/client/autoreload_on_server_restart.js +133 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/packages/frontend/custom-elements-redefine/src/main.js +217 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/packages/internal/plugin-supervisor/src/client/supervisor.js +1599 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/client_monitoring/client/client_reporter.js +582 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/dependency_status/client/dependency_status.js +290 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/drop_to_open/client/drop_to_open.js +49 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/page_switcher/client/page_switcher.js +230 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/page_switcher/client/page_switcher.js.map +11 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/protocol_file/client/page_picker.js +783 -0
- package/src/.jsenv/chrome@151.0.0/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/server_events/client/server_events_client.js +461 -0
- package/src/.jsenv/chrome@151.0.0/gif_encoder.js +400 -0
- package/src/.jsenv/chrome@151.0.0/gif_encoder.js.map +11 -0
- package/src/.jsenv/chrome@151.0.0/lzw_encoder.js +253 -0
- package/src/.jsenv/chrome@151.0.0/neuquant.js +498 -0
- package/src/.jsenv/chrome@151.0.0/terminal_recorder.js +384 -0
- package/src/.jsenv/chrome@151.0.0/terminal_recorder.js.map +11 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html +88 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html@L11C7-L15C16.js +5 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html@L14C5-L18C14.js +5 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html@L17C7-L21C16.js +5 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html@L19C5-L24C14.js +7 -0
- package/src/.jsenv/chrome@151.0.0/xterm.html@L19C5-L24C14.js.map +11 -0
|
@@ -2,8 +2,9 @@ function ansiRegex({onlyFirst = false} = {}) {
|
|
|
2
2
|
// Valid string terminator sequences are BEL, ESC\, and 0x9c
|
|
3
3
|
const ST = '(?:\\u0007|\\u001B\\u005C|\\u009C)';
|
|
4
4
|
|
|
5
|
-
// OSC sequences only: ESC ] ... ST
|
|
6
|
-
|
|
5
|
+
// OSC sequences only: ESC ] ... ST
|
|
6
|
+
// The payload stops at the first terminator character rather than scanning ahead for one, so an unterminated `ESC ]` cannot rescan the rest of the input. Terminals likewise abort a control string on an unexpected ESC.
|
|
7
|
+
const osc = `(?:\\u001B\\][^\\u0007\\u001B\\u009C]*${ST})`;
|
|
7
8
|
|
|
8
9
|
// CSI and related: ESC/C1, optional intermediates, optional params (supports ; and :) then final byte
|
|
9
10
|
const csi = '[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/terminal-recorder",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.57",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Record terminal output as .svg, .gif, .webm, .mp4",
|
|
6
6
|
"repository": {
|
|
@@ -35,13 +35,13 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@jsenv/humanize": "1.7.8",
|
|
38
|
-
"@jsenv/server": "17.6.
|
|
38
|
+
"@jsenv/server": "17.6.2",
|
|
39
39
|
"@jsenv/terminal-text-size": "0.0.4",
|
|
40
|
-
"@playwright/browser-chromium": "1.
|
|
41
|
-
"ansi-regex": "6.
|
|
40
|
+
"@playwright/browser-chromium": "1.62.1",
|
|
41
|
+
"ansi-regex": "6.3.0",
|
|
42
42
|
"eastasianwidth": "0.3.0",
|
|
43
43
|
"he": "1.2.0",
|
|
44
|
-
"playwright": "1.
|
|
44
|
+
"playwright": "1.62.1",
|
|
45
45
|
"strip-ansi": "7.2.0",
|
|
46
46
|
"webm-to-mp4": "1.0.0"
|
|
47
47
|
},
|
|
@@ -1107,7 +1107,9 @@ window.__supervisor__ = (() => {
|
|
|
1107
1107
|
* a warning, never the other way around.
|
|
1108
1108
|
*/
|
|
1109
1109
|
class JsenvWarningOverlay extends HTMLElement {
|
|
1110
|
-
|
|
1110
|
+
// "node" is for what a string cannot say: a live indicator, something
|
|
1111
|
+
// animated, anything the code reporting the warning wants to render itself
|
|
1112
|
+
constructor({ title, text, details = [], node }) {
|
|
1111
1113
|
super();
|
|
1112
1114
|
const root = this.attachShadow({ mode: "open" });
|
|
1113
1115
|
root.innerHTML = `
|
|
@@ -1124,10 +1126,14 @@ window.__supervisor__ = (() => {
|
|
|
1124
1126
|
<div class="details">
|
|
1125
1127
|
${details.map((detail) => `<p>${escapeHtml(detail)}</p>`).join("\n ")}
|
|
1126
1128
|
</div>
|
|
1129
|
+
<div class="extra"></div>
|
|
1127
1130
|
<div class="tip">
|
|
1128
1131
|
Click outside to close.
|
|
1129
1132
|
</div>
|
|
1130
1133
|
</div>`;
|
|
1134
|
+
if (node) {
|
|
1135
|
+
root.querySelector(".extra").appendChild(node);
|
|
1136
|
+
}
|
|
1131
1137
|
enableCopyButton(root);
|
|
1132
1138
|
root.querySelector(".backdrop").onclick = () => {
|
|
1133
1139
|
if (!this.parentNode) {
|
|
@@ -1160,6 +1166,14 @@ window.__supervisor__ = (() => {
|
|
|
1160
1166
|
parts.push(paragraph.textContent.trim());
|
|
1161
1167
|
}
|
|
1162
1168
|
}
|
|
1169
|
+
// a node rendered by the warning reporter says which of its parts are
|
|
1170
|
+
// worth pasting, the rest being decoration
|
|
1171
|
+
const extraNode = overlay.querySelector(".extra");
|
|
1172
|
+
if (extraNode) {
|
|
1173
|
+
for (const line of extraNode.querySelectorAll("[data-copy-line]")) {
|
|
1174
|
+
parts.push(line.textContent.trim().replace(/\s+/g, " "));
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1163
1177
|
const copied = await writeTextToClipboard(parts.join("\n"));
|
|
1164
1178
|
button.textContent = copied ? "Copied" : "Copy failed";
|
|
1165
1179
|
clearTimeout(resetTimeout);
|
|
@@ -1376,6 +1390,13 @@ window.__supervisor__ = (() => {
|
|
|
1376
1390
|
overflow: auto;
|
|
1377
1391
|
}
|
|
1378
1392
|
|
|
1393
|
+
.extra {
|
|
1394
|
+
padding: 0 20px 16px;
|
|
1395
|
+
}
|
|
1396
|
+
.extra:empty {
|
|
1397
|
+
display: none;
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1379
1400
|
.tip {
|
|
1380
1401
|
padding-top: 12px;
|
|
1381
1402
|
border-top: 1px solid #999;
|
|
@@ -1406,11 +1427,11 @@ window.__supervisor__ = (() => {
|
|
|
1406
1427
|
}
|
|
1407
1428
|
|
|
1408
1429
|
supervisor.createException = createException;
|
|
1409
|
-
supervisor.reportWarning = ({ title, text, details }) => {
|
|
1430
|
+
supervisor.reportWarning = ({ title, text, details, node }) => {
|
|
1410
1431
|
if (!errorOverlay) {
|
|
1411
1432
|
return () => {};
|
|
1412
1433
|
}
|
|
1413
|
-
return displayJsenvWarningOverlay({ title, text, details });
|
|
1434
|
+
return displayJsenvWarningOverlay({ title, text, details, node });
|
|
1414
1435
|
};
|
|
1415
1436
|
supervisor.reportException = (exception) => {
|
|
1416
1437
|
if (errorOverlay) {
|
|
@@ -3,11 +3,22 @@
|
|
|
3
3
|
*
|
|
4
4
|
* A missing dependency is not handled here: it makes the import fail, so the
|
|
5
5
|
* error overlay already says it, with more precision (which import, where).
|
|
6
|
+
*
|
|
7
|
+
* The overlay does not only describe the problem, it shows the dev server
|
|
8
|
+
* watching for the fix: the paths being looked at are named and kept alive on
|
|
9
|
+
* screen, so waiting for "npm install" feels like waiting for something that is
|
|
10
|
+
* actually going to happen.
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
let removeOverlay = () => {};
|
|
14
|
+
let watchInfoFromServer = {};
|
|
15
|
+
// packages listed as outdated at some point, and the path watched for each;
|
|
16
|
+
// what leaves this map has been installed while the overlay was open, which is
|
|
17
|
+
// worth showing as such
|
|
18
|
+
const outdatedPathMap = new Map();
|
|
9
19
|
|
|
10
|
-
export const initDependencyStatus = ({ problems }) => {
|
|
20
|
+
export const initDependencyStatus = ({ problems, watchInfo }) => {
|
|
21
|
+
watchInfoFromServer = watchInfo || {};
|
|
11
22
|
render(problems);
|
|
12
23
|
// without the server events channel the page only knows the state it was
|
|
13
24
|
// served with, which is still better than nothing
|
|
@@ -24,9 +35,19 @@ const render = (problems) => {
|
|
|
24
35
|
const outdatedList = problems.filter(({ state }) => state === "outdated");
|
|
25
36
|
removeOverlay();
|
|
26
37
|
removeOverlay = () => {};
|
|
38
|
+
const installedList = [];
|
|
39
|
+
for (const [packageName, watchedPath] of outdatedPathMap) {
|
|
40
|
+
if (!outdatedList.some((problem) => problem.packageName === packageName)) {
|
|
41
|
+
installedList.push({ packageName, watchedPath });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
27
44
|
if (outdatedList.length === 0) {
|
|
45
|
+
outdatedPathMap.clear();
|
|
28
46
|
return;
|
|
29
47
|
}
|
|
48
|
+
for (const { packageName, watchedPath } of outdatedList) {
|
|
49
|
+
outdatedPathMap.set(packageName, watchedPath);
|
|
50
|
+
}
|
|
30
51
|
const supervisor = window.__supervisor__;
|
|
31
52
|
if (!supervisor || !supervisor.reportWarning) {
|
|
32
53
|
console.warn(summarize(outdatedList));
|
|
@@ -41,8 +62,8 @@ const render = (problems) => {
|
|
|
41
62
|
details: [
|
|
42
63
|
"The page is running with what is installed in node_modules, which is not what package.json asks for. It may work, but it is not the code the project expects.",
|
|
43
64
|
"Run npm install to fix it. If an install is already running, there is nothing to do but wait.",
|
|
44
|
-
"This page reloads on its own as soon as node_modules matches package.json.",
|
|
45
65
|
],
|
|
66
|
+
node: createWatchNode(outdatedList, installedList),
|
|
46
67
|
});
|
|
47
68
|
};
|
|
48
69
|
|
|
@@ -57,3 +78,213 @@ const summarize = (outdatedList) => {
|
|
|
57
78
|
.map(({ packageName }) => packageName)
|
|
58
79
|
.join(", ")}`;
|
|
59
80
|
};
|
|
81
|
+
|
|
82
|
+
const createWatchNode = (outdatedList, installedList) => {
|
|
83
|
+
const { packageJsonPath = "package.json", pollInterval } =
|
|
84
|
+
watchInfoFromServer;
|
|
85
|
+
const node = document.createElement("div");
|
|
86
|
+
node.className = "dependency_watch";
|
|
87
|
+
const rows = [];
|
|
88
|
+
rows.push(
|
|
89
|
+
createRow({
|
|
90
|
+
state: "watching",
|
|
91
|
+
path: packageJsonPath,
|
|
92
|
+
// a file watcher, not a poll: an edit here is what puts a dependency out
|
|
93
|
+
// of date in the first place
|
|
94
|
+
note: "on every change",
|
|
95
|
+
pollInterval,
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
for (const { packageName, declaredVersion, watchedPath } of outdatedList) {
|
|
99
|
+
rows.push(
|
|
100
|
+
createRow({
|
|
101
|
+
state: "watching",
|
|
102
|
+
path: watchedPath || `node_modules/${packageName}/package.json`,
|
|
103
|
+
note: pollInterval
|
|
104
|
+
? `waiting for ${declaredVersion}, read every ${pollInterval}ms`
|
|
105
|
+
: `waiting for ${declaredVersion}`,
|
|
106
|
+
pollInterval,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
for (const { packageName, watchedPath } of installedList) {
|
|
111
|
+
rows.push(
|
|
112
|
+
createRow({
|
|
113
|
+
state: "done",
|
|
114
|
+
path: watchedPath || `node_modules/${packageName}/package.json`,
|
|
115
|
+
note: "installed",
|
|
116
|
+
}),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
node.innerHTML = `
|
|
120
|
+
<style>
|
|
121
|
+
${watchCSS}
|
|
122
|
+
</style>
|
|
123
|
+
<div class="dependency_watch_head">
|
|
124
|
+
<span class="dependency_watch_live">
|
|
125
|
+
<span class="dependency_watch_live_dot"></span>watching
|
|
126
|
+
</span>
|
|
127
|
+
<span class="dependency_watch_head_text">
|
|
128
|
+
the files that will tell the install is over
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
<div class="dependency_watch_rows">
|
|
132
|
+
${rows.join("\n ")}
|
|
133
|
+
</div>
|
|
134
|
+
<div class="dependency_watch_effect" data-copy-line>
|
|
135
|
+
As soon as node_modules matches package.json, this page reloads by itself.
|
|
136
|
+
</div>`;
|
|
137
|
+
return node;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const createRow = ({ state, path, note, pollInterval }) => {
|
|
141
|
+
const sweepStyle = pollInterval
|
|
142
|
+
? ` style="animation-duration: ${pollInterval}ms"`
|
|
143
|
+
: "";
|
|
144
|
+
return `<div class="dependency_watch_row" data-state="${state}">
|
|
145
|
+
<span class="dependency_watch_row_icon"></span>
|
|
146
|
+
<code class="dependency_watch_row_path">${escapeHtml(path)}</code>
|
|
147
|
+
<span class="dependency_watch_row_note">${escapeHtml(note)}</span>
|
|
148
|
+
${
|
|
149
|
+
state === "watching"
|
|
150
|
+
? `<span class="dependency_watch_row_sweep"${sweepStyle}></span>`
|
|
151
|
+
: ""
|
|
152
|
+
}
|
|
153
|
+
</div>`;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const escapeHtml = (string) => {
|
|
157
|
+
return String(string)
|
|
158
|
+
.replace(/&/g, "&")
|
|
159
|
+
.replace(/</g, "<")
|
|
160
|
+
.replace(/>/g, ">")
|
|
161
|
+
.replace(/"/g, """)
|
|
162
|
+
.replace(/'/g, "'");
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const watchCSS = /* css */ `
|
|
166
|
+
.dependency_watch_head {
|
|
167
|
+
display: flex;
|
|
168
|
+
margin-bottom: 8px;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 8px;
|
|
171
|
+
}
|
|
172
|
+
.dependency_watch_live {
|
|
173
|
+
display: flex;
|
|
174
|
+
padding: 2px 8px;
|
|
175
|
+
align-items: center;
|
|
176
|
+
gap: 6px;
|
|
177
|
+
color: #ffab40;
|
|
178
|
+
font-size: 12px;
|
|
179
|
+
text-transform: uppercase;
|
|
180
|
+
letter-spacing: 0.08em;
|
|
181
|
+
border: 1px solid currentColor;
|
|
182
|
+
border-radius: 999px;
|
|
183
|
+
}
|
|
184
|
+
.dependency_watch_live_dot {
|
|
185
|
+
width: 7px;
|
|
186
|
+
height: 7px;
|
|
187
|
+
background: currentColor;
|
|
188
|
+
border-radius: 50%;
|
|
189
|
+
animation: dependency_watch_pulse 1.4s ease-in-out infinite;
|
|
190
|
+
}
|
|
191
|
+
@keyframes dependency_watch_pulse {
|
|
192
|
+
0%,
|
|
193
|
+
100% {
|
|
194
|
+
opacity: 0.25;
|
|
195
|
+
transform: scale(0.8);
|
|
196
|
+
}
|
|
197
|
+
50% {
|
|
198
|
+
opacity: 1;
|
|
199
|
+
transform: scale(1.15);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
.dependency_watch_head_text {
|
|
203
|
+
font-size: 13px;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.dependency_watch_rows {
|
|
207
|
+
display: flex;
|
|
208
|
+
flex-direction: column;
|
|
209
|
+
gap: 4px;
|
|
210
|
+
}
|
|
211
|
+
.dependency_watch_row {
|
|
212
|
+
position: relative;
|
|
213
|
+
display: flex;
|
|
214
|
+
padding: 6px 10px;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 8px;
|
|
217
|
+
font-size: 13px;
|
|
218
|
+
background: rgba(255, 255, 255, 0.04);
|
|
219
|
+
border: 1px solid #333;
|
|
220
|
+
border-radius: 4px;
|
|
221
|
+
overflow: hidden;
|
|
222
|
+
}
|
|
223
|
+
.dependency_watch_row_icon {
|
|
224
|
+
width: 8px;
|
|
225
|
+
height: 8px;
|
|
226
|
+
flex: none;
|
|
227
|
+
border: 1px solid #ffab40;
|
|
228
|
+
border-radius: 50%;
|
|
229
|
+
}
|
|
230
|
+
.dependency_watch_row[data-state="done"] .dependency_watch_row_icon {
|
|
231
|
+
background: #7fd67f;
|
|
232
|
+
border-color: #7fd67f;
|
|
233
|
+
}
|
|
234
|
+
.dependency_watch_row_path {
|
|
235
|
+
color: #eee;
|
|
236
|
+
text-overflow: ellipsis;
|
|
237
|
+
white-space: nowrap;
|
|
238
|
+
overflow: hidden;
|
|
239
|
+
}
|
|
240
|
+
.dependency_watch_row_note {
|
|
241
|
+
margin-left: auto;
|
|
242
|
+
padding-left: 8px;
|
|
243
|
+
font-size: 12px;
|
|
244
|
+
white-space: nowrap;
|
|
245
|
+
opacity: 0.8;
|
|
246
|
+
}
|
|
247
|
+
.dependency_watch_row[data-state="done"] .dependency_watch_row_note {
|
|
248
|
+
color: #7fd67f;
|
|
249
|
+
opacity: 1;
|
|
250
|
+
}
|
|
251
|
+
/* the sweep is the watching made visible: one pass per read */
|
|
252
|
+
.dependency_watch_row_sweep {
|
|
253
|
+
position: absolute;
|
|
254
|
+
top: 0;
|
|
255
|
+
left: 0;
|
|
256
|
+
width: 35%;
|
|
257
|
+
height: 100%;
|
|
258
|
+
background: linear-gradient(
|
|
259
|
+
90deg,
|
|
260
|
+
transparent,
|
|
261
|
+
rgba(255, 171, 64, 0.16),
|
|
262
|
+
transparent
|
|
263
|
+
);
|
|
264
|
+
animation: dependency_watch_sweep 2s linear infinite;
|
|
265
|
+
pointer-events: none;
|
|
266
|
+
}
|
|
267
|
+
@keyframes dependency_watch_sweep {
|
|
268
|
+
from {
|
|
269
|
+
transform: translateX(-100%);
|
|
270
|
+
}
|
|
271
|
+
to {
|
|
272
|
+
transform: translateX(340%);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.dependency_watch_effect {
|
|
277
|
+
margin-top: 8px;
|
|
278
|
+
color: #ffab40;
|
|
279
|
+
font-size: 13px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
@media (prefers-reduced-motion: reduce) {
|
|
283
|
+
.dependency_watch_live_dot {
|
|
284
|
+
animation: none;
|
|
285
|
+
}
|
|
286
|
+
.dependency_watch_row_sweep {
|
|
287
|
+
display: none;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
`;
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* cmd+K / ctrl+K on any dev-served page: the .html files the server serves, as a
|
|
3
3
|
* tree one walks, filter as you type, Enter to go there.
|
|
4
4
|
*
|
|
5
|
+
* cmd+E / ctrl+E is the other half of the same question: instead of going to a
|
|
6
|
+
* page, open its file in the editor (the server does it, see
|
|
7
|
+
* GET /.internal/open_file/*). Pressed on the page itself it opens the page one
|
|
8
|
+
* is on; pressed inside the switcher it opens the row one is looking at.
|
|
9
|
+
*
|
|
5
10
|
* A tree rather than a list of paths, because the paths are mostly the same
|
|
6
11
|
* path: folding every directory that holds a single thing turns
|
|
7
12
|
* "packages/frontend/navi/src/layout/demos/…" repeated forty times into one
|
|
@@ -25,6 +30,10 @@
|
|
|
25
30
|
// another injected client. A function scope owes nothing to anybody.
|
|
26
31
|
(() => {
|
|
27
32
|
const PAGES_ENDPOINT = "/.internal/pages.json";
|
|
33
|
+
// The server asks the OS to open a file in whatever editor is configured
|
|
34
|
+
// (VSCode here) — it takes a file url, which is why the page list carries one
|
|
35
|
+
// per page (see html_pages.js).
|
|
36
|
+
const OPEN_FILE_ENDPOINT = "/.internal/open_file/";
|
|
28
37
|
const STORAGE_KEY = "jsenv_page_switcher";
|
|
29
38
|
// Open across a reload: this is a dev tool on a page one is editing, and a hot
|
|
30
39
|
// reload in the middle of looking for the next page should not close what one
|
|
@@ -56,15 +65,20 @@
|
|
|
56
65
|
}
|
|
57
66
|
};
|
|
58
67
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
// cmd on mac, ctrl elsewhere — the same split every editor makes.
|
|
64
|
-
return window.navigator.platform.toLowerCase().includes("mac")
|
|
68
|
+
// cmd on mac, ctrl elsewhere — the same split every editor makes.
|
|
69
|
+
const isCommandKey = (event) =>
|
|
70
|
+
window.navigator.platform.toLowerCase().includes("mac")
|
|
65
71
|
? event.metaKey && !event.ctrlKey
|
|
66
72
|
: event.ctrlKey && !event.metaKey;
|
|
67
|
-
|
|
73
|
+
|
|
74
|
+
const isSwitcherKey = (event) =>
|
|
75
|
+
(event.key === "k" || event.key === "K") && isCommandKey(event);
|
|
76
|
+
// E for edit, next to K for the same reason the two belong together: K asks
|
|
77
|
+
// "which page", E asks "where does this page live". Outside the switcher it
|
|
78
|
+
// means the page one is on; inside it, the row one is looking at — so the
|
|
79
|
+
// same press reads the same way in both places.
|
|
80
|
+
const isEditorKey = (event) =>
|
|
81
|
+
(event.key === "e" || event.key === "E") && isCommandKey(event);
|
|
68
82
|
|
|
69
83
|
const STYLE_TEXT = /* css */ `
|
|
70
84
|
:host {
|
|
@@ -230,6 +244,32 @@
|
|
|
230
244
|
}
|
|
231
245
|
`;
|
|
232
246
|
|
|
247
|
+
const FLASH_STYLE_TEXT = /* css */ `
|
|
248
|
+
:host {
|
|
249
|
+
position: fixed;
|
|
250
|
+
right: 16px;
|
|
251
|
+
bottom: 16px;
|
|
252
|
+
/* Above the switcher's own panel: it is the switcher that triggers it. */
|
|
253
|
+
z-index: 2147483647;
|
|
254
|
+
display: block;
|
|
255
|
+
font-family: system-ui, sans-serif;
|
|
256
|
+
pointer-events: none;
|
|
257
|
+
}
|
|
258
|
+
.flash {
|
|
259
|
+
padding: 8px 14px;
|
|
260
|
+
color: light-dark(#0f172a, #e2e8f0);
|
|
261
|
+
font-size: 13px;
|
|
262
|
+
background: light-dark(white, #1e293b);
|
|
263
|
+
border-radius: 8px;
|
|
264
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
265
|
+
color-scheme: light dark;
|
|
266
|
+
}
|
|
267
|
+
.flash[data-error] {
|
|
268
|
+
color: light-dark(#991b1b, #fecaca);
|
|
269
|
+
background: light-dark(#fee2e2, #7f1d1d);
|
|
270
|
+
}
|
|
271
|
+
`;
|
|
272
|
+
|
|
233
273
|
let pagesPromise = null;
|
|
234
274
|
const loadPages = () => {
|
|
235
275
|
// Once per page load: the list is a filesystem scan behind a short cache
|
|
@@ -240,6 +280,54 @@
|
|
|
240
280
|
return pagesPromise;
|
|
241
281
|
};
|
|
242
282
|
|
|
283
|
+
// Opening a file in an editor happens in another application, on another
|
|
284
|
+
// screen sometimes: without a word here, a press that failed and a press that
|
|
285
|
+
// worked look exactly the same. Its own host and its own shadow root, so it
|
|
286
|
+
// can be shown whether or not the switcher is open.
|
|
287
|
+
let flashHost = null;
|
|
288
|
+
let flashBox = null;
|
|
289
|
+
let flashTimeout = null;
|
|
290
|
+
const flash = (message, isError) => {
|
|
291
|
+
if (!flashHost) {
|
|
292
|
+
flashHost = document.createElement("div");
|
|
293
|
+
const shadow = flashHost.attachShadow({ mode: "open" });
|
|
294
|
+
const style = document.createElement("style");
|
|
295
|
+
style.textContent = FLASH_STYLE_TEXT;
|
|
296
|
+
flashBox = document.createElement("div");
|
|
297
|
+
flashBox.className = "flash";
|
|
298
|
+
shadow.append(style, flashBox);
|
|
299
|
+
}
|
|
300
|
+
flashBox.textContent = message;
|
|
301
|
+
flashBox.toggleAttribute("data-error", Boolean(isError));
|
|
302
|
+
// Appended last every time, so it sits above the switcher's host when both
|
|
303
|
+
// are on the page and they share the same z-index.
|
|
304
|
+
document.body.append(flashHost);
|
|
305
|
+
window.clearTimeout(flashTimeout);
|
|
306
|
+
flashTimeout = window.setTimeout(() => flashHost.remove(), 2500);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const openInEditor = async (file) => {
|
|
310
|
+
if (!file || !file.fileUrl) {
|
|
311
|
+
flash("This page is not a file the server lists.", true);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
flash(`Opening ${file.name || file.url} in editor…`);
|
|
315
|
+
try {
|
|
316
|
+
const response = await fetch(
|
|
317
|
+
`${OPEN_FILE_ENDPOINT}${encodeURIComponent(file.fileUrl)}`,
|
|
318
|
+
);
|
|
319
|
+
if (response.status === 404) {
|
|
320
|
+
// The route exists only when the server is willing to expose the
|
|
321
|
+
// machine it runs on (see start_server.js).
|
|
322
|
+
flash("This server does not open files in an editor.", true);
|
|
323
|
+
} else if (!response.ok) {
|
|
324
|
+
flash(`Editor said no (${response.status}).`, true);
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
flash("Could not reach the dev server.", true);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
|
|
243
331
|
const readStoredState = () => {
|
|
244
332
|
try {
|
|
245
333
|
const stored = JSON.parse(
|
|
@@ -396,7 +484,11 @@
|
|
|
396
484
|
panel.className = "panel";
|
|
397
485
|
const input = document.createElement("input");
|
|
398
486
|
input.type = "search";
|
|
399
|
-
|
|
487
|
+
// The other key is written where one is already looking: a shortcut nobody
|
|
488
|
+
// is told about is a shortcut nobody presses. Both names, not the one this
|
|
489
|
+
// platform uses — the reader knows which of the two their keyboard has, and
|
|
490
|
+
// it keeps what the panel says the same everywhere.
|
|
491
|
+
input.placeholder = "Go to page… (cmd/ctrl+E to open in editor)";
|
|
400
492
|
input.setAttribute("aria-label", "Go to page");
|
|
401
493
|
const kindsRow = document.createElement("div");
|
|
402
494
|
kindsRow.className = "kinds";
|
|
@@ -664,6 +756,21 @@
|
|
|
664
756
|
toggleCollapsed(row.node.path);
|
|
665
757
|
return;
|
|
666
758
|
}
|
|
759
|
+
if (isEditorKey(event)) {
|
|
760
|
+
// Taken whatever the row is: let go of on a directory it would reach
|
|
761
|
+
// the page below and open the page one came from, which is not what a
|
|
762
|
+
// key pressed inside an open switcher can be asking for.
|
|
763
|
+
stop();
|
|
764
|
+
const row = rows[currentIndex];
|
|
765
|
+
if (!row || row.type !== "file") {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
// Done with the switcher: the answer to "where does this live" arrives
|
|
769
|
+
// in the editor, not here.
|
|
770
|
+
close();
|
|
771
|
+
openInEditor(row.file);
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
667
774
|
if (isSwitcherKey(event)) {
|
|
668
775
|
stop();
|
|
669
776
|
close();
|
|
@@ -708,15 +815,31 @@
|
|
|
708
815
|
// preventDefault, the key was theirs and nothing happens here.
|
|
709
816
|
const listenSwitcherKey = () => {
|
|
710
817
|
window.addEventListener("keydown", (event) => {
|
|
711
|
-
if (event.defaultPrevented
|
|
818
|
+
if (event.defaultPrevented) {
|
|
712
819
|
return;
|
|
713
820
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
821
|
+
if (isSwitcherKey(event)) {
|
|
822
|
+
// Ours now: the browser has its own use for cmd+K (the address bar),
|
|
823
|
+
// which it must not get.
|
|
824
|
+
event.preventDefault();
|
|
825
|
+
openSwitcher();
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (isEditorKey(event)) {
|
|
829
|
+
event.preventDefault();
|
|
830
|
+
openCurrentPageInEditor();
|
|
831
|
+
}
|
|
718
832
|
});
|
|
719
833
|
};
|
|
834
|
+
// The page one is looking at, in the editor. The list is where the file url
|
|
835
|
+
// comes from, so a page the server does not list (an @fs url, something under
|
|
836
|
+
// node_modules) says so rather than opening the wrong thing.
|
|
837
|
+
const openCurrentPageInEditor = async () => {
|
|
838
|
+
const here = currentPageUrl();
|
|
839
|
+
const pages = await loadPages();
|
|
840
|
+
const page = pages.find((candidate) => candidate.url === here);
|
|
841
|
+
openInEditor(page && { ...page, name: here.split("/").pop() });
|
|
842
|
+
};
|
|
720
843
|
const setup = () => {
|
|
721
844
|
listenSwitcherKey();
|
|
722
845
|
if (wasOpen()) {
|
|
@@ -46,7 +46,11 @@
|
|
|
46
46
|
import { initDependencyStatus } from "/@fs/Users/dmail/Documents/dev/jsenv/core/src/plugins/dependency_status/client/dependency_status.js";
|
|
47
47
|
|
|
48
48
|
initDependencyStatus({
|
|
49
|
-
"problems": []
|
|
49
|
+
"problems": [],
|
|
50
|
+
"watchInfo": {
|
|
51
|
+
"packageJsonPath": "package.json",
|
|
52
|
+
"pollInterval": 500
|
|
53
|
+
}
|
|
50
54
|
});
|
|
51
55
|
</script>
|
|
52
56
|
<script type="module" jsenv-injected-by="jsenv:drop_to_open" content-indented="">
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": [
|
|
4
|
+
"file:///Users/dmail/Documents/dev/jsenv/core/node_modules/@xterm/addon-fit/src/FitAddon.ts"
|
|
5
|
+
],
|
|
6
|
+
"sourcesContent": [
|
|
7
|
+
"/**\n * Copyright (c) 2017 The xterm.js authors. All rights reserved.\n * @license MIT\n */\n\nimport type { Terminal, ITerminalAddon } from '@xterm/xterm';\nimport type { FitAddon as IFitApi } from '@xterm/addon-fit';\nimport { IRenderDimensions } from 'browser/renderer/shared/Types';\nimport { ViewportConstants } from 'browser/shared/Constants';\n\ninterface ITerminalDimensions {\n /**\n * The number of rows in the terminal.\n */\n rows: number;\n\n /**\n * The number of columns in the terminal.\n */\n cols: number;\n}\n\nconst MINIMUM_COLS = 2;\nconst MINIMUM_ROWS = 1;\n\nexport class FitAddon implements ITerminalAddon , IFitApi {\n private _terminal: Terminal | undefined;\n\n public activate(terminal: Terminal): void {\n this._terminal = terminal;\n }\n\n public dispose(): void {}\n\n public fit(): void {\n const dims = this.proposeDimensions();\n if (!dims || !this._terminal || isNaN(dims.cols) || isNaN(dims.rows)) {\n return;\n }\n\n // TODO: Remove reliance on private API\n const core = (this._terminal as any)._core;\n\n // Force a full render\n if (this._terminal.rows !== dims.rows || this._terminal.cols !== dims.cols) {\n core._renderService.clear();\n this._terminal.resize(dims.cols, dims.rows);\n }\n }\n\n public proposeDimensions(): ITerminalDimensions | undefined {\n if (!this._terminal) {\n return undefined;\n }\n\n if (!this._terminal.element || !this._terminal.element.parentElement) {\n return undefined;\n }\n\n // TODO: Remove reliance on private API\n const core = (this._terminal as any)._core;\n const dims: IRenderDimensions = core._renderService.dimensions;\n\n if (dims.css.cell.width === 0 || dims.css.cell.height === 0) {\n return undefined;\n }\n\n const scrollbarWidth = (this._terminal.options.scrollback === 0\n ? 0\n : (this._terminal.options.overviewRuler?.width || ViewportConstants.DEFAULT_SCROLL_BAR_WIDTH));\n\n const parentElementStyle = window.getComputedStyle(this._terminal.element.parentElement);\n const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height'));\n const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')));\n const elementStyle = window.getComputedStyle(this._terminal.element);\n const elementPadding = {\n top: parseInt(elementStyle.getPropertyValue('padding-top')),\n bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')),\n right: parseInt(elementStyle.getPropertyValue('padding-right')),\n left: parseInt(elementStyle.getPropertyValue('padding-left'))\n };\n const elementPaddingVer = elementPadding.top + elementPadding.bottom;\n const elementPaddingHor = elementPadding.right + elementPadding.left;\n const availableHeight = parentElementHeight - elementPaddingVer;\n const availableWidth = parentElementWidth - elementPaddingHor - scrollbarWidth;\n const geometry = {\n cols: Math.max(MINIMUM_COLS, Math.floor(availableWidth / dims.css.cell.width)),\n rows: Math.max(MINIMUM_ROWS, Math.floor(availableHeight / dims.css.cell.height))\n };\n return geometry;\n }\n}\n"
|
|
8
|
+
],
|
|
9
|
+
"mappings": ";;;;;;;;;;;;;;;;AAsBA,IAAMA,EAAe,EACfC,EAAe,EAERC,EAAN,KAAmD,CAGjD,SAASC,EAA0B,CACxC,KAAK,UAAYA,CACnB,CAEO,SAAgB,CAAC,CAEjB,KAAY,CACjB,IAAMC,EAAO,KAAK,kBAAkB,EACpC,GAAI,CAACA,GAAQ,CAAC,KAAK,WAAa,MAAMA,EAAK,IAAI,GAAK,MAAMA,EAAK,IAAI,EACjE,OAIF,IAAMC,EAAQ,KAAK,UAAkB,OAGjC,KAAK,UAAU,OAASD,EAAK,MAAQ,KAAK,UAAU,OAASA,EAAK,QACpEC,EAAK,eAAe,MAAM,EAC1B,KAAK,UAAU,OAAOD,EAAK,KAAMA,EAAK,IAAI,EAE9C,CAEO,mBAAqD,CAK1D,GAJI,CAAC,KAAK,WAIN,CAAC,KAAK,UAAU,SAAW,CAAC,KAAK,UAAU,QAAQ,cACrD,OAKF,IAAMA,EADQ,KAAK,UAAkB,MACA,eAAe,WAEpD,GAAIA,EAAK,IAAI,KAAK,QAAU,GAAKA,EAAK,IAAI,KAAK,SAAW,EACxD,OAGF,IAAME,EAAkB,KAAK,UAAU,QAAQ,aAAe,EAC1D,EACC,KAAK,UAAU,QAAQ,eAAe,OAAS,GAE9CC,EAAqB,OAAO,iBAAiB,KAAK,UAAU,QAAQ,aAAa,EACjFC,EAAsB,SAASD,EAAmB,iBAAiB,QAAQ,CAAC,EAC5EE,EAAqB,KAAK,IAAI,EAAG,SAASF,EAAmB,iBAAiB,OAAO,CAAC,CAAC,EACvFG,EAAe,OAAO,iBAAiB,KAAK,UAAU,OAAO,EAC7DC,EAAiB,CACrB,IAAK,SAASD,EAAa,iBAAiB,aAAa,CAAC,EAC1D,OAAQ,SAASA,EAAa,iBAAiB,gBAAgB,CAAC,EAChE,MAAO,SAASA,EAAa,iBAAiB,eAAe,CAAC,EAC9D,KAAM,SAASA,EAAa,iBAAiB,cAAc,CAAC,CAC9D,EACME,EAAoBD,EAAe,IAAMA,EAAe,OACxDE,EAAoBF,EAAe,MAAQA,EAAe,KAC1DG,EAAkBN,EAAsBI,EACxCG,EAAiBN,EAAqBI,EAAoBP,EAKhE,MAJiB,CACf,KAAM,KAAK,IAAIN,EAAc,KAAK,MAAMe,EAAiBX,EAAK,IAAI,KAAK,KAAK,CAAC,EAC7E,KAAM,KAAK,IAAIH,EAAc,KAAK,MAAMa,EAAkBV,EAAK,IAAI,KAAK,MAAM,CAAC,CACjF,CAEF,CACF",
|
|
10
|
+
"names": [
|
|
11
|
+
"MINIMUM_COLS",
|
|
12
|
+
"MINIMUM_ROWS",
|
|
13
|
+
"FitAddon",
|
|
14
|
+
"terminal",
|
|
15
|
+
"dims",
|
|
16
|
+
"core",
|
|
17
|
+
"scrollbarWidth",
|
|
18
|
+
"parentElementStyle",
|
|
19
|
+
"parentElementHeight",
|
|
20
|
+
"parentElementWidth",
|
|
21
|
+
"elementStyle",
|
|
22
|
+
"elementPadding",
|
|
23
|
+
"elementPaddingVer",
|
|
24
|
+
"elementPaddingHor",
|
|
25
|
+
"availableHeight",
|
|
26
|
+
"availableWidth"
|
|
27
|
+
]
|
|
28
|
+
}
|