Skip to content

Commit 5b3fdb8

Browse files
cd vfs improvements
1 parent 3bd1b46 commit 5b3fdb8

File tree

3 files changed

+170
-43
lines changed

3 files changed

+170
-43
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
node_modules/
22
dist/
33
.vite/
4-
*.log
4+
*.log
5+
temp/

src/index.html

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
<!DOCTYPE html>
2-
<html>
3-
<head>
4-
<meta charset="UTF-8" />
5-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6-
<title>Xterm Test</title>
7-
<link rel="stylesheet" href="./style.css" />
8-
</head>
9-
<body>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>XTerm Test</title>
7+
<link rel="stylesheet" href="style.css">
8+
</head>
9+
<body>
1010
<div id="terminal"></div>
11-
<script type="module" src="./script.js"></script>
12-
</body>
11+
<script type="module" src="script.js"></script>
12+
</body>
1313
</html>

src/script.js

Lines changed: 158 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,6 @@ import { FitAddon } from '@xterm/addon-fit';
44
import { WebLinksAddon } from 'xterm-addon-web-links';
55
import '@xterm/xterm/css/xterm.css';
66

7-
// Custom beep function using Web Audio API
8-
function squareBeep(freq = 440, duration = 0.1) {
9-
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
10-
const oscillator = audioCtx.createOscillator();
11-
const gainNode = audioCtx.createGain();
12-
13-
oscillator.type = 'square';
14-
oscillator.frequency.value = freq; // in Hz
15-
16-
oscillator.connect(gainNode);
17-
gainNode.connect(audioCtx.destination);
18-
19-
oscillator.start();
20-
oscillator.stop(audioCtx.currentTime + duration);
21-
}
22-
237
// ANSI color codes for terminal formatting
248
const colors = {
259
reset: '\x1B[0m',
@@ -45,8 +29,6 @@ window.addEventListener('resize', () => {
4529
term.open(document.getElementById('terminal'));
4630
term.loadAddon(new WebLinksAddon());
4731

48-
49-
5032
let currentInput = '';
5133
let currentLine = '';
5234
let cursorPosition = 0;
@@ -56,6 +38,52 @@ let cursorPosition = 0;
5638
// --- Virtual File System ---
5739
let vfs = {};
5840
const VFS_KEY = 'miaoshell-vfs';
41+
let cwd_path = '/'; // Current working directory
42+
43+
//convert cwd to a return vfs object
44+
function cwd() {
45+
if (cwd_path === '/') {
46+
return vfs;
47+
}
48+
const parts = cwd_path.split('/').filter(Boolean);
49+
let current = vfs;
50+
for (const part of parts) {
51+
if (current[part] && typeof current[part] === 'object') {
52+
current = current[part];
53+
} else {
54+
return {}; // Return empty object if path doesn't exist
55+
}
56+
}
57+
return current;
58+
}
59+
60+
//convert vfs object to cwd path
61+
function vfsToCwd(vfsObj) {
62+
if (vfsObj === vfs) {
63+
return '/';
64+
}
65+
const parts = [];
66+
function findPath(obj, currentPath) {
67+
for (const key in obj) {
68+
if (obj.hasOwnProperty(key)) {
69+
const newPath = currentPath ? `${currentPath}/${key}` : key;
70+
if (obj[key] === vfsObj) {
71+
parts.push(newPath);
72+
return true; // Found the path
73+
}
74+
if (typeof obj[key] === 'object') {
75+
if (findPath(obj[key], newPath)) {
76+
return true; // Found in subdirectory
77+
}
78+
}
79+
}
80+
}
81+
return false; // Not found in this branch
82+
}
83+
findPath(vfs, '');
84+
return parts.length > 0 ? parts[0] : '/'; // Return first found path or root
85+
}
86+
5987

6088
function loadVFS() {
6189
try {
@@ -74,20 +102,31 @@ function saveVFS() {
74102
// Load VFS on startup
75103
loadVFS();
76104

105+
function getSizeInKB(variable) {
106+
const json = JSON.stringify(variable);
107+
const bytes = new TextEncoder().encode(json).length;
108+
const kb = bytes / 1024;
109+
return kb;
110+
}
111+
77112
const commands = {
78113
help: () => {
79114
term.writeln('\r\nAvailable commands:');
80115
term.writeln(' help - Show this help message');
81116
term.writeln(' clear - Clear the terminal');
82117
term.writeln(' echo - Echo back your message');
83118
term.writeln(' date - Show current date and time');
84-
term.writeln(' beep - Make a beep sound');
85-
term.writeln(' ls - List files in the virtual file system');
119+
term.writeln(' cd - Change directory: cd dirname or cd .. or cd (for root)');
120+
term.writeln(' ls - List files in the current directory');
86121
term.writeln(' cat - Show file contents: cat filename');
87122
term.writeln(' write - Write to a file: write filename content');
123+
term.writeln(' mkdir - Create directory: mkdir dirname');
124+
term.writeln(' rmdir - Remove directory: rmdir dirname');
88125
term.writeln(' rm - Remove a file: rm filename');
89126
term.writeln(' save - Save VFS to browser storage');
90127
term.writeln(' load - Load VFS from browser storage');
128+
term.writeln(' import - Import VFS from JSON file: import filename.json');
129+
term.writeln(' storage - Show VFS storage usage');
91130
term.writeln(' about - About this terminal');
92131
},
93132
clear: () => {
@@ -101,23 +140,51 @@ const commands = {
101140
date: () => {
102141
term.writeln('\r\n' + new Date().toString());
103142
},
104-
beep: () => {
105-
squareBeep();
106-
term.writeln('\r\n🔊 Beep!');
143+
cd: (args) => {
144+
if (args.length === 0) {
145+
cwd_path = '/';
146+
term.writeln(`\r\nChanged directory to: ${cwd_path}`);
147+
return;
148+
}
149+
const dir = args[0];
150+
if (dir === '..') {
151+
if (cwd_path !== '/') {
152+
const parts = cwd_path.split('/').filter(Boolean);
153+
parts.pop(); // Go up one directory
154+
cwd_path = '/' + parts.join('/');
155+
if (cwd_path === '/') cwd_path = '/'; // Ensure root path
156+
}
157+
} else {
158+
const currentDir = cwd();
159+
if (currentDir[dir] && typeof currentDir[dir] === 'object') {
160+
cwd_path = cwd_path === '/' ? `/${dir}` : `${cwd_path}/${dir}`;
161+
} else {
162+
term.writeln(`\r\nNo such directory: ${dir}`);
163+
return;
164+
}
165+
}
166+
term.writeln(`\r\nChanged directory to: ${cwd_path}`);
107167
},
108168
ls: () => {
109-
const files = Object.keys(vfs);
169+
const currentDir = cwd();
170+
const files = Object.keys(currentDir);
110171
if (files.length === 0) term.writeln('\r\n(no files)');
111-
else term.writeln('\r\n' + files.join(' '));
172+
else {
173+
const displayFiles = files.map(file => {
174+
return typeof currentDir[file] === 'object' ? file + '/' : file;
175+
});
176+
term.writeln('\r\n' + displayFiles.join(' '));
177+
}
112178
},
113179
cat: (args) => {
114180
if (!args[0]) {
115181
term.writeln('\r\nUsage: cat filename');
116182
return;
117183
}
118184
const file = args[0];
119-
if (vfs[file] !== undefined) {
120-
term.writeln('\r\n' + vfs[file]);
185+
const currentDir = cwd();
186+
if (currentDir[file] !== undefined && typeof currentDir[file] !== 'object') {
187+
term.writeln('\r\n' + currentDir[file]);
121188
} else {
122189
term.writeln(`\r\nFile not found: ${file}`);
123190
}
@@ -129,17 +196,47 @@ const commands = {
129196
}
130197
const file = args[0];
131198
const content = args.slice(1).join(' ');
132-
vfs[file] = content;
199+
const currentDir = cwd();
200+
currentDir[file] = content;
133201
term.writeln(`\r\nWrote to ${file}`);
134202
},
203+
mkdir: (args) => {
204+
if (!args[0]) {
205+
term.writeln('\r\nUsage: mkdir directory_name');
206+
return;
207+
}
208+
const dir = args[0];
209+
const currentDir = cwd();
210+
if (currentDir[dir] !== undefined) {
211+
term.writeln(`\r\nDirectory already exists: ${dir}`);
212+
return;
213+
}
214+
currentDir[dir] = {}; // Create an empty directory
215+
term.writeln(`\r\nCreated directory: ${dir}`);
216+
},
217+
rmdir: (args) => {
218+
if (!args[0]) {
219+
term.writeln('\r\nUsage: rmdir directory_name');
220+
return;
221+
}
222+
const dir = args[0];
223+
const currentDir = cwd();
224+
if (currentDir[dir] !== undefined && typeof currentDir[dir] === 'object') {
225+
delete currentDir[dir];
226+
term.writeln(`\r\nRemoved directory: ${dir}`);
227+
} else {
228+
term.writeln(`\r\nDirectory not found: ${dir}`);
229+
}
230+
},
135231
rm: (args) => {
136232
if (!args[0]) {
137233
term.writeln('\r\nUsage: rm filename');
138234
return;
139235
}
140236
const file = args[0];
141-
if (vfs[file] !== undefined) {
142-
delete vfs[file];
237+
const currentDir = cwd();
238+
if (currentDir[file] !== undefined && typeof currentDir[file] !== 'object') {
239+
delete currentDir[file];
143240
term.writeln(`\r\nDeleted ${file}`);
144241
} else {
145242
term.writeln(`\r\nFile not found: ${file}`);
@@ -153,6 +250,34 @@ const commands = {
153250
loadVFS();
154251
term.writeln('\r\nVFS loaded from browser storage.');
155252
},
253+
import: (args) => {
254+
if (!args[0]) {
255+
term.writeln('\r\nUsage: import filename.json');
256+
return;
257+
}
258+
const filename = args[0];
259+
fetch(filename)
260+
.then(response => {
261+
if (!response.ok) {
262+
throw new Error(`HTTP ${response.status}`);
263+
}
264+
return response.json();
265+
})
266+
.then(data => {
267+
Object.assign(vfs, data);
268+
saveVFS();
269+
term.writeln(`\r\nImported ${Object.keys(data).length} files from ${filename}`);
270+
})
271+
.catch(error => {
272+
term.writeln(`\r\nError importing ${filename}: ${error.message}`);
273+
});
274+
},
275+
storage: () => {
276+
term.writeln('\r\nVirtual File System Storage:');
277+
term.writeln(`${getSizeInKB(vfs).toFixed(2)} KB / 512 KB`);
278+
term.writeln(`(${Object.keys(vfs).length} files)`);
279+
term.writeln(`(${(getSizeInKB(vfs) / 512 * 100).toFixed(2)}% used)`);
280+
},
156281
about: () => {
157282
term.writeln('\r\n' + colors.bold + 'MiaoShell' + colors.reset + ' - A Useless Terminal');
158283
term.writeln('Version: 1.0.0');
@@ -178,7 +303,8 @@ function executeCommand(input) {
178303
}
179304

180305
function prompt() {
181-
term.write(`\r\n${colors.green}user@miaoshell${colors.reset}:${colors.blue}~${colors.reset}$ `);
306+
const displayPath = cwd_path === '/' ? '~' : cwd_path;
307+
term.write(`\r\n${colors.green}user@miaoshell${colors.reset}:${colors.blue}${displayPath}${colors.reset}$ `);
182308
}
183309

184310
// Handle user input
@@ -205,7 +331,7 @@ term.onData(data => {
205331
async function init() {
206332
// Initialize terminal
207333
//print /assets/miao.txt
208-
await fetch('/assets/miao.txt')
334+
await fetch('./assets/miao.txt')
209335
.then(response => {
210336
if (response.status === 200) {
211337
return response.text();

0 commit comments

Comments
 (0)