Compare commits

...

35 Commits

Author SHA1 Message Date
794c59e375 Update docker/nginx.conf
All checks were successful
Build Website / Build NPM Project (push) Successful in 11s
Build Website / Tag Version (push) Successful in 6s
Build Website / Build & Push Dockerfile (push) Successful in 23s
2025-02-03 08:53:21 -05:00
9b6d50fa42 Refresh page on logout
All checks were successful
Build Website / Build NPM Project (push) Successful in 43s
Build Website / Tag Version (push) Successful in 10s
Build Website / Build & Push Dockerfile (push) Successful in 43s
2025-01-07 15:09:23 -05:00
f7211e3f87 Fix momentum session expired & sending emails
All checks were successful
Build Website / Build NPM Project (push) Successful in 47s
Build Website / Tag Version (push) Successful in 9s
Build Website / Build & Push Dockerfile (push) Successful in 41s
2025-01-05 21:00:21 -05:00
8d84fc8274 Fix momentum config
All checks were successful
Build Website / Build NPM Project (push) Successful in 11s
Build Website / Tag Version (push) Successful in 6s
Build Website / Build & Push Dockerfile (push) Successful in 25s
2024-12-30 12:19:57 -05:00
94c2f336f6 Zakscode is now using momentum
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Tag Version (push) Successful in 7s
Build Website / Build & Push Dockerfile (push) Successful in 42s
2024-12-30 12:05:38 -05:00
accd08d568 Update src/models/project.ts
All checks were successful
Build Website / Build NPM Project (push) Successful in 11s
Build Website / Build & Push Dockerfile (push) Successful in 28s
Build Website / Tag Version (push) Successful in 6s
2024-05-24 08:52:06 -04:00
521ba1902d Fixed build
All checks were successful
Build Website / Build NPM Project (push) Successful in 20s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 1m27s
2024-02-09 21:50:57 -05:00
36facdebe3 Fixed up project links
Some checks failed
Build Website / Build NPM Project (push) Failing after 7s
Build Website / Tag Version (push) Has been skipped
Build Website / Build & Push Dockerfile (push) Has been skipped
2024-02-09 12:38:20 -05:00
9439683b01 Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 9s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 1m4s
2024-02-09 17:26:39 +00:00
2ea1c345c7 Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 14s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 1m12s
2024-02-09 17:20:46 +00:00
2aebc4c54b Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 8s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 48s
2024-02-01 05:39:20 +00:00
81cf9c09f9 Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 11s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 43s
2024-02-01 04:31:17 +00:00
bdbaf799a8 Update src/components/refrences.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 9s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 50s
2024-02-01 04:29:16 +00:00
e055e306df Added Andre's references
All checks were successful
Build Website / Build NPM Project (push) Successful in 36s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 44s
2024-01-22 14:23:54 -05:00
1c25802a2e Fixed typeo
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Tag Version (push) Successful in 7s
Build Website / Build & Push Dockerfile (push) Successful in 1m4s
2024-01-09 09:18:03 -05:00
20be9d170a Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Tag Version (push) Successful in 9s
Build Website / Build & Push Dockerfile (push) Successful in 1m0s
2024-01-08 18:34:06 +00:00
120bd3ec50 Update index.html
All checks were successful
Build Website / Build NPM Project (push) Successful in 20s
Build Website / Tag Version (push) Successful in 7s
Build Website / Build & Push Dockerfile (push) Successful in 1m5s
2024-01-08 15:46:28 +00:00
8be3de8123 Update src/views/Home.vue
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Tag Version (push) Successful in 8s
Build Website / Build & Push Dockerfile (push) Successful in 1m3s
2024-01-07 22:09:53 +00:00
c0b348c5c6 Updated about section & meta tags
All checks were successful
Build Website / Build NPM Project (push) Successful in 21s
Build Website / Tag Version (push) Successful in 10s
Build Website / Build & Push Dockerfile (push) Successful in 56s
2024-01-07 16:44:25 -05:00
b71b2092c3 Updated konsole
All checks were successful
Build Website / Build NPM Project (push) Successful in 21s
Build Website / Tag Version (push) Successful in 7s
Build Website / Build & Push Dockerfile (push) Successful in 1m4s
2024-01-07 16:36:14 -05:00
0046dc1cb4 Pulled konsole out into it's own module
All checks were successful
Build Website / Build NPM Project (push) Successful in 22s
Build Website / Tag Version (push) Successful in 6s
Build Website / Build & Push Dockerfile (push) Successful in 1m18s
2024-01-07 12:29:00 -05:00
42d26503bc Fixed konsole clear command
All checks were successful
Build Website / Build NPM Project (push) Successful in 8s
Build Website / Tag Version (push) Successful in 6s
Build Website / Build & Push Dockerfile (push) Successful in 59s
2024-01-05 15:35:23 -05:00
ec5c66a9c2 Update version & tag
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Tag Version (push) Successful in 4s
Build Website / Build & Push Dockerfile (push) Successful in 37s
2024-01-05 15:33:44 -05:00
1217e86ac9 Updated documentation
All checks were successful
Build Website / Build NPM Project (push) Successful in 8s
Build Website / Build & Push Dockerfile (push) Successful in 31s
2024-01-05 15:32:17 -05:00
d07c1a4521 Fixed konsole not outputting
All checks were successful
Build Website / Build NPM Project (push) Successful in 20s
Build Website / Build & Push Dockerfile (push) Successful in 43s
2024-01-05 15:25:02 -05:00
cc9c9ea1b8 Fixed Chris Reference
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Build & Push Dockerfile (push) Successful in 43s
2024-01-05 15:19:24 -05:00
f1d6c0a25c Updated meta
All checks were successful
Build Website / Build NPM Project (push) Successful in 31s
Build Website / Build & Push Dockerfile (push) Successful in 48s
2024-01-05 15:14:29 -05:00
43f96b1ea9 Updated references
All checks were successful
Build Website / Build NPM Project (push) Successful in 9s
Build Website / Build & Push Dockerfile (push) Successful in 1m3s
2024-01-05 15:12:22 -05:00
a4d1f6b825 Updated konsole
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Build & Push Dockerfile (push) Successful in 44s
2024-01-05 14:53:45 -05:00
8038e79ad2 Fixed konsole rm 2024-01-05 13:11:48 -05:00
d7c257cb39 Updated links
All checks were successful
Build Website / Build NPM Project (push) Successful in 19s
Build Website / Build & Push Dockerfile (push) Successful in 44s
2024-01-05 01:47:43 -05:00
88f2a716b9 Konsole updates
All checks were successful
Build Website / Build NPM Project (push) Successful in 20s
Build Website / Build & Push Dockerfile (push) Successful in 1m5s
2024-01-05 01:40:17 -05:00
ae279b478b Updated env
Some checks failed
Build Website / Build NPM Project (push) Failing after 31s
Build Website / Build & Push Dockerfile (push) Has been skipped
2024-01-04 20:00:42 -05:00
88761a6a66 Added build 2024-01-04 19:59:19 -05:00
6b28ed61bd Website updates 2024-01-04 19:56:46 -05:00
47 changed files with 1709 additions and 1117 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
APP_POSTMAIL_ACCESS_TOKEN=

53
.github/workflows/build.yaml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: Build Website
run-name: Build Website
on:
push:
jobs:
build:
name: Build NPM Project
runs-on: ubuntu-latest
container: node
steps:
- name: Clone Repository
uses: ztimson/actions/clone@develop
- name: Install Dependencies
run: npm i
- name: Build Project
run: npm run build
- name: Upload Artifacts
uses: actions/upload-artifact@v3
with:
name: website
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
uses: ztimson/actions/.github/workflows/docker.yaml@develop
with:
name: ztimson/zakscode
repository: ${{github.server_url}}/${{github.repository}}.git
pass: ${{secrets.DEPLOY_TOKEN}}

View File

@@ -1,4 +1,4 @@
FROM node as build
FROM node:20-alpine as build
# Variables
ARG NODE_ENV=prod
@@ -8,15 +8,14 @@ ENV NG_CLI_ANALYTICS=ci \
NODE_OPTIONS=${NODE_OPTIONS}
# Setup
RUN mkdir /app
WORKDIR /app
COPY . .
# Install & build
RUN if [ ! -d "dist" ]; then npm ci && npm run build; fi
# Build
RUN if [ ! -d "dist" ]; then npm install && npm run build; fi
# Use Nginx to serve
FROM nginx:1.20-alpine
COPY --from=build /app/dist/browser /usr/share/nginx/html
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
COPY docker/nginx.conf /etc/nginx/nginx.conf
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
View 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
View File

@@ -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 -->
[![Version](https://img.shields.io/badge/dynamic/json.svg?label=Version&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/zakscode/tags&query=$[0].name)](https://git.zakscode.com/ztimson/zakscode/tags)
[![Pull Requests](https://img.shields.io/badge/dynamic/json.svg?label=Pull%20Requests&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/zakscode&query=open_pr_counter)](https://git.zakscode.com/ztimson/zakscode/pulls)
[![Issues](https://img.shields.io/badge/dynamic/json.svg?label=Issues&style=for-the-badge&url=https://git.zakscode.com/api/v1/repos/ztimson/zakscode&query=open_issues_count)](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
[![Vue](https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs)](https://vuejs.org/)
[![Bootstrap](https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white)](https://getbootstrap.com)
[![Docker](https://img.shields.io/badge/Docker-384d54?style=for-the-badge&logo=docker)](https://docker.com/)
[![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](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.

View File

@@ -25,7 +25,7 @@ http {
autoindex off;
location / {
try_files $uri$args $uri$args/ /index.html;
try_files $uri $uri/ /index.html;
}
}
}

View File

@@ -0,0 +1,7 @@
#!/usr/bin/env sh
JSON_STRING='window.env = { \
APP_POSTMAIL_ACCESS_TOKEN: "'"${APP_POSTMAIL_ACCESS_TOKEN}"'", \
}'
sed -i "s@<script id=\"environment\"></script>@<script>${JSON_STRING}</script>@" /usr/share/nginx/html/index.html
exec "$@"

View File

@@ -1,22 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>ZaksCode</title>
<link rel="icon" href="/logo.png">
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name=”robots” content=”index,nofollow” />
<meta property=og:type” content=”website” />
<meta property=og:title” content=”Zakary Timson />
<meta property=og:description” content=”Devops & Software Engineer />
<meta property=og:image content=https://zakscode.com/cloud.gif” />
<meta property=og:url” content=”https://zakscode.com” />
<meta property=”og:site_name” content=”ZaksCode” />
<!-- <meta name=”twitter:title” content=”Zakary Timson” />-->
<!-- <meta name=”twitter:description” content=”Devops & Software Engineer” />-->
<!-- <meta name=”twitter:image” content=”https://zakscode.com/cloud.gif” />-->
<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>
<body>

1525
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 113 KiB

View File

@@ -10,7 +10,7 @@ import Profile from '@/components/profile.vue';
<!-- Content -->
<div class="cap-width mb-3 bg-white">
<!-- Header -->
<header class="p-3 mx-auto" style="background: #732222">
<header class="px-4 d-flex justify-content-center justify-content-sm-start" style="background: #732222">
<profile style="transform: translateY(-33%)" />
</header>
@@ -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>

23
src/components/card.vue Normal file
View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
import Icon from '@/components/icon.vue';
defineProps({
color: {type: String, required: true},
icon: {type: String, required: true},
title: {type: String, required: true},
text: {type: String, required: true},
offset: {type: String, default: '0px'}
});
</script>
<template>
<div class="d-flex flex-column align-items-center border mb-3 p-3" style="width: 240px">
<div class="m-3 p-3 rounded-circle text-white d-flex align-items-middle justify-content-center" :style="'height: 50px; width: 50px; background:' + color">
<icon :name="icon" :style="'margin-top:' + offset"/>
</div>
<h4>{{title}}</h4>
<p class="text-center">
{{text}}
</p>
</div>
</template>

View File

@@ -1,5 +1,6 @@
<script setup lang="ts">
import Icon from '@/components/icon.vue';
import {momentum} from '@/services/momentum.service';
import {ref} from 'vue';
const disable = ref(false);
@@ -10,7 +11,7 @@ const data = ref({
subject: '',
message: '',
});
const errors = ref({
const errors = ref<any>({
banner: false,
name: false,
email: false,
@@ -29,6 +30,14 @@ function validateEmail(email: string) {
return /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email);
}
function send(name: string, email: string, subject: string, message: string) {
return momentum.email.send({
to: ['zaktimson@gmail.com', email],
subject: `ZaksCode: ${subject}`,
body: `From: ${name} &lt;${email}&gt;<br><br>${message}`
});
}
function submit() {
disable.value = true;
const d = data.value;
@@ -42,39 +51,51 @@ function submit() {
};
if(errors.value.name || errors.value.email || errors.value.subject || errors.value.message) return disable.value = false;
// Email.send(d.name, d.email, d.subject, d.message).then(() => {
// done.value = true;
// }).catch(err => {
// console.error(err);
// errors.value.banner = err?.message || err.toString();
// disable.value = false;
// });
send(d.name, d.email, d.subject, d.message).then(() => {
done.value = true;
}).catch(err => {
console.error(err);
errors.value.banner = err?.message || err.toString();
disable.value = false;
});
}
</script>
<template>
<!-- Success Banner -->
<div v-if="done" class="alert alert-success">
<span>Success! We sent you a copy & we will be intouch shortly.</span>
<span>Success! We will be intouch shortly.</span>
</div>
<!-- Error Banner -->
<div v-if="errors?.banner" class="alert alert-danger">
{{ errors.banner }}
Error: {{ (errors as any).banner }}
</div>
<!-- Contact Form -->
<form>
<div class="d-flex flex-wrap justify-content-between mb-2">
<div class="input-group mb-2 mb-sm-0" style="width: 49%; min-width: 250px">
<div class="d-flex flex-column flex-md-row flex-wrap justify-content-between">
<!-- Name -->
<div class="mb-2 p-0 pe-md-1 p-md-0" style="flex: 1 0 0;">
<div class="input-group">
<span class="input-group-text"><icon name="user"/></span>
<input class="form-control" type="text" placeholder="Name" v-model="data.name" v-bind:class="{'is-invalid': errors?.name}" :disabled="!!user || disable" required>
<input class="form-control" type="text" placeholder="Name" v-model="data.name" v-bind:class="{'is-invalid': errors?.name}" :disabled="disable" required>
</div>
<div class="input-group" style="width: 49%; min-width: 250px">
</div>
<!-- Email -->
<div class="mb-2 p-0 ps-md-1" style="flex: 1 0 0;">
<div class="input-group">
<span class="input-group-text"><icon name="envelope"/></span>
<input class="form-control" type="email" placeholder="Email" v-model="data.email" v-bind:class="{'is-invalid': errors?.email}" :disabled="!!user || disable" required>
<input class="form-control" type="email" placeholder="Email" v-model="data.email" v-bind:class="{'is-invalid': errors?.email}" :disabled="disable" required>
</div>
</div>
</div>
<!-- Subject -->
<div class="input-group mb-2">
<span class="input-group-text"><icon name="book"/></span>
<input class="form-control" type="text" placeholder="Subject" v-model="data.subject" v-bind:class="{'is-invalid': errors?.subject}" :disabled="disable" required>
</div>
<!-- Message Body -->
<textarea class="form-control" placeholder="Message" rows="5" v-model="data.message" v-bind:class="{'is-invalid': errors?.message}" :disabled="disable" required></textarea>
<!-- Buttons -->
<div class="text-end pt-3">
<button type="button" class="btn rounded-pill ms-3" @click="reset()">Reset</button>
<button type="button" class="btn btn-primary rounded-pill ms-3" @click="submit" :disabled="disable">Send Message</button>

View File

@@ -4,7 +4,11 @@
<style scoped>
footer {
background: #343a40;
color: darkgrey;
color: #A3A3A3;
a {
color: #75B8FF;
}
}
</style>

View File

@@ -1,251 +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;
}
}
}
}
@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>
<input class="console-input-field" type="text" @keydown="stdIn($event)">
</div>
</div>
<div id="konsole" @click="animate = false"></div>
</template>

View File

@@ -21,7 +21,7 @@ import Icon from '@/components/icon.vue';
</h2>
<ul class="m-0 p-0 text-start" style="list-style: none">
<li><icon name="map-marker-alt" class="me-1"/> Toronto Ontario, Canada</li>
<li><icon name="envelope" class="me-1"/> <a href="mailto:zaktimson@gmail.com">zaktimson@gmail.com</a></li>
<li><icon name="envelope" class="me-1"/> <a href="mailto:zaktimson@gmail.com" target="_blank">zaktimson@gmail.com</a></li>
<li><icon name="github" class="fa-brands me-1"/> <a href="https://github.com/ztimson" target="_blank">github.com/ztimson</a></li>
<li><icon name="git-alt" class="fa-brands me-1"/> <a href="https://git.zakscode.com" target="_blank">git.zakscode.com</a></li>
</ul>

View File

@@ -1,24 +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">
<hr v-if="i != 0">
<div class="d-flex align-items-center">
<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>

View File

@@ -1,33 +1,30 @@
<script setup lang="ts">
</script>
<style scoped lang="scss">
.btn-outline-info:hover {
color: #ffffff;
}
</style>
const resume = 'https://drive.google.com/file/d/1km4XqtXrYtoUnrDpEtDmAeKWh1qyp9nQ/view?usp=drive_link';
const refrences = [
['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-primary w-100">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 class="btn btn-outline-info">CD Projekt Red</a>
<a class="btn btn-outline-info">Chris Cartwright</a>
<a class="btn btn-outline-info">Garry Whyte</a>
<a class="btn btn-outline-info">Ray Power</a>
<a class="btn btn-outline-info">Linda Nicodemo</a>
<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-primary me-3">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 class="btn btn-outline-info">CD Projekt Red</a>
<a class="btn btn-outline-info">Chris Cartwright</a>
<a class="btn btn-outline-info">Garry Whyte</a>
<a class="btn btn-outline-info">Ray Power</a>
<a class="btn btn-outline-info">Linda Nicodemo</a>
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</a>
</div>
</div>
</template>

View File

@@ -0,0 +1,6 @@
const devMode = location?.port == '5173';
export const environment = {
apiUrl: devMode ? 'http://localhost' : location.origin,
devMode
}

View File

@@ -4,7 +4,7 @@
::-webkit-scrollbar { width: 10px; }
::-webkit-scrollbar-track { background: #333; }
::-webkit-scrollbar-thumb { background: #555; border-radius: 5px }
::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; }
::-webkit-scrollbar-thumb:hover { background: #aaa; }
html, body {
@@ -14,11 +14,11 @@ html, body {
padding: 0;
font-family: Roboto,sans-serif;
background: #354B72 url(/cloud.gif) no-repeat fixed right 50% top -10vh;
background: #354B72 url('/cloud.gif') no-repeat fixed right 50% top -10vh;
}
a:not(.btn), a:not(.btn):visited {
color: #007bff;
color: #006FE6;
text-decoration: none;
&:hover {

View 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`;
}
}

View 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;
}
}

View 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();
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['clear'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Clear console output';
},
run: args => {
setTimeout(() => window.cli._output.innerHTML = '', 1);
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['date'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get current date & time';
},
run: args => {
return new Date().toLocaleString();
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['echo'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Output text to console';
},
run: args => {
return args.join(' ');
}
}

View 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);
}
}

View 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');
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['hostname'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get computer hostname';
},
run: args => {
return window.cli.hostname;
}
}

View 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');
}
}

View 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();
}
}

View 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], {});
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['pwd'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get present working directory';
},
run: args => {
return window.cli.pwd;
}
}

View 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));
}
}

View 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)];
}
}

View 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], '');
}
}

View File

@@ -0,0 +1,11 @@
window.cli.exec['whoami'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get current user account';
},
run: args => {
return window.cli.user;
}
}

View 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';

View 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;
}

View 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);
});
}
};

View 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('');
}
}

View File

@@ -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}
]

View 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();
});

View File

@@ -1,96 +1,56 @@
<script setup lang="ts">
import Card from '@/components/card.vue';
import Contact from '@/components/contact.vue';
import Icon from '@/components/icon.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: 'Screen Printing Supplies'},
{name: 'Map Alliance', icon: 'https://maps.zakscode.com/assets/images/logo.png', link: 'https://maps.zakscode.com', description: 'Online GIS & map editing solution'},
{name: 'Phone Reminders', icon: 'https://phone-reminders.com/phone-reminders.png', link: 'https://phone-reminders.com', description: 'Send SMS & Voice call reminders from Google Calendar'},
];
// 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)));
const openSource: Projects[] = [
{name: 'Formula Manager', icon: 'https://git.zakscode.com/avatars/7ec6bfd66b2bf9bad5c43c75a33f9cb3f6609b05c33a31f5d1e524a567cd09c1?size=280', link: 'https://screenprintingsuppliescanada.com/formulation-manager', description: 'Screen Printing Supplies'},
{name: 'Map Alliance', icon: 'https://maps.zakscode.com/assets/images/logo.png', link: 'https://maps.zakscode.com', description: 'Online GIS & map editing solution', source: 'https://google.com'},
{name: 'Phone Reminders', icon: 'https://phone-reminders.com/phone-reminders.png', link: 'https://phone-reminders.com', description: 'Send SMS & Voice call reminders from Google Calendar'},
];
// Get repository count
let remainder = ref(0);
fetch('https://git.zakscode.com/api/v1/repos/search?limit=1000', {
method: 'get',
headers: {"Content-Type": "application/json"}
}).then(async (resp: any) => {
const data = (await resp.json())?.data;
remainder.value = data.length - openSource.value.length;
});
</script>
<template>
<div class="p-3">
<konsole class="mb-5" style="max-height: 300px" />
<!-- Terminal -->
<konsole class="mb-5" />
<!-- Steps -->
<div class="mb-5 pt-5">
<h4 class="mb-0 text-center">Plan for Success</h4>
<h3 class="mb-0 text-center">Plan for Success</h3>
<hr class="mb-4">
<div class="text-center my-5">
<img src="/cycle.svg" alt="Development Cycle" style="width: 100%; max-width: 600px; height: auto;">
</div>
<div class="d-flex">
<div class="d-flex flex-column align-items-center border m-3 p-3 text-center" style="flex: 1 0 0;">
<div class="m-3 p-3 rounded-circle text-white d-flex align-items-middle justify-content-center" style="background: #6aa84f; height: 50px; width: 50px">
<icon name="clipboard"/>
</div>
<h5>Plan</h5>
<p class="text-center">
Working with the client we will identify the goals of the project. This includes things like the target audience, use case, features, style, and deployment strategy.
</p>
</div>
<div class="d-flex flex-column align-items-center border m-3 p-3" style="flex: 1 0 0;">
<div class="m-3 p-3 rounded-circle text-white d-flex align-items-middle justify-content-center" style="background: #6d9eeb; height: 50px; width: 50px">
<icon name="code"/>
</div>
<h5>Develop</h5>
<p class="text-center">
Goals are broken down into tasks and are prioritized. Using CI/CD, tasks are automatically deployed for testing as they are completed.
</p>
</div>
<div class="d-flex flex-column align-items-center border m-3 p-3" style="flex: 1 0 0;">
<div class="m-3 p-3 rounded-circle text-white d-flex align-items-middle justify-content-center" style="background: #e69138; height: 50px; width: 50px">
<icon name="message"/>
</div>
<h5>Feedback</h5>
<p class="text-center">
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.
</p>
</div>
<div class="d-flex flex-column align-items-center border m-3 p-3" style="flex: 1 0 0;">
<div class="m-3 p-3 rounded-circle text-white" style="background: #674ea7; height: 50px; width: 50px">
<h5 class="m-0">4</h5>
</div>
<h5>Release</h5>
<p class="text-center">
Once all goals are complete we will work with you to deploy the product to any location. Once setup, future updates are automatically rolled out.
</p>
</div>
</div>
<div class="d-flex align-items-center">
<div>
</div>
<div class="d-flex flex-column">
</div>
<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 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>
<!-- About Section -->
<div class="mb-5">
<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>
@@ -98,19 +58,22 @@ const openSource: Projects[] = [
</div>
</div>
<!-- Projects List -->
<div class="mb-5">
<h3 class="m-0">Projects</h3>
<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>
<projects :projects="openSource"/>
</div>
<a v-if="remainder" class="float-end m-2" href="https://git.zakscode.com/explore" target="_blank">See {{remainder}} More...</a>
</div>
<!-- Contact Form -->
<div>
<h3 class="m-0">Contact</h3>
<hr class="mb-4">

View File

@@ -9,9 +9,11 @@
"src/**/__tests__/*"
],
"compilerOptions": {
"composite": true,
"noEmit": true,
"baseUrl": ".",
"composite": true,
"lib": ["DOM", "ESNext"],
"moduleResolution": "Node",
"noEmit": true,
"paths": {
"@/*": [
"./src/*"

View File

@@ -1,7 +1,6 @@
import vue from '@vitejs/plugin-vue';
import {fileURLToPath, URL} from 'node:url';
import {defineConfig} from 'vite';
import {fileURLToPath, URL} from 'node:url';
import vue from '@vitejs/plugin-vue';
// https://vitejs.dev/config/
export default defineConfig({
@@ -12,5 +11,6 @@ export default defineConfig({
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
}
},
envPrefix: 'APP',
});