Konsole updates
This commit is contained in:
		@@ -1,216 +1,43 @@
 | 
			
		||||
<script setup>
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import '../misc/konsole';
 | 
			
		||||
import {onMounted} from 'vue';
 | 
			
		||||
 | 
			
		||||
const hostname = 'virtual';
 | 
			
		||||
let history = [];
 | 
			
		||||
let historyIndex = 0;
 | 
			
		||||
let prompt;
 | 
			
		||||
let input;
 | 
			
		||||
let output;
 | 
			
		||||
 | 
			
		||||
function focus() {
 | 
			
		||||
	input.focus();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function disable() {
 | 
			
		||||
	input.disabled = true;
 | 
			
		||||
	prompt.style.visibility = 'hidden';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function enable() {
 | 
			
		||||
	input.disabled = false;
 | 
			
		||||
	input.focus();
 | 
			
		||||
	prompt.style.visibility = 'visible';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function banner() {
 | 
			
		||||
	stdOut(`Konsole 0.2.0 LTS virtual tty1<br><br>${hostname} login: root<br>password:<br><br>`);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function process(command) {
 | 
			
		||||
	(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
 | 
			
		||||
		const parts = c.split(' ').filter(c => !!c);
 | 
			
		||||
		if(window.cli[parts[0]] == undefined || window.cli[parts[0]].run == undefined) {
 | 
			
		||||
			stdErr(`${parts[0]}: command not found`);
 | 
			
		||||
		} else {
 | 
			
		||||
			try {
 | 
			
		||||
				const out = window.cli[parts[0]].run(parts.slice(1));
 | 
			
		||||
				if(!!out) stdOut(out);
 | 
			
		||||
			} catch(err) {
 | 
			
		||||
				console.error(err)
 | 
			
		||||
				stdErr(`${parts[0]}: exited with a non-zero status`);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stdErr(text) {
 | 
			
		||||
	const p = document.createElement('p');
 | 
			
		||||
	p.classList.add('console-output-line');
 | 
			
		||||
	p.classList.add('console-output-error');
 | 
			
		||||
	p.innerText = text;
 | 
			
		||||
	output.appendChild(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stdOut(text, html=true) {
 | 
			
		||||
	const p = document.createElement('p');
 | 
			
		||||
	p.classList.add('console-output-line');
 | 
			
		||||
	p[html ? 'innerHTML' : 'innerText'] = text;
 | 
			
		||||
	output.appendChild(p);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function stdIn(event) {
 | 
			
		||||
	if(event.key == "Enter") {
 | 
			
		||||
		disable();
 | 
			
		||||
		let inputValue = input.value;
 | 
			
		||||
		input.value = '';
 | 
			
		||||
		stdOut(`root@localhost:~ # ${inputValue}`, false);
 | 
			
		||||
		if(!!inputValue) {
 | 
			
		||||
			history.push(inputValue);
 | 
			
		||||
			historyIndex = history.length;
 | 
			
		||||
			process(inputValue)
 | 
			
		||||
		}
 | 
			
		||||
		enable();
 | 
			
		||||
	} else if(event.key == 'Up' || event.key == 'ArrowUp') {
 | 
			
		||||
		if(historyIndex > 0) historyIndex--;
 | 
			
		||||
		input.value = historyIndex == history.length ? '' : history[historyIndex];
 | 
			
		||||
		setTimeout(() => {
 | 
			
		||||
			const end = input.value.length;
 | 
			
		||||
			input.setSelectionRange(end, end);
 | 
			
		||||
			input.focus();
 | 
			
		||||
		}, 1)
 | 
			
		||||
	} else if(event.key == 'Down' || event.key == 'ArrowDown') {
 | 
			
		||||
		if(historyIndex < history.length) historyIndex++;
 | 
			
		||||
		input.value = historyIndex == history.length ? '' : history[historyIndex];
 | 
			
		||||
		setTimeout(() => {
 | 
			
		||||
			const end = input.value.length;
 | 
			
		||||
			input.setSelectionRange(end, end);
 | 
			
		||||
			input.focus();
 | 
			
		||||
		}, 1)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
onMounted(() => {
 | 
			
		||||
	prompt = document.getElementsByClassName('console-input-prompt')[0];
 | 
			
		||||
	input = document.getElementsByClassName('console-input-field')[0];
 | 
			
		||||
	output = document.getElementsByClassName('console-output')[0];
 | 
			
		||||
	banner();
 | 
			
		||||
	(<any>window).cli?.build('#konsole');
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
window.cli = {};
 | 
			
		||||
window.cli['clear'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		output.innerHTML = '';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['echo'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Output text to console';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return args.join(' ');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['exit'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'End session';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		process('clear');
 | 
			
		||||
		history = [];
 | 
			
		||||
		historyIndex = 0;
 | 
			
		||||
		banner();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['help'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display all commands';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return Object.keys(window.cli).map(command => `${command} - ${window.cli[command].help()}`).join('<br>') + '<br><br>';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['hostname'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get computer hostname';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return 'localhost'
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['man'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Command manual';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli[args[0]].help();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli['whoami'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get username';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return 'root'
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<style>
 | 
			
		||||
.console {
 | 
			
		||||
	display: flex;
 | 
			
		||||
	flex-direction: column;
 | 
			
		||||
.cli-container {
 | 
			
		||||
	padding: 1rem;
 | 
			
		||||
	background: #333;
 | 
			
		||||
	font-family: monospace !important;
 | 
			
		||||
	overflow-y: auto;
 | 
			
		||||
	min-height: 150px;
 | 
			
		||||
	max-height: 300px;
 | 
			
		||||
 | 
			
		||||
	.console-output {
 | 
			
		||||
	.cli-stdout {
 | 
			
		||||
		flex-grow: 1;
 | 
			
		||||
		color: #0f0;
 | 
			
		||||
 | 
			
		||||
		.cli-stdout-line {
 | 
			
		||||
			margin: 0;
 | 
			
		||||
			padding: 0;
 | 
			
		||||
			min-height: 1.25rem;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.console-output-line {
 | 
			
		||||
		margin: 0;
 | 
			
		||||
		padding: 0;
 | 
			
		||||
		min-height: 1.25rem;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.console-input {
 | 
			
		||||
	.cli-stdin {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		margin: 0;
 | 
			
		||||
 | 
			
		||||
		.console-input-prompt {
 | 
			
		||||
		.cli-stdin-prompt {
 | 
			
		||||
			padding-right: 0.55em;
 | 
			
		||||
			text-wrap: nowrap;
 | 
			
		||||
			color: #0f0;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.console-input-field {
 | 
			
		||||
		.cli-stdin-input {
 | 
			
		||||
			border: none;
 | 
			
		||||
			outline: none;
 | 
			
		||||
			font-size: 1rem;
 | 
			
		||||
@@ -255,12 +82,5 @@ window.cli['whoami'] = {
 | 
			
		||||
</style>
 | 
			
		||||
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="console">
 | 
			
		||||
		<div class="console-output"></div>
 | 
			
		||||
		<div class="console-input" @click=" focus()">
 | 
			
		||||
			<div class="console-input-prompt">root@{{hostname}}:~ #</div>
 | 
			
		||||
			<label for="console-input-field" class="hidden-label"><!-- Accessibility -->CLI Input</label>
 | 
			
		||||
			<input id="console-input-field" class="console-input-field" type="text" @keydown="stdIn($event)">
 | 
			
		||||
		</div>
 | 
			
		||||
	</div>
 | 
			
		||||
	<div id="konsole"></div>
 | 
			
		||||
</template>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,5 @@
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
export interface Project {
 | 
			
		||||
	icon?: string;
 | 
			
		||||
	name: string;
 | 
			
		||||
	description: string;
 | 
			
		||||
	link: string;
 | 
			
		||||
	source?: string;
 | 
			
		||||
}
 | 
			
		||||
import type {Project} from '@/models/project';
 | 
			
		||||
 | 
			
		||||
defineProps({
 | 
			
		||||
	projects: {type: Array as () => Project[], required: true}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,3 @@
 | 
			
		||||
export const environment = {
 | 
			
		||||
	postMailKey: (<any>window)?.env?.APP_POSTMAIL_KEY || import.meta.env.APP_POSTMAIL_ACCESS_TOKEN,
 | 
			
		||||
	postMailKey: (<any>window)?.env?.APP_POSTMAIL_KEY || (<any>import.meta).env.APP_POSTMAIL_ACCESS_TOKEN,
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										315
									
								
								src/misc/konsole.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								src/misc/konsole.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,315 @@
 | 
			
		||||
window.cli = {
 | 
			
		||||
	// Element references
 | 
			
		||||
	_input: null,
 | 
			
		||||
	_parent: null,
 | 
			
		||||
	_prompt: null,
 | 
			
		||||
	_output: null,
 | 
			
		||||
 | 
			
		||||
	// CLI State
 | 
			
		||||
	_history: [],
 | 
			
		||||
	_index: 0,
 | 
			
		||||
	pwd: '/',
 | 
			
		||||
	hostname: 'virtual',
 | 
			
		||||
	env: {},
 | 
			
		||||
	exec: {},
 | 
			
		||||
	filesystem: {},
 | 
			
		||||
	user: 'root',
 | 
			
		||||
	version: '0.3.0',
 | 
			
		||||
 | 
			
		||||
	build: (elementId) => {
 | 
			
		||||
		window.cli._parent = document.querySelector(elementId);
 | 
			
		||||
		if(!window.cli._parent)
 | 
			
		||||
			throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
 | 
			
		||||
 | 
			
		||||
		window.cli._parent.innerHTML = `
 | 
			
		||||
            <div class="cli-container">
 | 
			
		||||
                <div class="cli-stdout"></div>
 | 
			
		||||
                <div class="cli-stdin" onclick="window.cli._input.focus()">
 | 
			
		||||
                    <label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli.prompt()}</label>
 | 
			
		||||
                    <input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>`;
 | 
			
		||||
 | 
			
		||||
		window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
 | 
			
		||||
		window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
 | 
			
		||||
		window.cli._output = document.querySelector(elementId + ' .cli-stdout');
 | 
			
		||||
		window.cli._input.addEventListener('keyup', (e) => {
 | 
			
		||||
			if(e.key == "Enter") {
 | 
			
		||||
				window.cli.disable();
 | 
			
		||||
				window.cli.stdOut(`${window.cli.prompt()} ${window.cli._input.value}`);
 | 
			
		||||
				if(!!window.cli._input.value) {
 | 
			
		||||
					window.cli._history.push(window.cli._input.value);
 | 
			
		||||
					window.cli._index = window.cli._history.length;
 | 
			
		||||
					window.cli.stdIn(window.cli._input.value)
 | 
			
		||||
				}
 | 
			
		||||
				window.cli._input.value = '';
 | 
			
		||||
				window.cli.enable();
 | 
			
		||||
			} else if(e.key == 'Up' || e.key == 'ArrowUp') {
 | 
			
		||||
				if(window.cli._index > 0) window.cli._index--;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			} else if(e.key == 'Down' || e.key == 'ArrowDown') {
 | 
			
		||||
				if(window.cli._index < window.cli._history.length) window.cli._index++;
 | 
			
		||||
				window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
 | 
			
		||||
				setTimeout(() => {
 | 
			
		||||
					const end = window.cli._input.value.length;
 | 
			
		||||
					window.cli._input.setSelectionRange(end, end);
 | 
			
		||||
					window.cli._input.focus();
 | 
			
		||||
				}, 1)
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		setTimeout(() => window.cli.exec['banner'].run(), 1);
 | 
			
		||||
	},
 | 
			
		||||
	disable: () => {
 | 
			
		||||
		window.cli._input.disabled = true;
 | 
			
		||||
		window.cli._prompt.style.visibility = 'hidden';
 | 
			
		||||
	},
 | 
			
		||||
	enable: () => {
 | 
			
		||||
		window.cli._input.disabled = false;
 | 
			
		||||
		window.cli._input.focus();
 | 
			
		||||
		window.cli._prompt.style.visibility = 'visible';
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	path: (path=window.cli.pwd) => {
 | 
			
		||||
		let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
 | 
			
		||||
			.replaceAll('//', '/');
 | 
			
		||||
		const parts = p.split('/').filter(p => !!p);
 | 
			
		||||
		for(let i = 0; i < parts.length; i++) {
 | 
			
		||||
			if(parts[i] == '..') {
 | 
			
		||||
				i--;
 | 
			
		||||
				parts.splice(i, 2);
 | 
			
		||||
				i--;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return '/' + (parts.length ? parts.join('/') : '');
 | 
			
		||||
	},
 | 
			
		||||
	prompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
 | 
			
		||||
	fs: (path, set) => {
 | 
			
		||||
		let target = window.cli.filesystem;
 | 
			
		||||
		const parts = window.cli.path(path).split('/').filter(p => !!p);
 | 
			
		||||
		parts.forEach((p, i, arr) => {
 | 
			
		||||
			if(!target[p] && set !== undefined) {
 | 
			
		||||
				if(i + 1 != arr.length) target[p] = {};
 | 
			
		||||
				else target[p] = set == null ? undefined : set;
 | 
			
		||||
			}
 | 
			
		||||
			target = target[p];
 | 
			
		||||
		});
 | 
			
		||||
		return target;
 | 
			
		||||
	},
 | 
			
		||||
	stdErr: (text) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p.classList.add('cli-stdout-error');
 | 
			
		||||
		p.innerText = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
	stdIn:(command) => {
 | 
			
		||||
		(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
 | 
			
		||||
			const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
 | 
			
		||||
			if(!parts) return;
 | 
			
		||||
 | 
			
		||||
			const exec = window.cli.exec[parts[0]];
 | 
			
		||||
			if(!exec?.run) {
 | 
			
		||||
				window.cli.stdErr(`${parts[0]}: command not found`);
 | 
			
		||||
			} else {
 | 
			
		||||
				try {
 | 
			
		||||
					const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
 | 
			
		||||
					const out = exec.run(args);
 | 
			
		||||
					if(!!out) window.cli.stdOut(out);
 | 
			
		||||
				} catch(err) {
 | 
			
		||||
					console.error(err);
 | 
			
		||||
					window.cli.stdErr(err.message || `${parts[0]}: exited with a non-zero status`);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
	},
 | 
			
		||||
	stdOut: (text, html=false) => {
 | 
			
		||||
		const p = document.createElement('p');
 | 
			
		||||
		p.classList.add('cli-stdout-line');
 | 
			
		||||
		p[html ? 'innerHTML' : 'innerText'] = text;
 | 
			
		||||
		window.cli._output.appendChild(p);
 | 
			
		||||
	},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
window.cli.exec['banner'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display login banner';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		window.cli.stdOut(`Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['cd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const path = window.cli.fs(args[0]);
 | 
			
		||||
		if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
 | 
			
		||||
 | 
			
		||||
		window.cli.pwd = window.cli.path(args[0]);
 | 
			
		||||
		window.cli._prompt.innerText = window.cli.prompt();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['clear'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Clear console output';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		window.cli._output.innerHTML = '';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['echo'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Output text to console';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return args.join(' ');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['exit'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'End session';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		window.cli.stdIn('clear');
 | 
			
		||||
		window.cli._history = [];
 | 
			
		||||
		window.cli._index = 0;
 | 
			
		||||
		window.cli.stdIn('banner');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['help'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display all commands';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return `Konsole v${window.cli.version} - A prototype bash emulator written by Zakary Timson\n\n` +
 | 
			
		||||
			Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n') + '\n\n';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['hostname'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get computer hostname';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.hostname;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['ls'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Display directory contents';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const target = window.cli.fs(args[0]);
 | 
			
		||||
		if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
 | 
			
		||||
		return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['man'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'View command\'s manual';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.exec[args[0]].help();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['mkdir'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Create new directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('mkdir: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], {});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['pwd'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get present working directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.pwd;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['rm'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Delete file or directory';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		if(!args[0]) throw new Error('rm: missing operand');
 | 
			
		||||
		window.cli.fs(args[0], null);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['shower-thought'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Random shower thought';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		const motd = [
 | 
			
		||||
			'Why do kamikaze pilots wear helmets?',
 | 
			
		||||
			'How are giraffes real, but unicorns made up',
 | 
			
		||||
			'When you are a kid you don\'t realize you are also watching your parents grow up',
 | 
			
		||||
			'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
 | 
			
		||||
			'The number of people older than you never goes up',
 | 
			
		||||
			'When you brush your teeth you are cleaning your skeleton',
 | 
			
		||||
			'Pregnancy is like a group project where one person get\'s stuck with all the work',
 | 
			
		||||
			'If the universe wasn\'t infinite it would be even scarier',
 | 
			
		||||
			'Either we are alone in the universe or we are not. both are terrifying',
 | 
			
		||||
			'The object of golf is to play the least amount of golf.'
 | 
			
		||||
		];
 | 
			
		||||
		return motd[~~(Math.random() * motd.length)];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
window.cli.exec['whoami'] = {
 | 
			
		||||
	autocomplete: () => {
 | 
			
		||||
		return [];
 | 
			
		||||
	},
 | 
			
		||||
	help: () => {
 | 
			
		||||
		return 'Get current user account';
 | 
			
		||||
	},
 | 
			
		||||
	run: args => {
 | 
			
		||||
		return window.cli.user;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/models/project.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/models/project.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
export interface Project {
 | 
			
		||||
	icon?: string;
 | 
			
		||||
	name: string;
 | 
			
		||||
	description: string;
 | 
			
		||||
	link: string;
 | 
			
		||||
	source?: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
 | 
			
		||||
import Home from '@/views/Home.vue'
 | 
			
		||||
 | 
			
		||||
const router = createRouter({
 | 
			
		||||
  history: createWebHistory(import.meta.env.BASE_URL),
 | 
			
		||||
  history: createWebHistory((<any>import.meta).env.BASE_URL),
 | 
			
		||||
  routes: [
 | 
			
		||||
	  {path: '/', name: 'home', component: Home}
 | 
			
		||||
  ]
 | 
			
		||||
 
 | 
			
		||||
@@ -4,15 +4,16 @@ import Contact from '@/components/contact.vue';
 | 
			
		||||
import Konsole from '@/components/konsole.vue';
 | 
			
		||||
import Projects from '@/components/projects.vue';
 | 
			
		||||
import Refrences from '@/components/refrences.vue';
 | 
			
		||||
import type {Project as P} from '@/models/project';
 | 
			
		||||
import {ref} from 'vue';
 | 
			
		||||
 | 
			
		||||
const services: Projects[] = [
 | 
			
		||||
const services: P[] = [
 | 
			
		||||
	{name: 'Formula Manager', icon: 'https://git.zakscode.com/avatars/7ec6bfd66b2bf9bad5c43c75a33f9cb3f6609b05c33a31f5d1e524a567cd09c1?size=280', link: 'https://screenprintingsuppliescanada.com/formulation-manager', description: 'A web & computer application used by FH&Sons to record chemical formulas & distribute them to clients'},
 | 
			
		||||
	{name: 'Map Alliance', icon: 'https://maps.zakscode.com/assets/images/logo.png', link: 'https://maps.zakscode.com', description: 'An online GIS tool which enables users to view, edit & share various "marked-up" maps'},
 | 
			
		||||
	{name: 'Phone Reminders', icon: 'https://phone-reminders.com/phone-reminders.png', link: 'https://phone-reminders.com', description: 'Automatically call & send SMS reminders to clients for events using Google Calendar'},
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const openSource: Projects[] = [
 | 
			
		||||
const openSource: P[] = [
 | 
			
		||||
	{name: 'ETF Demo', icon: 'https://git.zakscode.com/repo-avatars/0709db0c51d295d2d29b709865bd95f26e351f72a5c993ca63cd9ec4b4a07f43', link: 'https://etf.zakscode.com', source: 'https://git.zakscode.com/ztimson/etf-demo', description: 'Compare CSV files containing "Electronically Traded Funds" data (Check source for CSV files)'},
 | 
			
		||||
	{name: 'Legio 30', icon: 'https://git.zakscode.com/repo-avatars/f66e3d6f5ff4646b45e859f6bf00c0e0de0621d8a45a47481d53d67b67700f2a', link: 'https://legio-30.org', source: 'https://git.zakscode.com/ztimson/legio-30', description: 'Website for a non-profit Roman re-enactment group from Southern Ontario'},
 | 
			
		||||
	{name: 'Pelican Landing', icon: 'https://git.zakscode.com/ztimson/pelican-landing/raw/branch/develop/src/assets/logo.png', link: 'https://pelican-landing.zakscode.com', source: 'https://git.zakscode.com/ztimson/pelican-landing', description: 'Business website for a hunting & fishing lodge on the Lage of Woods in Northern Ontario '},
 | 
			
		||||
@@ -36,7 +37,7 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
 | 
			
		||||
<template>
 | 
			
		||||
	<div class="p-3">
 | 
			
		||||
		<!-- Terminal -->
 | 
			
		||||
		<konsole class="mb-5" style="max-height: 300px" />
 | 
			
		||||
		<konsole class="mb-5" />
 | 
			
		||||
 | 
			
		||||
		<!-- Steps -->
 | 
			
		||||
		<div class="mb-5 pt-5">
 | 
			
		||||
@@ -58,17 +59,8 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
 | 
			
		||||
			<h3 class="mb-0">About</h3>
 | 
			
		||||
			<hr class="mb-4">
 | 
			
		||||
			<img alt="Childhood" src="/childhood.jpg" height="150px" width="auto" class="float-end m-3 m-md-0 ml-md-3" style="border-radius: 50%;">
 | 
			
		||||
			<p>
 | 
			
		||||
				Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for
 | 
			
		||||
				computers since he was born & taught him self to code at the age of 13. Since then, he has gone to school
 | 
			
		||||
				for computer science & has worked for both small businesses and large corporations as a developer and team lead.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>
 | 
			
		||||
				Zak specializes in full-stack web development & server infrastructure, and primarily works on large enterprise
 | 
			
		||||
				grade "Software as a service" (SaaS) products. As a software architect & team lead he is able to work with
 | 
			
		||||
				business's to create a road map of their needs, build enterprise grade software solutions that meet those
 | 
			
		||||
				needs & work with clients to host & deliver automatic updates at scale using continuous integration.
 | 
			
		||||
			</p>
 | 
			
		||||
			<p>Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for computers since he was born & taught him self to code at the age of 13. Since then, he has gone to school for computer science & has worked for both small businesses and large corporations as a developer and team lead.</p>
 | 
			
		||||
			<p>Zak specializes in full-stack web development & server infrastructure, and primarily works on large enterprise grade "Software as a service" (SaaS) products. As a software architect & team lead he is able to work with business's to create a road map of their needs, build enterprise grade software solutions that meet those needs & work with clients to host & deliver automatic updates at scale using continuous integration.</p>
 | 
			
		||||
 | 
			
		||||
			<div class="mt-4">
 | 
			
		||||
				<h4 class="mb-3 text-muted">CSV & References</h4>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user