@ecmaos/coreutils 0.3.1 → 0.4.2
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +48 -0
- package/dist/commands/awk.d.ts +4 -0
- package/dist/commands/awk.d.ts.map +1 -0
- package/dist/commands/awk.js +324 -0
- package/dist/commands/awk.js.map +1 -0
- package/dist/commands/chgrp.d.ts +4 -0
- package/dist/commands/chgrp.d.ts.map +1 -0
- package/dist/commands/chgrp.js +187 -0
- package/dist/commands/chgrp.js.map +1 -0
- package/dist/commands/chmod.d.ts.map +1 -1
- package/dist/commands/chmod.js +139 -2
- package/dist/commands/chmod.js.map +1 -1
- package/dist/commands/chown.d.ts +4 -0
- package/dist/commands/chown.d.ts.map +1 -0
- package/dist/commands/chown.js +257 -0
- package/dist/commands/chown.js.map +1 -0
- package/dist/commands/cksum.d.ts +4 -0
- package/dist/commands/cksum.d.ts.map +1 -0
- package/dist/commands/cksum.js +124 -0
- package/dist/commands/cksum.js.map +1 -0
- package/dist/commands/cmp.d.ts +4 -0
- package/dist/commands/cmp.d.ts.map +1 -0
- package/dist/commands/cmp.js +120 -0
- package/dist/commands/cmp.js.map +1 -0
- package/dist/commands/column.d.ts +4 -0
- package/dist/commands/column.d.ts.map +1 -0
- package/dist/commands/column.js +274 -0
- package/dist/commands/column.js.map +1 -0
- package/dist/commands/cp.d.ts.map +1 -1
- package/dist/commands/cp.js +81 -4
- package/dist/commands/cp.js.map +1 -1
- package/dist/commands/cron.d.ts.map +1 -1
- package/dist/commands/cron.js +116 -23
- package/dist/commands/cron.js.map +1 -1
- package/dist/commands/curl.d.ts +4 -0
- package/dist/commands/curl.d.ts.map +1 -0
- package/dist/commands/curl.js +238 -0
- package/dist/commands/curl.js.map +1 -0
- package/dist/commands/du.d.ts +4 -0
- package/dist/commands/du.d.ts.map +1 -0
- package/dist/commands/du.js +168 -0
- package/dist/commands/du.js.map +1 -0
- package/dist/commands/echo.d.ts.map +1 -1
- package/dist/commands/echo.js +125 -2
- package/dist/commands/echo.js.map +1 -1
- package/dist/commands/env.d.ts +4 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +129 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/expand.d.ts +4 -0
- package/dist/commands/expand.d.ts.map +1 -0
- package/dist/commands/expand.js +197 -0
- package/dist/commands/expand.js.map +1 -0
- package/dist/commands/factor.d.ts +4 -0
- package/dist/commands/factor.d.ts.map +1 -0
- package/dist/commands/factor.js +141 -0
- package/dist/commands/factor.js.map +1 -0
- package/dist/commands/fmt.d.ts +4 -0
- package/dist/commands/fmt.d.ts.map +1 -0
- package/dist/commands/fmt.js +278 -0
- package/dist/commands/fmt.js.map +1 -0
- package/dist/commands/fold.d.ts +4 -0
- package/dist/commands/fold.d.ts.map +1 -0
- package/dist/commands/fold.js +253 -0
- package/dist/commands/fold.js.map +1 -0
- package/dist/commands/groups.d.ts +4 -0
- package/dist/commands/groups.d.ts.map +1 -0
- package/dist/commands/groups.js +61 -0
- package/dist/commands/groups.js.map +1 -0
- package/dist/commands/head.d.ts.map +1 -1
- package/dist/commands/head.js +184 -77
- package/dist/commands/head.js.map +1 -1
- package/dist/commands/hostname.d.ts +4 -0
- package/dist/commands/hostname.d.ts.map +1 -0
- package/dist/commands/hostname.js +80 -0
- package/dist/commands/hostname.js.map +1 -0
- package/dist/commands/less.d.ts.map +1 -1
- package/dist/commands/less.js +1 -0
- package/dist/commands/less.js.map +1 -1
- package/dist/commands/man.d.ts.map +1 -1
- package/dist/commands/man.js +3 -1
- package/dist/commands/man.js.map +1 -1
- package/dist/commands/mount.d.ts +4 -0
- package/dist/commands/mount.d.ts.map +1 -0
- package/dist/commands/mount.js +1136 -0
- package/dist/commands/mount.js.map +1 -0
- package/dist/commands/od.d.ts +4 -0
- package/dist/commands/od.d.ts.map +1 -0
- package/dist/commands/od.js +342 -0
- package/dist/commands/od.js.map +1 -0
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +298 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/printf.d.ts +4 -0
- package/dist/commands/printf.d.ts.map +1 -0
- package/dist/commands/printf.js +271 -0
- package/dist/commands/printf.js.map +1 -0
- package/dist/commands/readlink.d.ts +4 -0
- package/dist/commands/readlink.d.ts.map +1 -0
- package/dist/commands/readlink.js +104 -0
- package/dist/commands/readlink.js.map +1 -0
- package/dist/commands/realpath.d.ts +4 -0
- package/dist/commands/realpath.d.ts.map +1 -0
- package/dist/commands/realpath.js +111 -0
- package/dist/commands/realpath.js.map +1 -0
- package/dist/commands/rev.d.ts +4 -0
- package/dist/commands/rev.d.ts.map +1 -0
- package/dist/commands/rev.js +134 -0
- package/dist/commands/rev.js.map +1 -0
- package/dist/commands/shuf.d.ts +4 -0
- package/dist/commands/shuf.d.ts.map +1 -0
- package/dist/commands/shuf.js +221 -0
- package/dist/commands/shuf.js.map +1 -0
- package/dist/commands/sleep.d.ts +4 -0
- package/dist/commands/sleep.d.ts.map +1 -0
- package/dist/commands/sleep.js +102 -0
- package/dist/commands/sleep.js.map +1 -0
- package/dist/commands/strings.d.ts +4 -0
- package/dist/commands/strings.d.ts.map +1 -0
- package/dist/commands/strings.js +170 -0
- package/dist/commands/strings.js.map +1 -0
- package/dist/commands/tac.d.ts +4 -0
- package/dist/commands/tac.d.ts.map +1 -0
- package/dist/commands/tac.js +130 -0
- package/dist/commands/tac.js.map +1 -0
- package/dist/commands/time.d.ts +4 -0
- package/dist/commands/time.d.ts.map +1 -0
- package/dist/commands/time.js +126 -0
- package/dist/commands/time.js.map +1 -0
- package/dist/commands/umount.d.ts +4 -0
- package/dist/commands/umount.d.ts.map +1 -0
- package/dist/commands/umount.js +103 -0
- package/dist/commands/umount.js.map +1 -0
- package/dist/commands/uname.d.ts +4 -0
- package/dist/commands/uname.d.ts.map +1 -0
- package/dist/commands/uname.js +149 -0
- package/dist/commands/uname.js.map +1 -0
- package/dist/commands/unexpand.d.ts +4 -0
- package/dist/commands/unexpand.d.ts.map +1 -0
- package/dist/commands/unexpand.js +286 -0
- package/dist/commands/unexpand.js.map +1 -0
- package/dist/commands/uptime.d.ts +4 -0
- package/dist/commands/uptime.d.ts.map +1 -0
- package/dist/commands/uptime.js +62 -0
- package/dist/commands/uptime.js.map +1 -0
- package/dist/commands/view.d.ts +1 -0
- package/dist/commands/view.d.ts.map +1 -1
- package/dist/commands/view.js +408 -66
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/yes.d.ts +4 -0
- package/dist/commands/yes.d.ts.map +1 -0
- package/dist/commands/yes.js +58 -0
- package/dist/commands/yes.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -3
- package/src/commands/awk.ts +340 -0
- package/src/commands/chmod.ts +141 -2
- package/src/commands/chown.ts +321 -0
- package/src/commands/cksum.ts +133 -0
- package/src/commands/cmp.ts +126 -0
- package/src/commands/column.ts +273 -0
- package/src/commands/cp.ts +93 -4
- package/src/commands/cron.ts +115 -23
- package/src/commands/curl.ts +231 -0
- package/src/commands/echo.ts +122 -2
- package/src/commands/env.ts +143 -0
- package/src/commands/expand.ts +207 -0
- package/src/commands/factor.ts +151 -0
- package/src/commands/fmt.ts +293 -0
- package/src/commands/fold.ts +257 -0
- package/src/commands/groups.ts +72 -0
- package/src/commands/head.ts +176 -77
- package/src/commands/hostname.ts +81 -0
- package/src/commands/less.ts +1 -0
- package/src/commands/man.ts +4 -1
- package/src/commands/mount.ts +1302 -0
- package/src/commands/od.ts +327 -0
- package/src/commands/pr.ts +291 -0
- package/src/commands/printf.ts +271 -0
- package/src/commands/readlink.ts +102 -0
- package/src/commands/realpath.ts +126 -0
- package/src/commands/rev.ts +143 -0
- package/src/commands/shuf.ts +218 -0
- package/src/commands/sleep.ts +109 -0
- package/src/commands/strings.ts +176 -0
- package/src/commands/tac.ts +138 -0
- package/src/commands/time.ts +144 -0
- package/src/commands/umount.ts +116 -0
- package/src/commands/uname.ts +130 -0
- package/src/commands/unexpand.ts +305 -0
- package/src/commands/uptime.ts +73 -0
- package/src/commands/view.ts +463 -73
- package/src/index.ts +82 -0
- package/tsconfig.json +4 -0
package/src/commands/view.ts
CHANGED
|
@@ -1,47 +1,46 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
2
|
import chalk from 'chalk'
|
|
3
|
+
import { marked } from 'marked'
|
|
4
|
+
import '@alenaksu/json-viewer'
|
|
3
5
|
import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
|
|
4
6
|
import { TerminalCommand } from '../shared/terminal-command.js'
|
|
5
7
|
import { writelnStderr, writelnStdout } from '../shared/helpers.js'
|
|
6
8
|
|
|
7
|
-
type FileType = 'pdf' | 'image' | 'audio' | 'video'
|
|
9
|
+
type FileType = 'pdf' | 'image' | 'audio' | 'video' | 'markdown' | 'json' | 'application/octet-stream'
|
|
8
10
|
|
|
9
11
|
function detectFileType(filePath: string): FileType {
|
|
10
12
|
const ext = path.extname(filePath).toLowerCase()
|
|
11
13
|
|
|
12
14
|
// PDF
|
|
13
|
-
if (ext === '.pdf')
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
if (ext === '.pdf') return 'pdf'
|
|
16
|
+
|
|
17
|
+
// Markdown
|
|
18
|
+
const markdownExts = ['.md', '.markdown', '.mdown', '.mkd', '.mkdn']
|
|
19
|
+
if (markdownExts.includes(ext)) return 'markdown'
|
|
20
|
+
|
|
21
|
+
// JSON
|
|
22
|
+
const jsonExts = ['.json', '.jsonl', '.jsonc', '.jsonld']
|
|
23
|
+
if (jsonExts.includes(ext)) return 'json'
|
|
16
24
|
|
|
17
25
|
// Images
|
|
18
26
|
const imageExts = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp', '.svg', '.ico', '.tiff', '.tif']
|
|
19
|
-
if (imageExts.includes(ext))
|
|
20
|
-
return 'image'
|
|
21
|
-
}
|
|
27
|
+
if (imageExts.includes(ext)) return 'image'
|
|
22
28
|
|
|
23
29
|
// Audio
|
|
24
30
|
const audioExts = ['.mp3', '.wav', '.ogg', '.oga', '.opus', '.m4a', '.aac', '.flac', '.webm', '.wma', '.aiff', '.aif', '.3gp', '.amr']
|
|
25
|
-
if (audioExts.includes(ext))
|
|
26
|
-
return 'audio'
|
|
27
|
-
}
|
|
31
|
+
if (audioExts.includes(ext)) return 'audio'
|
|
28
32
|
|
|
29
33
|
// Video
|
|
30
34
|
const videoExts = ['.mp4', '.webm', '.ogg', '.ogv', '.mov', '.avi', '.mkv', '.m4v', '.flv', '.wmv', '.3gp']
|
|
31
|
-
if (videoExts.includes(ext))
|
|
32
|
-
return 'video'
|
|
33
|
-
}
|
|
35
|
+
if (videoExts.includes(ext)) return 'video'
|
|
34
36
|
|
|
35
|
-
|
|
36
|
-
return 'image'
|
|
37
|
+
return 'application/octet-stream'
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
function getMimeType(filePath: string, fileType: FileType): string {
|
|
40
41
|
const ext = path.extname(filePath).toLowerCase()
|
|
41
42
|
|
|
42
|
-
if (fileType === 'pdf')
|
|
43
|
-
return 'application/pdf'
|
|
44
|
-
}
|
|
43
|
+
if (fileType === 'pdf') return 'application/pdf'
|
|
45
44
|
|
|
46
45
|
if (fileType === 'image') {
|
|
47
46
|
const mimeTypes: Record<string, string> = {
|
|
@@ -95,13 +94,15 @@ function getMimeType(filePath: string, fileType: FileType): string {
|
|
|
95
94
|
}
|
|
96
95
|
return mimeTypes[ext] || 'video/mp4'
|
|
97
96
|
}
|
|
98
|
-
|
|
97
|
+
|
|
98
|
+
if (fileType === 'json') return 'application/json'
|
|
99
|
+
|
|
99
100
|
return 'application/octet-stream'
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
function printUsage(process: Process | undefined, terminal: Terminal): void {
|
|
103
104
|
const usage = `Usage: view [OPTIONS] [FILE...]
|
|
104
|
-
View files in a new window. Supports PDF, images, audio, and video files.
|
|
105
|
+
View files in a new window. Supports PDF, markdown, JSON, images, audio, and video files.
|
|
105
106
|
|
|
106
107
|
--help display this help and exit
|
|
107
108
|
|
|
@@ -118,13 +119,15 @@ Audio/Video Options (for audio and video files):
|
|
|
118
119
|
|
|
119
120
|
Examples:
|
|
120
121
|
view document.pdf view a PDF file
|
|
122
|
+
view README.md view a markdown file
|
|
123
|
+
view data.json view a JSON file
|
|
121
124
|
view image.png view an image
|
|
122
125
|
view song.mp3 view/play an audio file
|
|
123
126
|
view movie.mp4 view/play a video file
|
|
124
|
-
view --loop music.mp3
|
|
125
|
-
view --no-autoplay video.mp4
|
|
126
|
-
view --volume 50 track.mp3
|
|
127
|
-
view --fullscreen movie.mp4
|
|
127
|
+
view --loop music.mp3 play audio in a loop
|
|
128
|
+
view --no-autoplay video.mp4 load video without auto-playing
|
|
129
|
+
view --volume 50 track.mp3 play at 50% volume
|
|
130
|
+
view --fullscreen movie.mp4 play video in fullscreen mode`
|
|
128
131
|
writelnStderr(process, terminal, usage)
|
|
129
132
|
}
|
|
130
133
|
|
|
@@ -182,6 +185,11 @@ function formatDuration(seconds: number): string {
|
|
|
182
185
|
return `${minutes}:${secs.toString().padStart(2, '0')}`
|
|
183
186
|
}
|
|
184
187
|
|
|
188
|
+
function generateRandomClass(prefix: string): string {
|
|
189
|
+
const randomSuffix = Math.random().toString(36).substring(2, 8)
|
|
190
|
+
return `${prefix}-${randomSuffix}`
|
|
191
|
+
}
|
|
192
|
+
|
|
185
193
|
export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
|
|
186
194
|
return new TerminalCommand({
|
|
187
195
|
command: 'view',
|
|
@@ -307,7 +315,312 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
307
315
|
? `${path.basename(file)} (${files.indexOf(file) + 1}/${files.length})`
|
|
308
316
|
: path.basename(file)
|
|
309
317
|
|
|
310
|
-
if (fileType === '
|
|
318
|
+
if (fileType === 'markdown') {
|
|
319
|
+
// Read and parse markdown file
|
|
320
|
+
const markdownText = new TextDecoder().decode(fileData)
|
|
321
|
+
const htmlContent = await marked.parse(markdownText)
|
|
322
|
+
|
|
323
|
+
const containerClass = generateRandomClass('markdown-container')
|
|
324
|
+
|
|
325
|
+
const container = document.createElement('div')
|
|
326
|
+
container.className = containerClass
|
|
327
|
+
container.style.width = '100%'
|
|
328
|
+
container.style.height = '100%'
|
|
329
|
+
container.style.display = 'flex'
|
|
330
|
+
container.style.flexDirection = 'column'
|
|
331
|
+
container.style.background = '#1e1e1e'
|
|
332
|
+
container.style.overflow = 'auto'
|
|
333
|
+
container.style.padding = '40px'
|
|
334
|
+
container.style.boxSizing = 'border-box'
|
|
335
|
+
container.style.fontFamily = "-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,'Helvetica Neue',Arial,sans-serif"
|
|
336
|
+
container.style.color = '#e0e0e0'
|
|
337
|
+
container.style.lineHeight = '1.6'
|
|
338
|
+
|
|
339
|
+
const contentWrapper = document.createElement('div')
|
|
340
|
+
contentWrapper.style.maxWidth = '800px'
|
|
341
|
+
contentWrapper.style.margin = '0 auto'
|
|
342
|
+
contentWrapper.style.width = '100%'
|
|
343
|
+
contentWrapper.innerHTML = htmlContent
|
|
344
|
+
|
|
345
|
+
const style = document.createElement('style')
|
|
346
|
+
style.textContent = `
|
|
347
|
+
.${containerClass} h1, .${containerClass} h2, .${containerClass} h3, .${containerClass} h4, .${containerClass} h5, .${containerClass} h6 { color: #fff; margin-top: 1.5em; margin-bottom: 0.5em; }
|
|
348
|
+
.${containerClass} h1 { font-size: 2em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
|
|
349
|
+
.${containerClass} h2 { font-size: 1.5em; border-bottom: 1px solid #444; padding-bottom: 0.3em; }
|
|
350
|
+
.${containerClass} h3 { font-size: 1.25em; }
|
|
351
|
+
.${containerClass} code { background-color: #2d2d2d; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; color: #f8f8f2; }
|
|
352
|
+
.${containerClass} pre { background-color: #2d2d2d; padding: 16px; border-radius: 6px; overflow-x: auto; }
|
|
353
|
+
.${containerClass} pre code { background-color: transparent; padding: 0; }
|
|
354
|
+
.${containerClass} a { color: #4a9eff; text-decoration: none; }
|
|
355
|
+
.${containerClass} a:hover { text-decoration: underline; }
|
|
356
|
+
.${containerClass} blockquote { border-left: 4px solid #4a9eff; padding-left: 16px; margin-left: 0; color: #b0b0b0; }
|
|
357
|
+
.${containerClass} table { border-collapse: collapse; width: 100%; margin: 1em 0; }
|
|
358
|
+
.${containerClass} th, .${containerClass} td { border: 1px solid #444; padding: 8px 12px; text-align: left; }
|
|
359
|
+
.${containerClass} th { background-color: #2d2d2d; font-weight: bold; }
|
|
360
|
+
.${containerClass} tr:nth-child(even) { background-color: #252525; }
|
|
361
|
+
.${containerClass} img { max-width: 100%; height: auto; }
|
|
362
|
+
.${containerClass} ul, .${containerClass} ol { padding-left: 2em; }
|
|
363
|
+
.${containerClass} hr { border: none; border-top: 1px solid #444; margin: 2em 0; }
|
|
364
|
+
`
|
|
365
|
+
|
|
366
|
+
container.appendChild(style)
|
|
367
|
+
container.appendChild(contentWrapper)
|
|
368
|
+
|
|
369
|
+
const win = kernel.windows.create({
|
|
370
|
+
title: windowTitle,
|
|
371
|
+
width: 900,
|
|
372
|
+
height: 700,
|
|
373
|
+
max: false
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
win.mount(container)
|
|
377
|
+
|
|
378
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
|
|
379
|
+
} else if (fileType === 'json') {
|
|
380
|
+
// Read and parse JSON file
|
|
381
|
+
const jsonText = new TextDecoder().decode(fileData)
|
|
382
|
+
let jsonData: unknown
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
jsonData = JSON.parse(jsonText)
|
|
386
|
+
} catch (error) {
|
|
387
|
+
await writelnStderr(process, terminal, chalk.red(`view: invalid JSON in ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`))
|
|
388
|
+
continue
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
type JsonViewerElement = HTMLElement & {
|
|
392
|
+
data?: unknown
|
|
393
|
+
filter(regexOrPath: RegExp | string): void
|
|
394
|
+
resetFilter(): void
|
|
395
|
+
expand(regexOrPath: RegExp | string): void
|
|
396
|
+
expandAll(): void
|
|
397
|
+
collapse(regexOrPath: RegExp | string): void
|
|
398
|
+
collapseAll(): void
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const containerClass = generateRandomClass('json-container')
|
|
402
|
+
|
|
403
|
+
const container = document.createElement('div')
|
|
404
|
+
container.className = containerClass
|
|
405
|
+
container.style.width = '100%'
|
|
406
|
+
container.style.height = '100%'
|
|
407
|
+
container.style.display = 'flex'
|
|
408
|
+
container.style.flexDirection = 'column'
|
|
409
|
+
container.style.background = '#2a2f3a'
|
|
410
|
+
container.style.overflow = 'hidden'
|
|
411
|
+
|
|
412
|
+
const buttonBar = document.createElement('div')
|
|
413
|
+
buttonBar.style.cssText = `
|
|
414
|
+
display: flex;
|
|
415
|
+
flex-wrap: wrap;
|
|
416
|
+
gap: 6px;
|
|
417
|
+
padding: 6px;
|
|
418
|
+
background: #2a2f3a;
|
|
419
|
+
border-bottom: 1px solid #3c3c3c;
|
|
420
|
+
align-items: center;
|
|
421
|
+
`
|
|
422
|
+
|
|
423
|
+
const createInput = (placeholder: string): HTMLInputElement => {
|
|
424
|
+
const input = document.createElement('input')
|
|
425
|
+
input.type = 'text'
|
|
426
|
+
input.placeholder = placeholder
|
|
427
|
+
input.style.cssText = `
|
|
428
|
+
background: #263040;
|
|
429
|
+
border: 1px solid #3c3c3c;
|
|
430
|
+
border-radius: 3px;
|
|
431
|
+
color: #fff;
|
|
432
|
+
padding: 0 8px;
|
|
433
|
+
font-size: 11px;
|
|
434
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
435
|
+
width: 120px;
|
|
436
|
+
height: 22px;
|
|
437
|
+
box-sizing: border-box;
|
|
438
|
+
line-height: 22px;
|
|
439
|
+
margin: 0;
|
|
440
|
+
vertical-align: middle;
|
|
441
|
+
`
|
|
442
|
+
return input
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const createButton = (text: string): HTMLButtonElement => {
|
|
446
|
+
const button = document.createElement('button')
|
|
447
|
+
button.textContent = text
|
|
448
|
+
button.style.cssText = `
|
|
449
|
+
background: #263040;
|
|
450
|
+
border: 1px solid #263040;
|
|
451
|
+
border-radius: 3px;
|
|
452
|
+
color: #fff;
|
|
453
|
+
cursor: pointer;
|
|
454
|
+
font-size: 11px;
|
|
455
|
+
font-weight: 600;
|
|
456
|
+
padding: 0;
|
|
457
|
+
height: 22px;
|
|
458
|
+
box-sizing: border-box;
|
|
459
|
+
line-height: 22px;
|
|
460
|
+
white-space: nowrap;
|
|
461
|
+
display: flex;
|
|
462
|
+
align-items: center;
|
|
463
|
+
justify-content: center;
|
|
464
|
+
padding-left: 8px;
|
|
465
|
+
padding-right: 8px;
|
|
466
|
+
margin: 0;
|
|
467
|
+
vertical-align: middle;
|
|
468
|
+
`
|
|
469
|
+
button.onmouseenter = () => {
|
|
470
|
+
button.style.background = '#333'
|
|
471
|
+
button.style.borderColor = '#333'
|
|
472
|
+
}
|
|
473
|
+
button.onmouseleave = () => {
|
|
474
|
+
button.style.background = '#263040'
|
|
475
|
+
button.style.borderColor = '#263040'
|
|
476
|
+
}
|
|
477
|
+
button.onmousedown = () => {
|
|
478
|
+
button.style.background = '#263040'
|
|
479
|
+
}
|
|
480
|
+
button.onmouseup = () => {
|
|
481
|
+
button.style.background = '#333'
|
|
482
|
+
}
|
|
483
|
+
return button
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const createSection = (label: string): HTMLDivElement => {
|
|
487
|
+
const section = document.createElement('div')
|
|
488
|
+
section.style.cssText = `
|
|
489
|
+
display: flex;
|
|
490
|
+
align-items: center;
|
|
491
|
+
gap: 4px;
|
|
492
|
+
height: 22px;
|
|
493
|
+
`
|
|
494
|
+
const labelEl = document.createElement('span')
|
|
495
|
+
labelEl.textContent = label
|
|
496
|
+
labelEl.style.cssText = `
|
|
497
|
+
color: #f8f8f2;
|
|
498
|
+
font-size: 11px;
|
|
499
|
+
font-weight: 600;
|
|
500
|
+
width: 50px;
|
|
501
|
+
flex-shrink: 0;
|
|
502
|
+
line-height: 22px;
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: center;
|
|
505
|
+
height: 22px;
|
|
506
|
+
`
|
|
507
|
+
section.appendChild(labelEl)
|
|
508
|
+
return section
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
const filterSection = createSection('Filter:')
|
|
512
|
+
const filterInput = createInput('Regex or path')
|
|
513
|
+
filterSection.appendChild(filterInput)
|
|
514
|
+
|
|
515
|
+
const expandSection = createSection('Expand:')
|
|
516
|
+
const expandInput = createInput('Regex or path')
|
|
517
|
+
const expandButton = createButton('Expand')
|
|
518
|
+
const expandAllButton = createButton('Expand All')
|
|
519
|
+
expandSection.appendChild(expandInput)
|
|
520
|
+
expandSection.appendChild(expandButton)
|
|
521
|
+
expandSection.appendChild(expandAllButton)
|
|
522
|
+
|
|
523
|
+
const collapseSection = createSection('Collapse:')
|
|
524
|
+
const collapseInput = createInput('Regex or path')
|
|
525
|
+
const collapseButton = createButton('Collapse')
|
|
526
|
+
const collapseAllButton = createButton('Collapse All')
|
|
527
|
+
collapseSection.appendChild(collapseInput)
|
|
528
|
+
collapseSection.appendChild(collapseButton)
|
|
529
|
+
collapseSection.appendChild(collapseAllButton)
|
|
530
|
+
|
|
531
|
+
buttonBar.appendChild(filterSection)
|
|
532
|
+
buttonBar.appendChild(expandSection)
|
|
533
|
+
buttonBar.appendChild(collapseSection)
|
|
534
|
+
|
|
535
|
+
const jsonViewer = document.createElement('json-viewer') as JsonViewerElement
|
|
536
|
+
jsonViewer.style.width = '100%'
|
|
537
|
+
jsonViewer.style.flex = '1'
|
|
538
|
+
jsonViewer.style.padding = '0.5rem'
|
|
539
|
+
jsonViewer.style.overflow = 'auto'
|
|
540
|
+
jsonViewer.data = jsonData
|
|
541
|
+
|
|
542
|
+
jsonViewer.style.setProperty('--background-color', '#2a2f3a')
|
|
543
|
+
jsonViewer.style.setProperty('--color', '#f8f8f2')
|
|
544
|
+
jsonViewer.style.setProperty('--font-family', "'Courier New', monospace")
|
|
545
|
+
jsonViewer.style.setProperty('--string-color', '#a3eea0')
|
|
546
|
+
jsonViewer.style.setProperty('--number-color', '#d19a66')
|
|
547
|
+
jsonViewer.style.setProperty('--boolean-color', '#4ba7ef')
|
|
548
|
+
jsonViewer.style.setProperty('--null-color', '#df9cf3')
|
|
549
|
+
jsonViewer.style.setProperty('--property-color', '#6fb3d2')
|
|
550
|
+
jsonViewer.style.setProperty('--preview-color', '#deae8f')
|
|
551
|
+
jsonViewer.style.setProperty('--highlight-color', '#c92a2a')
|
|
552
|
+
|
|
553
|
+
filterInput.addEventListener('input', () => {
|
|
554
|
+
const value = filterInput.value.trim()
|
|
555
|
+
if (value) {
|
|
556
|
+
try {
|
|
557
|
+
const regex = new RegExp(value)
|
|
558
|
+
jsonViewer.filter(regex)
|
|
559
|
+
} catch {
|
|
560
|
+
jsonViewer.filter(value)
|
|
561
|
+
}
|
|
562
|
+
} else {
|
|
563
|
+
jsonViewer.resetFilter()
|
|
564
|
+
}
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
expandButton.onclick = () => {
|
|
568
|
+
const value = expandInput.value.trim()
|
|
569
|
+
if (value) {
|
|
570
|
+
try {
|
|
571
|
+
const regex = new RegExp(value)
|
|
572
|
+
jsonViewer.expand(regex)
|
|
573
|
+
} catch {
|
|
574
|
+
jsonViewer.expand(value)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
expandAllButton.onclick = () => {
|
|
580
|
+
jsonViewer.expandAll()
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
collapseButton.onclick = () => {
|
|
584
|
+
const value = collapseInput.value.trim()
|
|
585
|
+
if (value) {
|
|
586
|
+
try {
|
|
587
|
+
const regex = new RegExp(value)
|
|
588
|
+
jsonViewer.collapse(regex)
|
|
589
|
+
} catch {
|
|
590
|
+
jsonViewer.collapse(value)
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
collapseAllButton.onclick = () => {
|
|
596
|
+
jsonViewer.collapseAll()
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
const handleEnterKey = (input: HTMLInputElement, button: HTMLButtonElement) => {
|
|
600
|
+
input.addEventListener('keydown', (e) => {
|
|
601
|
+
if (e.key === 'Enter') {
|
|
602
|
+
button.click()
|
|
603
|
+
}
|
|
604
|
+
})
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
handleEnterKey(expandInput, expandButton)
|
|
608
|
+
handleEnterKey(collapseInput, collapseButton)
|
|
609
|
+
|
|
610
|
+
container.appendChild(buttonBar)
|
|
611
|
+
container.appendChild(jsonViewer)
|
|
612
|
+
|
|
613
|
+
// Create window
|
|
614
|
+
const win = kernel.windows.create({
|
|
615
|
+
title: windowTitle,
|
|
616
|
+
width: 900,
|
|
617
|
+
height: 700,
|
|
618
|
+
max: false
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
win.mount(container)
|
|
622
|
+
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
|
|
623
|
+
} else if (fileType === 'pdf') {
|
|
311
624
|
// Convert PDF to base64 and display in object tag
|
|
312
625
|
// Use chunked encoding to avoid argument limit issues with large files
|
|
313
626
|
const uint8Array = new Uint8Array(fileData)
|
|
@@ -320,24 +633,48 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
320
633
|
const base64Data = btoa(binaryString)
|
|
321
634
|
const dataUrl = `data:application/pdf;base64,${base64Data}`
|
|
322
635
|
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
636
|
+
const containerClass = generateRandomClass('pdf-container')
|
|
637
|
+
|
|
638
|
+
const container = document.createElement('div')
|
|
639
|
+
container.className = containerClass
|
|
640
|
+
container.style.width = '100%'
|
|
641
|
+
container.style.height = '100%'
|
|
642
|
+
container.style.display = 'flex'
|
|
643
|
+
container.style.flexDirection = 'column'
|
|
644
|
+
container.style.background = '#1e1e1e'
|
|
645
|
+
container.style.overflow = 'hidden'
|
|
646
|
+
|
|
647
|
+
const object = document.createElement('object')
|
|
648
|
+
object.data = dataUrl
|
|
649
|
+
object.type = 'application/pdf'
|
|
650
|
+
object.style.width = '100%'
|
|
651
|
+
object.style.height = '100%'
|
|
652
|
+
object.style.flex = '1'
|
|
653
|
+
|
|
654
|
+
const fallback = document.createElement('p')
|
|
655
|
+
fallback.style.color = '#fff'
|
|
656
|
+
fallback.style.padding = '20px'
|
|
657
|
+
fallback.style.textAlign = 'center'
|
|
658
|
+
fallback.textContent = 'Your browser does not support PDFs. '
|
|
659
|
+
|
|
660
|
+
const downloadLink = document.createElement('a')
|
|
661
|
+
downloadLink.href = dataUrl
|
|
662
|
+
downloadLink.download = path.basename(file)
|
|
663
|
+
downloadLink.style.color = '#4a9eff'
|
|
664
|
+
downloadLink.textContent = 'Download PDF'
|
|
665
|
+
|
|
666
|
+
fallback.appendChild(downloadLink)
|
|
667
|
+
object.appendChild(fallback)
|
|
668
|
+
container.appendChild(object)
|
|
333
669
|
|
|
334
|
-
kernel.windows.create({
|
|
670
|
+
const win = kernel.windows.create({
|
|
335
671
|
title: windowTitle,
|
|
336
|
-
html: pdfHtml,
|
|
337
672
|
width: 800,
|
|
338
673
|
height: 600,
|
|
339
674
|
max: false
|
|
340
675
|
})
|
|
676
|
+
|
|
677
|
+
win.mount(container)
|
|
341
678
|
|
|
342
679
|
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
|
|
343
680
|
} else if (fileType === 'image') {
|
|
@@ -345,19 +682,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
345
682
|
const blob = new Blob([new Uint8Array(fileData)], { type: mimeType })
|
|
346
683
|
const url = URL.createObjectURL(blob)
|
|
347
684
|
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
685
|
+
const containerClass = generateRandomClass('image-container')
|
|
686
|
+
|
|
687
|
+
const container = document.createElement('div')
|
|
688
|
+
container.className = containerClass
|
|
689
|
+
container.style.width = '100%'
|
|
690
|
+
container.style.height = '100%'
|
|
691
|
+
container.style.display = 'flex'
|
|
692
|
+
container.style.alignItems = 'center'
|
|
693
|
+
container.style.justifyContent = 'center'
|
|
694
|
+
container.style.background = '#1e1e1e'
|
|
695
|
+
container.style.overflow = 'auto'
|
|
696
|
+
container.style.padding = '20px'
|
|
697
|
+
container.style.boxSizing = 'border-box'
|
|
698
|
+
|
|
699
|
+
const img = document.createElement('img')
|
|
700
|
+
img.src = url
|
|
701
|
+
img.alt = path.basename(file)
|
|
702
|
+
img.style.maxWidth = '100%'
|
|
703
|
+
img.style.maxHeight = '100%'
|
|
704
|
+
img.style.objectFit = 'contain'
|
|
705
|
+
|
|
706
|
+
container.appendChild(img)
|
|
353
707
|
|
|
354
|
-
kernel.windows.create({
|
|
708
|
+
const win = kernel.windows.create({
|
|
355
709
|
title: windowTitle,
|
|
356
|
-
html: imageHtml,
|
|
357
710
|
width: 800,
|
|
358
711
|
height: 600,
|
|
359
712
|
max: false
|
|
360
713
|
})
|
|
714
|
+
|
|
715
|
+
win.mount(container)
|
|
361
716
|
|
|
362
717
|
await writelnStdout(process, terminal, chalk.green(`Viewing: ${file}`))
|
|
363
718
|
} else if (fileType === 'audio') {
|
|
@@ -401,30 +756,55 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
401
756
|
} else {
|
|
402
757
|
// Create a simple audio player window
|
|
403
758
|
const audioId = `audio-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
404
|
-
const
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
759
|
+
const containerClass = generateRandomClass('audio-container')
|
|
760
|
+
|
|
761
|
+
const container = document.createElement('div')
|
|
762
|
+
container.className = containerClass
|
|
763
|
+
container.style.width = '100%'
|
|
764
|
+
container.style.height = '100%'
|
|
765
|
+
container.style.display = 'flex'
|
|
766
|
+
container.style.flexDirection = 'column'
|
|
767
|
+
container.style.alignItems = 'center'
|
|
768
|
+
container.style.justifyContent = 'center'
|
|
769
|
+
container.style.background = '#1e1e1e'
|
|
770
|
+
container.style.color = '#fff'
|
|
771
|
+
container.style.fontFamily = 'monospace'
|
|
772
|
+
container.style.padding = '20px'
|
|
773
|
+
container.style.boxSizing = 'border-box'
|
|
774
|
+
|
|
775
|
+
const title = document.createElement('div')
|
|
776
|
+
title.textContent = path.basename(file)
|
|
777
|
+
title.style.fontSize = '18px'
|
|
778
|
+
title.style.marginBottom = '20px'
|
|
779
|
+
title.style.textAlign = 'center'
|
|
780
|
+
title.style.wordBreak = 'break-word'
|
|
781
|
+
|
|
782
|
+
const audio = document.createElement('audio')
|
|
783
|
+
audio.id = audioId
|
|
784
|
+
audio.src = url
|
|
785
|
+
audio.controls = true
|
|
786
|
+
audio.style.width = '100%'
|
|
787
|
+
audio.style.maxWidth = '600px'
|
|
788
|
+
|
|
789
|
+
if (options.autoplay) audio.autoplay = true
|
|
790
|
+
if (options.loop) audio.loop = true
|
|
791
|
+
if (options.muted) audio.muted = true
|
|
792
|
+
|
|
793
|
+
audio.volume = options.volume / 100
|
|
794
|
+
audio.addEventListener('play', () => console.log(`Playing: ${file}`))
|
|
795
|
+
audio.addEventListener('ended', () => console.log(`Finished: ${file}`))
|
|
796
|
+
|
|
797
|
+
container.appendChild(title)
|
|
798
|
+
container.appendChild(audio)
|
|
420
799
|
|
|
421
|
-
kernel.windows.create({
|
|
800
|
+
const win = kernel.windows.create({
|
|
422
801
|
title: windowTitle,
|
|
423
|
-
html: audioHtml,
|
|
424
802
|
width: 500,
|
|
425
803
|
height: 200,
|
|
426
804
|
max: false
|
|
427
805
|
})
|
|
806
|
+
|
|
807
|
+
win.mount(container)
|
|
428
808
|
|
|
429
809
|
if (duration > 0) {
|
|
430
810
|
const durationStr = formatDuration(duration)
|
|
@@ -487,24 +867,34 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
|
|
|
487
867
|
windowWidth = Math.max(windowWidth, 320)
|
|
488
868
|
windowHeight = Math.max(windowHeight, 180)
|
|
489
869
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
870
|
+
const containerClass = generateRandomClass('video-container')
|
|
871
|
+
|
|
872
|
+
const container = document.createElement('div')
|
|
873
|
+
container.className = containerClass
|
|
874
|
+
container.style.width = '100%'
|
|
875
|
+
container.style.height = '100%'
|
|
876
|
+
|
|
877
|
+
const video = document.createElement('video')
|
|
878
|
+
video.src = url
|
|
879
|
+
video.style.width = '100%'
|
|
880
|
+
video.style.height = '100%'
|
|
881
|
+
video.style.objectFit = 'contain'
|
|
882
|
+
|
|
883
|
+
if (options.autoplay) video.autoplay = true
|
|
884
|
+
if (options.controls) video.controls = true
|
|
885
|
+
if (options.loop) video.loop = true
|
|
886
|
+
if (options.muted) video.muted = true
|
|
497
887
|
|
|
498
|
-
|
|
888
|
+
container.appendChild(video)
|
|
499
889
|
|
|
500
|
-
|
|
501
|
-
kernel.windows.create({
|
|
890
|
+
const win = kernel.windows.create({
|
|
502
891
|
title: windowTitle,
|
|
503
|
-
html: videoHtml,
|
|
504
892
|
width: windowWidth,
|
|
505
893
|
height: windowHeight,
|
|
506
894
|
max: options.fullscreen
|
|
507
895
|
})
|
|
896
|
+
|
|
897
|
+
win.mount(container)
|
|
508
898
|
|
|
509
899
|
if (duration > 0) {
|
|
510
900
|
const minutes = Math.floor(duration / 60)
|