Compare commits
33 Commits
88761a6a66
...
1.0.0
Author | SHA1 | Date | |
---|---|---|---|
794c59e375 | |||
9b6d50fa42 | |||
f7211e3f87 | |||
8d84fc8274 | |||
94c2f336f6 | |||
accd08d568 | |||
521ba1902d | |||
36facdebe3 | |||
9439683b01 | |||
2ea1c345c7 | |||
2aebc4c54b | |||
81cf9c09f9 | |||
bdbaf799a8 | |||
e055e306df | |||
1c25802a2e | |||
20be9d170a | |||
120bd3ec50 | |||
8be3de8123 | |||
c0b348c5c6 | |||
b71b2092c3 | |||
0046dc1cb4 | |||
42d26503bc | |||
ec5c66a9c2 | |||
1217e86ac9 | |||
d07c1a4521 | |||
cc9c9ea1b8 | |||
f1d6c0a25c | |||
43f96b1ea9 | |||
a4d1f6b825 | |||
8038e79ad2 | |||
d7c257cb39 | |||
88f2a716b9 | |||
ae279b478b |
2
.env
2
.env
@ -1 +1 @@
|
||||
APP_POSTMAIL_ACCESS_TOKEN=s7uhce84sx6fayy5xlq0nrtx
|
||||
APP_POSTMAIL_ACCESS_TOKEN=
|
||||
|
17
.github/workflows/build.yaml
vendored
17
.github/workflows/build.yaml
vendored
@ -26,6 +26,23 @@ jobs:
|
||||
path: dist
|
||||
retention-days: 7
|
||||
|
||||
tag:
|
||||
name: Tag Version
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
container: node
|
||||
steps:
|
||||
- name: Clone Repository
|
||||
uses: ztimson/actions/clone@develop
|
||||
|
||||
- name: Get Version Number
|
||||
run: echo "VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')" >> $GITHUB_ENV
|
||||
|
||||
- name: Tag Version
|
||||
uses: ztimson/actions/tag@develop
|
||||
with:
|
||||
tag: ${{env.VERSION}}
|
||||
|
||||
publish:
|
||||
name: Build & Push Dockerfile
|
||||
needs: build
|
||||
|
16
Dockerfile
16
Dockerfile
@ -16,16 +16,6 @@ COPY . .
|
||||
RUN if [ ! -d "dist" ]; then npm install && npm run build; fi
|
||||
|
||||
# Use Nginx to serve
|
||||
FROM nginx:1.23-alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy aditional files
|
||||
COPY package.json /usr/share/nginx/html
|
||||
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
|
||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
# Setup environment varible script
|
||||
COPY docker/setup-environment.sh /docker-entrypoint.d/setup-environment.sh
|
||||
RUN chmod +x /docker-entrypoint.d/setup-environment.sh
|
||||
|
||||
EXPOSE 80
|
||||
FROM git.zakscode.com/ztimson/momentum:latest
|
||||
RUN rm -rf /app/server/public/assets /app/server/public/index.html
|
||||
COPY --from=build /app/dist /app/server/public
|
||||
|
11
LICENSE
Normal file
11
LICENSE
Normal file
@ -0,0 +1,11 @@
|
||||
Copyright (c) 2023 Zakary Timson
|
||||
|
||||
All Rights Reserved.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
103
README.md
103
README.md
@ -1,18 +1,93 @@
|
||||
# ZaksCode
|
||||
Business website for ZaksCode. Provides some background information, contact inforation & a portfolio of projects.
|
||||
[View here](https://zakscode.com).
|
||||
<!-- Header -->
|
||||
<div id="top" align="center">
|
||||
<br />
|
||||
|
||||
Built using [Angular 13](https://angular.io).
|
||||
<!-- Logo -->
|
||||
<img src="./public/logo.png" alt="Logo" width="200" height="200">
|
||||
|
||||
<!-- Title -->
|
||||
### ZaksCode
|
||||
|
||||
<!-- Description -->
|
||||
Source Code for ZaksCode Website
|
||||
|
||||
<!-- Repo badges -->
|
||||
[](https://git.zakscode.com/ztimson/zakscode/tags)
|
||||
[](https://git.zakscode.com/ztimson/zakscode/pulls)
|
||||
[](https://git.zakscode.com/ztimson/zakscode/issues)
|
||||
|
||||
<!-- Links -->
|
||||
|
||||
---
|
||||
<div>
|
||||
<a href="https://git.zakscode.com/ztimson/zakscode/releases" target="_blank">Release Notes</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/zakscode/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/zakscode/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
[[_TOC_]]
|
||||
- [ZaksCode](#top)
|
||||
- [About](#about)
|
||||
- [Demo](#demo)
|
||||
- [Built With](#built-with)
|
||||
- [Setup](#setup)
|
||||
- [Production](#production)
|
||||
- [Development](#development)
|
||||
- [License](#license)
|
||||
|
||||
## Cheatsheet
|
||||
```bash
|
||||
# Start Angular development server
|
||||
npm run start
|
||||
# Build production site
|
||||
npm run build
|
||||
# Build docker image
|
||||
docker build -t gitlab.zakscode.com:5050/zakcode/zakscode:latest .
|
||||
```
|
||||
## About
|
||||
|
||||
This the source code for `zakscode.com`.
|
||||
|
||||
ZaksCode is the personal website of Zakary Timson & is used as his business page & portfolio.
|
||||
### Demo
|
||||
|
||||
Website: https://zakscode.com
|
||||
|
||||
### Built With
|
||||
[](https://vuejs.org/)
|
||||
[](https://getbootstrap.com)
|
||||
[](https://docker.com/)
|
||||
[](https://typescriptlang.org/)
|
||||
|
||||
## Setup
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="production" style="display: inline">
|
||||
Production
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Docker](https://docs.docker.com/install/)
|
||||
|
||||
#### Instructions
|
||||
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/zakscode:latest`
|
||||
2. Open [http://localhost](http://localhost)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="development" style="display: inline">
|
||||
Development
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
|
||||
#### Instructions
|
||||
1. Install the dependencies: `npm install`
|
||||
2. Start the dev server: `npm run start`
|
||||
3. Open [http://localhost:4200](http://localhost:5173)
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
Copyright © 2023 Zakary Timson | All Rights Reserved
|
||||
|
||||
See the [license](./LICENSE) for more information.
|
||||
|
@ -25,7 +25,7 @@ http {
|
||||
autoindex off;
|
||||
|
||||
location / {
|
||||
try_files $uri$args $uri$args/ /index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
18
index.html
18
index.html
@ -7,16 +7,14 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<meta property=”og:type” content=”website” />
|
||||
<meta property=”og:title” content=”Zakary Timson” />
|
||||
<!-- <meta name=”twitter:title” content=”Zakary Timson” />-->
|
||||
<meta name="description" content="Devops & Software Engineer" />
|
||||
<meta property=”og:description” content=”Devops & Software Engineer” />
|
||||
<!-- <meta name=”twitter:description” content=”Devops & Software Engineer” />-->
|
||||
<meta property=”og:image” content=”https://zakscode.com/cloud.gif” />
|
||||
<!-- <meta name=”twitter:image” content=”https://zakscode.com/cloud.gif” />-->
|
||||
<meta property=”og:url” content=”https://zakscode.com” />
|
||||
<meta property=”og:site_name” content=”ZaksCode” />
|
||||
<meta property="og:title" content="Zakary Timson">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://zakscode.com">
|
||||
<meta property="og:image" content="https://zakscode.com/cloud.png">
|
||||
<meta property="og:description" content="DevOps & Software Engineer">
|
||||
<meta name="description" content="DevOps & Software Engineer">
|
||||
<meta name="twitter:card" content="DevOps & Software Engineer">
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="default-src 'self' data: blob: git.zakscode.com; style-src 'self' 'unsafe-inline'; object-src 'none';">-->
|
||||
|
||||
<script id="environment"></script>
|
||||
</head>
|
||||
|
1523
package-lock.json
generated
1523
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,16 +1,17 @@
|
||||
{
|
||||
"name": "zakscode",
|
||||
"version": "2.0.0",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"start": "vite",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build --force"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ztimson/momentum": "^0.50.3",
|
||||
"vue": "^3.3.11",
|
||||
"vue-router": "^4.2.5"
|
||||
},
|
||||
@ -23,7 +24,7 @@
|
||||
"npm-run-all2": "^6.1.1",
|
||||
"sass": "^1.69.7",
|
||||
"typescript": "~5.3.0",
|
||||
"vite": "^5.0.10",
|
||||
"vite": "5.4.6",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
}
|
||||
|
@ -24,5 +24,5 @@ import Profile from '@/components/profile.vue';
|
||||
</div>
|
||||
|
||||
<!-- Spacer -->
|
||||
<div class="d-none d-sm-block w-100" style="height: 40px"></div>
|
||||
<div class="d-none d-lg-block w-100" style="height: 40px"></div>
|
||||
</template>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import Icon from '@/components/icon.vue';
|
||||
import {environment} from '@/environments/environment';
|
||||
import {momentum} from '@/services/momentum.service';
|
||||
import {ref} from 'vue';
|
||||
|
||||
const disable = ref(false);
|
||||
@ -31,23 +31,10 @@ function validateEmail(email: string) {
|
||||
}
|
||||
|
||||
function send(name: string, email: string, subject: string, message: string) {
|
||||
function formEncode(data: any): string {
|
||||
return Object.entries(data).map(([key, value]) =>
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(<any>value)
|
||||
).join('&');
|
||||
}
|
||||
|
||||
return fetch('https://postmail.invotes.com/send', {
|
||||
method: 'post',
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: formEncode({
|
||||
access_token: environment.postMailKey,
|
||||
return momentum.email.send({
|
||||
to: ['zaktimson@gmail.com', email],
|
||||
subject: `ZaksCode: ${subject}`,
|
||||
text: `App: ZaksCode\nFrom: ${name} <${email}>\nSubject: ${subject}\n\nMessage:\n${message}`
|
||||
})
|
||||
}).then(async resp => {
|
||||
if(!resp.ok) throw new Error(resp.statusText);
|
||||
return await resp.text();
|
||||
body: `From: ${name} <${email}><br><br>${message}`
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,266 +1,33 @@
|
||||
<script setup>
|
||||
import {onMounted} from 'vue';
|
||||
<script setup lang="ts">
|
||||
import '../modules/konsole';
|
||||
import {onMounted, ref} from 'vue';
|
||||
|
||||
const hostname = 'virtual';
|
||||
let history = [];
|
||||
let historyIndex = 0;
|
||||
let prompt;
|
||||
let input;
|
||||
let output;
|
||||
const animate = ref(true);
|
||||
|
||||
function focus() {
|
||||
input.focus();
|
||||
function sleep(time: number) {
|
||||
return new Promise(res => setTimeout(res, time));
|
||||
}
|
||||
|
||||
function disable() {
|
||||
input.disabled = true;
|
||||
prompt.style.visibility = 'hidden';
|
||||
async function showerThought(s=10000) {
|
||||
if(!animate.value) return;
|
||||
await (<any>window).cli.type('shower-thought');
|
||||
if(!animate.value) return;
|
||||
await sleep(s);
|
||||
if(!animate.value) return;
|
||||
await(<any>window).cli.type('clear');
|
||||
}
|
||||
|
||||
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`);
|
||||
onMounted(async () => {
|
||||
(<any>window).cli.build('#konsole');
|
||||
while(animate) {
|
||||
if(!animate.value) break;
|
||||
await sleep(3000);
|
||||
if(!animate.value) break;
|
||||
await showerThought();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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;
|
||||
padding: 1rem;
|
||||
background: #333;
|
||||
font-family: monospace !important;
|
||||
overflow-y: auto;
|
||||
|
||||
.console-output {
|
||||
flex-grow: 1;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.console-output-line {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 1.25rem;
|
||||
}
|
||||
|
||||
.console-input {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
|
||||
.console-input-prompt {
|
||||
padding-right: 0.55em;
|
||||
text-wrap: nowrap;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.console-input-field {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1rem;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #0f0;
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
|
||||
animation: blink-empty 1s infinite linear;
|
||||
background-image: linear-gradient(#0f0, #0f0);
|
||||
background-position: 1px center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 1px 1.1em;
|
||||
|
||||
&:focus {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hidden-label {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px 1px 1px 1px); /* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
|
||||
clip: rect(1px, 1px, 1px, 1px); /*maybe deprecated but we need to support legacy browsers */
|
||||
clip-path: inset(50%); /*modern browsers, clip-path works inwards from each corner*/
|
||||
white-space: nowrap; /* added line to stop words getting smushed together (as they go onto seperate lines and some screen readers do not understand line feeds as a space */
|
||||
}
|
||||
|
||||
@keyframes blink-empty {
|
||||
0% {background-size: 1px 1.1em;}
|
||||
50% {background-size: 1px 1.1em;}
|
||||
51% {background-size: 0 1.1em;}
|
||||
100% {background-size: 0 1.1em;}
|
||||
}
|
||||
</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" @click="animate = false"></div>
|
||||
</template>
|
||||
|
@ -1,23 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
export interface Project {
|
||||
icon?: string;
|
||||
name: string;
|
||||
description: string;
|
||||
link: string;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
defineProps({
|
||||
projects: {type: Array as () => Project[], required: true}
|
||||
projects: {type: Array as () => any[], required: true}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.invert {
|
||||
filter: invert(100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(p, i) in projects">
|
||||
<div class="d-flex align-items-center border p-2" :class="i == 0 ? '' : 'border-top-0'">
|
||||
<div class="me-2">
|
||||
<img :src="p.icon || '/git.png'" alt="Logo" style="height: 40px; width: 40px;">
|
||||
<img :src="p.icon || '/git.png'" alt="Logo" :class="{invert: p.invertIcon}" style="height: 40px; width: 40px;">
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div>
|
||||
|
@ -1,28 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
const resume = 'https://drive.google.com/file/d/1N1L2SYvqY49OJAR97cjjHANj0ModmZSy/view?usp=drive_link';
|
||||
const resume = 'https://drive.google.com/file/d/1km4XqtXrYtoUnrDpEtDmAeKWh1qyp9nQ/view?usp=drive_link';
|
||||
|
||||
const refrences = [
|
||||
// ['Andre Mourinho', ''],
|
||||
['CD Projekt Red', 'https://files.zakscode.com/share/N61Db3y0'],
|
||||
['Chris Cartwright', 'https://files.zakscode.com/share/luyY49_N'],
|
||||
['Garry Whyte', 'https://files.zakscode.com/share/zHjnHReT'],
|
||||
['Linda Nicodemo', 'https://files.zakscode.com/share/1wKpkQzW'],
|
||||
['Ray Power', 'https://files.zakscode.com/share/bTR2ab_P'],
|
||||
['Andre Mourinho', 'https://drive.google.com/file/d/1QSRzBg6xj1evwIhpAkrKZqPrbJOjwkYR/view?usp=drive_link'],
|
||||
['CD Projekt Red', 'https://drive.google.com/file/d/1QT02DBRFg4HcJXoxuH0WfiozjYHoJWrn/view?usp=drive_link'],
|
||||
['Chris Cartwright', 'https://drive.google.com/file/d/17JtSlVvMt_84XG0iQO8cHlDsi30oR34c/view?usp=drive_link'],
|
||||
['Garry Whyte', 'https://drive.google.com/file/d/18Mqv1vX2wmseQkqEPRIZiuSuGhDri-dB/view?usp=drive_link'],
|
||||
['Linda Nicodemo', 'https://drive.google.com/file/d/1c4rB1oSWW4OoaURHkSeCHpOEkdBdQ_fH/view?usp=drive_link'],
|
||||
['Ray Power', 'https://drive.google.com/file/d/1aP8Xpg3hlP0yQV8_sg9yeTgsrTRCcJZH/view?usp=drive_link'],
|
||||
]
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="d-block d-md-none">
|
||||
<div class="mb-3">
|
||||
<a class="btn btn-outline-danger w-100" :href="resume" target="_blank">CSV / Resume</a>
|
||||
<a class="btn btn-outline-danger w-100" :href="resume" target="_blank">Resume</a>
|
||||
</div>
|
||||
<div class="btn-group-vertical w-100" role="group">
|
||||
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-block">
|
||||
<a class="btn btn-outline-danger me-3" :href="resume" target="_blank">CSV / Resume</a>
|
||||
<div class="d-none d-md-flex align-items-start">
|
||||
<a class="btn btn-outline-danger me-3" :href="resume" target="_blank">Resume</a>
|
||||
<div class="btn-group" role="group">
|
||||
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</a>
|
||||
</div>
|
||||
|
@ -1,3 +1,6 @@
|
||||
const devMode = location?.port == '5173';
|
||||
|
||||
export const environment = {
|
||||
postMailKey: (<any>window)?.env?.APP_POSTMAIL_KEY || import.meta.env.APP_POSTMAIL_ACCESS_TOKEN,
|
||||
apiUrl: devMode ? 'http://localhost' : location.origin,
|
||||
devMode
|
||||
}
|
||||
|
11
src/modules/konsole/commands/banner.js
Normal file
11
src/modules/konsole/commands/banner.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['banner'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display login banner';
|
||||
},
|
||||
run: args => {
|
||||
return `Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`;
|
||||
}
|
||||
}
|
14
src/modules/konsole/commands/cat.js
Normal file
14
src/modules/konsole/commands/cat.js
Normal file
@ -0,0 +1,14 @@
|
||||
window.cli.exec['cat'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display file contents';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('cat: missing operand');
|
||||
const file = window.cli.fs(args[0]);
|
||||
if(file == null) throw new Error('cat: File does not exist');
|
||||
return file;
|
||||
}
|
||||
}
|
16
src/modules/konsole/commands/cd.js
Normal file
16
src/modules/konsole/commands/cd.js
Normal file
@ -0,0 +1,16 @@
|
||||
window.cli.exec['cd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Change present working directory';
|
||||
},
|
||||
run: args => {
|
||||
const path = window.cli.fs(args[0]);
|
||||
if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`);
|
||||
if(typeof path != 'object') throw new Error(`cd: \'${args[0]}\': Not a directory`);
|
||||
|
||||
window.cli.pwd = window.cli.path(args[0]);
|
||||
window.cli._prompt.innerText = window.cli._buildPrompt();
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/clear.js
Normal file
11
src/modules/konsole/commands/clear.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['clear'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Clear console output';
|
||||
},
|
||||
run: args => {
|
||||
setTimeout(() => window.cli._output.innerHTML = '', 1);
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/date.js
Normal file
11
src/modules/konsole/commands/date.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['date'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current date & time';
|
||||
},
|
||||
run: args => {
|
||||
return new Date().toLocaleString();
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/echo.js
Normal file
11
src/modules/konsole/commands/echo.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['echo'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Output text to console';
|
||||
},
|
||||
run: args => {
|
||||
return args.join(' ');
|
||||
}
|
||||
}
|
16
src/modules/konsole/commands/exit.js
Normal file
16
src/modules/konsole/commands/exit.js
Normal file
@ -0,0 +1,16 @@
|
||||
window.cli.exec['exit'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'End session';
|
||||
},
|
||||
run: args => {
|
||||
setTimeout(() => {
|
||||
window.cli._history = [];
|
||||
window.cli._index = 0;
|
||||
window.cli._output.innerHTML = ''
|
||||
window.cli.stdOut(window.cli.exec['banner'].run());
|
||||
}, 1);
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/help.js
Normal file
12
src/modules/konsole/commands/help.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['help'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display all commands';
|
||||
},
|
||||
run: args => {
|
||||
return `Konsole v${window.cli.version} - an experimental bash emulator written in JavaScript\nCreated By: Zakary Timson\n\n` +
|
||||
Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n');
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/hostname.js
Normal file
11
src/modules/konsole/commands/hostname.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['hostname'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get computer hostname';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.hostname;
|
||||
}
|
||||
}
|
20
src/modules/konsole/commands/ls.js
Normal file
20
src/modules/konsole/commands/ls.js
Normal file
@ -0,0 +1,20 @@
|
||||
window.cli.exec['ls'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Display directory contents';
|
||||
},
|
||||
run: args => {
|
||||
const target = window.cli.fs(args[0]);
|
||||
if(!target || typeof target != 'object') throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
|
||||
return Object.keys(target)
|
||||
.sort((a, b) => {
|
||||
if(a > b) return 1;
|
||||
if(b > a) return -1;
|
||||
return 0;
|
||||
})
|
||||
.map(p => `-rwxrw---- 1 root root ${typeof target[p] =='object' ? '-' : target[p].length} ${p}`)
|
||||
.join('\n');
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/man.js
Normal file
11
src/modules/konsole/commands/man.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['man'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'View command\'s manual';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.exec[args[0]].help();
|
||||
}
|
||||
}
|
13
src/modules/konsole/commands/mkdir.js
Normal file
13
src/modules/konsole/commands/mkdir.js
Normal file
@ -0,0 +1,13 @@
|
||||
window.cli.exec['mkdir'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Create new directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('mkdir: missing operand');
|
||||
if(window.cli.fs(args[0]) != null) throw new Error('mkdir: File or directory already exists');
|
||||
window.cli.fs(args[0], {});
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/pwd.js
Normal file
11
src/modules/konsole/commands/pwd.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['pwd'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get present working directory';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.pwd;
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/rm.js
Normal file
12
src/modules/konsole/commands/rm.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['rm'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Delete file or directory';
|
||||
},
|
||||
run: args => {
|
||||
if(!args.length) throw new Error('rm: missing operand');
|
||||
args.forEach(a => window.cli.fs(a, null));
|
||||
}
|
||||
}
|
23
src/modules/konsole/commands/shower-thought.js
Normal file
23
src/modules/konsole/commands/shower-thought.js
Normal file
@ -0,0 +1,23 @@
|
||||
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)];
|
||||
}
|
||||
}
|
12
src/modules/konsole/commands/touch.js
Normal file
12
src/modules/konsole/commands/touch.js
Normal file
@ -0,0 +1,12 @@
|
||||
window.cli.exec['touch'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Change file timestamps & create file if missing';
|
||||
},
|
||||
run: args => {
|
||||
if(!args[0]) throw new Error('touch: missing operand');
|
||||
if(!window.cli.fs(args[0])) window.cli.fs(args[0], '');
|
||||
}
|
||||
}
|
11
src/modules/konsole/commands/whoami.js
Normal file
11
src/modules/konsole/commands/whoami.js
Normal file
@ -0,0 +1,11 @@
|
||||
window.cli.exec['whoami'] = {
|
||||
autocomplete: () => {
|
||||
return [];
|
||||
},
|
||||
help: () => {
|
||||
return 'Get current user account';
|
||||
},
|
||||
run: args => {
|
||||
return window.cli.user;
|
||||
}
|
||||
}
|
21
src/modules/konsole/index.js
Normal file
21
src/modules/konsole/index.js
Normal file
@ -0,0 +1,21 @@
|
||||
import './konsole.js';
|
||||
import './konsole.css';
|
||||
|
||||
// CLI Commands
|
||||
import './commands/banner.js';
|
||||
import './commands/cat.js';
|
||||
import './commands/cd.js'
|
||||
import './commands/clear.js';
|
||||
import './commands/date.js';
|
||||
import './commands/echo.js';
|
||||
import './commands/exit.js';
|
||||
import './commands/help.js';
|
||||
import './commands/hostname.js';
|
||||
import './commands/ls.js';
|
||||
import './commands/man.js';
|
||||
import './commands/mkdir.js';
|
||||
import './commands/pwd.js';
|
||||
import './commands/rm.js';
|
||||
import './commands/shower-thought.js';
|
||||
import './commands/touch.js';
|
||||
import './commands/whoami.js';
|
40
src/modules/konsole/konsole.css
Normal file
40
src/modules/konsole/konsole.css
Normal file
@ -0,0 +1,40 @@
|
||||
.cli-container {
|
||||
padding: 1em;
|
||||
background: #333;
|
||||
font-family: monospace !important;
|
||||
overflow-y: auto;
|
||||
min-height: 150px;
|
||||
max-height: 300px;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
.cli-stdout {
|
||||
flex-grow: 1;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cli-stdout-line {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-height: 1em;
|
||||
}
|
||||
|
||||
.cli-stdin {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.cli-stdin-prompt {
|
||||
padding-right: 0.5em;
|
||||
text-wrap: nowrap;
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
.cli-stdin-input {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 1em;
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
color: #0f0;
|
||||
flex-grow: 1;
|
||||
padding: 0;
|
||||
}
|
156
src/modules/konsole/konsole.js
Normal file
156
src/modules/konsole/konsole.js
Normal file
@ -0,0 +1,156 @@
|
||||
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',
|
||||
|
||||
_buildPrompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
|
||||
|
||||
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._buildPrompt()}</label>
|
||||
<input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" autocomplete="off"/>
|
||||
</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();
|
||||
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.stdOut(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('/') : '');
|
||||
},
|
||||
fs: (path, set) => {
|
||||
return window.cli.path(path).split('/').filter(p => !!p).reduce((t, p, i, arr) => {
|
||||
if(!t?.hasOwnProperty(p)) {
|
||||
if(set == undefined) return undefined;
|
||||
t[p] = {};
|
||||
}
|
||||
if(set !== undefined && i == arr.length - 1) {
|
||||
if(set == null) delete t[p];
|
||||
else t[p] = set;
|
||||
}
|
||||
return t[p];
|
||||
}, window.cli.filesystem);
|
||||
},
|
||||
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, suppress=false) => {
|
||||
(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) {
|
||||
if(!suppress) window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${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(!suppress) window.cli.stdOut(`${window.cli._buildPrompt()} ${command}${out != null ? '\n' + (out || '\n') : ''}`);
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
if(!suppress) {
|
||||
window.cli.stdErr(`${window.cli._buildPrompt()} ${command}\n${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);
|
||||
},
|
||||
type: (text, speed=150) => {
|
||||
let counter = 0;
|
||||
return new Promise(res => {
|
||||
let typing = setInterval(() => {
|
||||
if(counter < text.length) {
|
||||
window.cli._input.value += text[counter];
|
||||
} else {
|
||||
clearInterval(typing);
|
||||
setTimeout(() => {
|
||||
window.cli.stdIn(text);
|
||||
window.cli._input.value = '';
|
||||
res();
|
||||
}, 750);
|
||||
}
|
||||
counter++;
|
||||
}, speed);
|
||||
});
|
||||
}
|
||||
};
|
45
src/modules/konsole/models/file.js
Normal file
45
src/modules/konsole/models/file.js
Normal file
@ -0,0 +1,45 @@
|
||||
export class CliFile {
|
||||
owner = window.cli.user;
|
||||
group = window.cli.user;
|
||||
permissions = 750;
|
||||
created = new Date();
|
||||
modified = new Date();
|
||||
|
||||
#data;
|
||||
get date() { return this.#data; }
|
||||
set date(d) {
|
||||
this.modified = new Date();
|
||||
this.#data = d;
|
||||
}
|
||||
|
||||
static instanceOf(f) {
|
||||
return f.hasOwnProperty('data');
|
||||
}
|
||||
|
||||
size() {
|
||||
return this.#data.toString().length;
|
||||
}
|
||||
|
||||
permsString() {
|
||||
return '-' + this.permissions.toString().split('').map(p => {
|
||||
switch (p) {
|
||||
case 0:
|
||||
return '---';
|
||||
case 1:
|
||||
return '--x';
|
||||
case 2:
|
||||
return '-w-';
|
||||
case 3:
|
||||
return '-wx';
|
||||
case 4:
|
||||
return 'r--';
|
||||
case 5:
|
||||
return 'r-x';
|
||||
case 6:
|
||||
return 'rw-';
|
||||
case 7:
|
||||
return 'rwx';
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
}
|
@ -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}
|
||||
]
|
||||
|
36
src/services/momentum.service.ts
Normal file
36
src/services/momentum.service.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import {environment} from '@/environments/environment';
|
||||
import {PathEvent} from '@ztimson/utils';
|
||||
import {ref, onUnmounted} from 'vue';
|
||||
import {Momentum} from '@ztimson/momentum';
|
||||
|
||||
export function mRef<T>(event: string, get: (event?: PathEvent, ...args: any[]) => T | Promise<T>): any;
|
||||
export function mRef<T>(event: string, init: () => T | Promise<T>, get: (event: PathEvent, ...args: any[]) => T | Promise<T>): any;
|
||||
export function mRef<T>(event: string, fn1: (...args: any[]) => T | Promise<T>, fn2?: (event: PathEvent, ...args: any[]) => T | Promise<T>): any {
|
||||
// Init
|
||||
const resp = fn1(), promise = resp instanceof Promise;
|
||||
const r = ref<T | null>(promise ? null : resp);
|
||||
if(promise) resp.then(v => r.value = v);
|
||||
|
||||
// Get
|
||||
const get = fn2 || fn1;
|
||||
mWatch(event, async (event, ...args) => r.value = await get(<any>event, ...args));
|
||||
return r;
|
||||
}
|
||||
|
||||
export function mWatch(event: string, get: (event?: PathEvent, ...args: any[]) => any) {
|
||||
const unsubscribe = momentum.on(event, get);
|
||||
onUnmounted(() => unsubscribe());
|
||||
}
|
||||
|
||||
export const momentum = new Momentum(environment.apiUrl, {
|
||||
app: 'ZaksCode',
|
||||
logLevel: 'ERROR',
|
||||
persist: true,
|
||||
socket: true
|
||||
});
|
||||
momentum.client.inject(true);
|
||||
|
||||
momentum.on('auth/session-expired', () => {
|
||||
momentum.auth.logout();
|
||||
location.reload();
|
||||
});
|
@ -4,39 +4,30 @@ 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 {momentum} from '@/services/momentum.service';
|
||||
import {ref} from 'vue';
|
||||
|
||||
const services: Projects[] = [
|
||||
{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[] = [
|
||||
{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 '},
|
||||
{name: 'Persist', icon: 'https://git.zakscode.com/repo-avatars/89f6c36caf75762ed9f7f98b69044b7db30da5230be7c5cea54f8a1158f1669a', link: 'https://www.npmjs.com/package/@ztimson/persist', source: 'https://git.zakscode.com/ztimson/persist', description: 'Typescript library to sync variables with LocalStorage & persist state through page reloads'},
|
||||
{name: 'PyBar', icon: 'https://git.zakscode.com/repo-avatars/002f97340c2781ccfa5d09fde97403fd499c39a9ad5675dc0edf05a8396e9ac5', link: 'https://git.zakscode.com/ztimson/py-bar', source: 'https://git.zakscode.com/ztimson/py-bar', description: 'Python library to display ASCII progress bars using iterators'},
|
||||
{name: 'Transmute', icon: 'https://git.zakscode.com/repo-avatars/b497daaf22a214fe6d6cc35b8ec217cd22401b668dff93dcfcc7557bd8a46d96', link: 'https://git.zakscode.com/ztimson/transmute', source: 'https://git.zakscode.com/ztimson/transmute', description: 'Distributed video conversion tool with built in WebUI'},
|
||||
{name: 'ZaksCode', icon: 'https://git.zakscode.com/repo-avatars/590279cb4b176c6a7924364c7b0ef78afa80696703abe5bef8d9ce7e12477f3d', link: 'https://zakscode.com', source: 'https://git.zakscode.com/ztimson/zakscode', description: 'Source code for this website, ZaksCode.com'},
|
||||
];
|
||||
// Get favorites
|
||||
const products = ref<any[]>([]);
|
||||
const openSource = ref<any[]>([]);
|
||||
momentum.data.read('Repos').then(resp =>
|
||||
resp.forEach((r: any) => (r.product ? products : openSource).value.push(r)));
|
||||
|
||||
// Get repository count
|
||||
let remainder = ref(0);
|
||||
fetch('https://git.zakscode.com/api/v1/repos/search', {
|
||||
fetch('https://git.zakscode.com/api/v1/repos/search?limit=1000', {
|
||||
method: 'get',
|
||||
headers: {"Content-Type": "application/json"}
|
||||
}).then(async repos => {
|
||||
const data = (await repos.json())?.data;
|
||||
remainder.value = data.length - openSource.length;
|
||||
}).then(async (resp: any) => {
|
||||
const data = (await resp.json())?.data;
|
||||
remainder.value = data.length - openSource.value.length;
|
||||
});
|
||||
</script>
|
||||
|
||||
<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">
|
||||
@ -48,7 +39,7 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
|
||||
<div class="d-flex flex-wrap justify-content-around">
|
||||
<card color="#6aa84f" icon="clipboard" offset="1px" title="Plan" text="Working with the client we will identify the goals of the project. This includes things like the target audience, use case, features, style, and delivery."/>
|
||||
<card color="#6d9eeb" icon="code" offset="2px" title="Code" text="Goals are broken down into tasks and prioritized in our ticketing system. Using CI/CD, tasks are automatically deployed for testing as they are completed."/>
|
||||
<card color="#e69138" icon="message" offset="3px" title="Feedback" text="Clients are notified with the release notes and can test at their convince. Any critiques can be communicated directly to us or through our ticketing system."/>
|
||||
<card color="#e69138" icon="message" offset="3px" title="Feedback" text="Clients are notified with the release notes and can test at their convenience. Any critiques can be communicated directly to us or through our ticketing system."/>
|
||||
<card color="#674ea7" icon="play" offset="2px" title="Release" text="Once all goals are complete we will work with you to deploy the product to any location. Once setup, future updates are automatically deployed to our clients."/>
|
||||
</div>
|
||||
</div>
|
||||
@ -58,17 +49,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 himself 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, DevOps, cloud/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 businesses 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>
|
||||
@ -82,7 +64,7 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
|
||||
<hr class="mb-4">
|
||||
<div class="mb-4">
|
||||
<h4 class="mb-3 text-muted">Products & Services</h4>
|
||||
<projects :projects="services"/>
|
||||
<projects :projects="products"/>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="mb-3 text-muted">Open Source</h4>
|
||||
|
@ -9,10 +9,11 @@
|
||||
"src/**/__tests__/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"moduleResolution": "Node",
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"moduleResolution": "Node",
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
|
@ -10,6 +10,7 @@
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": [
|
||||
"node"
|
||||
|
Reference in New Issue
Block a user