Compare commits
35 Commits
4b3e89fa59
...
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 | |||
88761a6a66 | |||
6b28ed61bd |
53
.github/workflows/build.yaml
vendored
Normal file
53
.github/workflows/build.yaml
vendored
Normal 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}}
|
15
Dockerfile
15
Dockerfile
@@ -1,4 +1,4 @@
|
|||||||
FROM node as build
|
FROM node:20-alpine as build
|
||||||
|
|
||||||
# Variables
|
# Variables
|
||||||
ARG NODE_ENV=prod
|
ARG NODE_ENV=prod
|
||||||
@@ -8,15 +8,14 @@ ENV NG_CLI_ANALYTICS=ci \
|
|||||||
NODE_OPTIONS=${NODE_OPTIONS}
|
NODE_OPTIONS=${NODE_OPTIONS}
|
||||||
|
|
||||||
# Setup
|
# Setup
|
||||||
|
RUN mkdir /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Install & build
|
# Build
|
||||||
RUN if [ ! -d "dist" ]; then npm ci && npm run build; fi
|
RUN if [ ! -d "dist" ]; then npm install && npm run build; fi
|
||||||
|
|
||||||
# Use Nginx to serve
|
# Use Nginx to serve
|
||||||
FROM nginx:1.20-alpine
|
FROM git.zakscode.com/ztimson/momentum:latest
|
||||||
COPY --from=build /app/dist/browser /usr/share/nginx/html
|
RUN rm -rf /app/server/public/assets /app/server/public/index.html
|
||||||
COPY docker/robots.txt /usr/share/nginx/html/robots.txt
|
COPY --from=build /app/dist /app/server/public
|
||||||
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
|
||||||
EXPOSE 80
|
|
||||||
|
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
|
<!-- Header -->
|
||||||
Business website for ZaksCode. Provides some background information, contact inforation & a portfolio of projects.
|
<div id="top" align="center">
|
||||||
[View here](https://zakscode.com).
|
<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
|
## Table of Contents
|
||||||
[[_TOC_]]
|
- [ZaksCode](#top)
|
||||||
|
- [About](#about)
|
||||||
|
- [Demo](#demo)
|
||||||
|
- [Built With](#built-with)
|
||||||
|
- [Setup](#setup)
|
||||||
|
- [Production](#production)
|
||||||
|
- [Development](#development)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
## Cheatsheet
|
## About
|
||||||
```bash
|
|
||||||
# Start Angular development server
|
This the source code for `zakscode.com`.
|
||||||
npm run start
|
|
||||||
# Build production site
|
ZaksCode is the personal website of Zakary Timson & is used as his business page & portfolio.
|
||||||
npm run build
|
### Demo
|
||||||
# Build docker image
|
|
||||||
docker build -t gitlab.zakscode.com:5050/zakcode/zakscode:latest .
|
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;
|
autoindex off;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
try_files $uri$args $uri$args/ /index.html;
|
try_files $uri $uri/ /index.html;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
7
docker/setup-environment.sh
Normal file
7
docker/setup-environment.sh
Normal 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 "$@"
|
22
index.html
22
index.html
@@ -1,22 +1,22 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
|
||||||
<title>ZaksCode</title>
|
<title>ZaksCode</title>
|
||||||
<link rel="icon" href="/logo.png">
|
<link rel="icon" href="/logo.png">
|
||||||
|
|
||||||
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<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:title” content=”Zakary Timson” />
|
<meta property="og:type" content="article">
|
||||||
<meta property=”og:description” content=”Devops & Software Engineer” />
|
<meta property="og:url" content="https://zakscode.com">
|
||||||
<meta property=”og:image” content=”https://zakscode.com/cloud.gif” />
|
<meta property="og:image" content="https://zakscode.com/cloud.png">
|
||||||
<meta property=”og:url” content=”https://zakscode.com” />
|
<meta property="og:description" content="DevOps & Software Engineer">
|
||||||
<meta property=”og:site_name” content=”ZaksCode” />
|
<meta name="description" content="DevOps & Software Engineer">
|
||||||
<!-- <meta name=”twitter:title” content=”Zakary Timson” />-->
|
<meta name="twitter:card" content="DevOps & Software Engineer">
|
||||||
<!-- <meta name=”twitter:description” 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';">-->
|
||||||
<!-- <meta name=”twitter:image” content=”https://zakscode.com/cloud.gif” />-->
|
|
||||||
|
<script id="environment"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
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",
|
"name": "zakscode",
|
||||||
"version": "2.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"start": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build-only": "vite build",
|
"build-only": "vite build",
|
||||||
"type-check": "vue-tsc --build --force"
|
"type-check": "vue-tsc --build --force"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@ztimson/momentum": "^0.50.3",
|
||||||
"vue": "^3.3.11",
|
"vue": "^3.3.11",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5"
|
||||||
},
|
},
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
"npm-run-all2": "^6.1.1",
|
"npm-run-all2": "^6.1.1",
|
||||||
"sass": "^1.69.7",
|
"sass": "^1.69.7",
|
||||||
"typescript": "~5.3.0",
|
"typescript": "~5.3.0",
|
||||||
"vite": "^5.0.10",
|
"vite": "5.4.6",
|
||||||
"vue-tsc": "^1.8.25"
|
"vue-tsc": "^1.8.25"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
BIN
public/logo.png
BIN
public/logo.png
Binary file not shown.
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 113 KiB |
@@ -10,7 +10,7 @@ import Profile from '@/components/profile.vue';
|
|||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<div class="cap-width mb-3 bg-white">
|
<div class="cap-width mb-3 bg-white">
|
||||||
<!-- Header -->
|
<!-- 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%)" />
|
<profile style="transform: translateY(-33%)" />
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -24,5 +24,5 @@ import Profile from '@/components/profile.vue';
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Spacer -->
|
<!-- 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>
|
</template>
|
||||||
|
23
src/components/card.vue
Normal file
23
src/components/card.vue
Normal 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>
|
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Icon from '@/components/icon.vue';
|
import Icon from '@/components/icon.vue';
|
||||||
|
import {momentum} from '@/services/momentum.service';
|
||||||
import {ref} from 'vue';
|
import {ref} from 'vue';
|
||||||
|
|
||||||
const disable = ref(false);
|
const disable = ref(false);
|
||||||
@@ -10,7 +11,7 @@ const data = ref({
|
|||||||
subject: '',
|
subject: '',
|
||||||
message: '',
|
message: '',
|
||||||
});
|
});
|
||||||
const errors = ref({
|
const errors = ref<any>({
|
||||||
banner: false,
|
banner: false,
|
||||||
name: false,
|
name: false,
|
||||||
email: 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);
|
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} <${email}><br><br>${message}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function submit() {
|
function submit() {
|
||||||
disable.value = true;
|
disable.value = true;
|
||||||
const d = data.value;
|
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;
|
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(() => {
|
send(d.name, d.email, d.subject, d.message).then(() => {
|
||||||
// done.value = true;
|
done.value = true;
|
||||||
// }).catch(err => {
|
}).catch(err => {
|
||||||
// console.error(err);
|
console.error(err);
|
||||||
// errors.value.banner = err?.message || err.toString();
|
errors.value.banner = err?.message || err.toString();
|
||||||
// disable.value = false;
|
disable.value = false;
|
||||||
// });
|
});
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- Success Banner -->
|
||||||
<div v-if="done" class="alert alert-success">
|
<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>
|
</div>
|
||||||
|
<!-- Error Banner -->
|
||||||
<div v-if="errors?.banner" class="alert alert-danger">
|
<div v-if="errors?.banner" class="alert alert-danger">
|
||||||
{{ errors.banner }}
|
Error: {{ (errors as any).banner }}
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Contact Form -->
|
||||||
<form>
|
<form>
|
||||||
<div class="d-flex flex-wrap justify-content-between mb-2">
|
<div class="d-flex flex-column flex-md-row flex-wrap justify-content-between">
|
||||||
<div class="input-group mb-2 mb-sm-0" style="width: 49%; min-width: 250px">
|
<!-- 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>
|
<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>
|
||||||
<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>
|
<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>
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Subject -->
|
||||||
<div class="input-group mb-2">
|
<div class="input-group mb-2">
|
||||||
<span class="input-group-text"><icon name="book"/></span>
|
<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>
|
<input class="form-control" type="text" placeholder="Subject" v-model="data.subject" v-bind:class="{'is-invalid': errors?.subject}" :disabled="disable" required>
|
||||||
</div>
|
</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>
|
<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">
|
<div class="text-end pt-3">
|
||||||
<button type="button" class="btn rounded-pill ms-3" @click="reset()">Reset</button>
|
<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>
|
<button type="button" class="btn btn-primary rounded-pill ms-3" @click="submit" :disabled="disable">Send Message</button>
|
||||||
|
@@ -4,7 +4,11 @@
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
footer {
|
footer {
|
||||||
background: #343a40;
|
background: #343a40;
|
||||||
color: darkgrey;
|
color: #A3A3A3;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #75B8FF;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@@ -1,251 +1,33 @@
|
|||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import {onMounted} from 'vue';
|
import '../modules/konsole';
|
||||||
|
import {onMounted, ref} from 'vue';
|
||||||
|
|
||||||
const hostname = 'virtual';
|
const animate = ref(true);
|
||||||
let history = [];
|
|
||||||
let historyIndex = 0;
|
|
||||||
let prompt;
|
|
||||||
let input;
|
|
||||||
let output;
|
|
||||||
|
|
||||||
function focus() {
|
function sleep(time: number) {
|
||||||
input.focus();
|
return new Promise(res => setTimeout(res, time));
|
||||||
}
|
}
|
||||||
|
|
||||||
function disable() {
|
async function showerThought(s=10000) {
|
||||||
input.disabled = true;
|
if(!animate.value) return;
|
||||||
prompt.style.visibility = 'hidden';
|
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() {
|
onMounted(async () => {
|
||||||
input.disabled = false;
|
(<any>window).cli.build('#konsole');
|
||||||
input.focus();
|
while(animate) {
|
||||||
prompt.style.visibility = 'visible';
|
if(!animate.value) break;
|
||||||
}
|
await sleep(3000);
|
||||||
|
if(!animate.value) break;
|
||||||
function banner() {
|
await showerThought();
|
||||||
stdOut(`Konsole 0.2.0 LTS virtual tty1<br><br>${hostname} login: root<br>password:<br><br>`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function process(command) {
|
|
||||||
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
|
|
||||||
const parts = c.split(' ').filter(c => !!c);
|
|
||||||
if(window.cli[parts[0]] == undefined || window.cli[parts[0]].run == undefined) {
|
|
||||||
stdErr(`${parts[0]}: command not found`);
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
const out = window.cli[parts[0]].run(parts.slice(1));
|
|
||||||
if(!!out) stdOut(out);
|
|
||||||
} catch(err) {
|
|
||||||
console.error(err)
|
|
||||||
stdErr(`${parts[0]}: exited with a non-zero status`);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function stdErr(text) {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.classList.add('console-output-line');
|
|
||||||
p.classList.add('console-output-error');
|
|
||||||
p.innerText = text;
|
|
||||||
output.appendChild(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stdOut(text, html=true) {
|
|
||||||
const p = document.createElement('p');
|
|
||||||
p.classList.add('console-output-line');
|
|
||||||
p[html ? 'innerHTML' : 'innerText'] = text;
|
|
||||||
output.appendChild(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
function stdIn(event) {
|
|
||||||
if(event.key == "Enter") {
|
|
||||||
disable();
|
|
||||||
let inputValue = input.value;
|
|
||||||
input.value = '';
|
|
||||||
stdOut(`root@localhost:~ # ${inputValue}`, false);
|
|
||||||
if(!!inputValue) {
|
|
||||||
history.push(inputValue);
|
|
||||||
historyIndex = history.length;
|
|
||||||
process(inputValue)
|
|
||||||
}
|
|
||||||
enable();
|
|
||||||
} else if(event.key == 'Up' || event.key == 'ArrowUp') {
|
|
||||||
if(historyIndex > 0) historyIndex--;
|
|
||||||
input.value = historyIndex == history.length ? '' : history[historyIndex];
|
|
||||||
setTimeout(() => {
|
|
||||||
const end = input.value.length;
|
|
||||||
input.setSelectionRange(end, end);
|
|
||||||
input.focus();
|
|
||||||
}, 1)
|
|
||||||
} else if(event.key == 'Down' || event.key == 'ArrowDown') {
|
|
||||||
if(historyIndex < history.length) historyIndex++;
|
|
||||||
input.value = historyIndex == history.length ? '' : history[historyIndex];
|
|
||||||
setTimeout(() => {
|
|
||||||
const end = input.value.length;
|
|
||||||
input.setSelectionRange(end, end);
|
|
||||||
input.focus();
|
|
||||||
}, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
prompt = document.getElementsByClassName('console-input-prompt')[0];
|
|
||||||
input = document.getElementsByClassName('console-input-field')[0];
|
|
||||||
output = document.getElementsByClassName('console-output')[0];
|
|
||||||
banner();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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>
|
</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>
|
<template>
|
||||||
<div class="console">
|
<div id="konsole" @click="animate = false"></div>
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
|
@@ -21,7 +21,7 @@ import Icon from '@/components/icon.vue';
|
|||||||
</h2>
|
</h2>
|
||||||
<ul class="m-0 p-0 text-start" style="list-style: none">
|
<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="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="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>
|
<li><icon name="git-alt" class="fa-brands me-1"/> <a href="https://git.zakscode.com" target="_blank">git.zakscode.com</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@@ -1,24 +1,22 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
export interface Project {
|
|
||||||
icon?: string;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
link: string;
|
|
||||||
source?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
projects: {type: Array as () => Project[], required: true}
|
projects: {type: Array as () => any[], required: true}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.invert {
|
||||||
|
filter: invert(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-for="(p, i) in projects">
|
<div v-for="(p, i) in projects">
|
||||||
<hr v-if="i != 0">
|
<div class="d-flex align-items-center border p-2" :class="i == 0 ? '' : 'border-top-0'">
|
||||||
<div class="d-flex align-items-center">
|
|
||||||
<div class="me-2">
|
<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>
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div>
|
<div>
|
||||||
|
@@ -1,33 +1,30 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
const resume = 'https://drive.google.com/file/d/1km4XqtXrYtoUnrDpEtDmAeKWh1qyp9nQ/view?usp=drive_link';
|
||||||
.btn-outline-info:hover {
|
|
||||||
color: #ffffff;
|
const refrences = [
|
||||||
}
|
['Andre Mourinho', 'https://drive.google.com/file/d/1QSRzBg6xj1evwIhpAkrKZqPrbJOjwkYR/view?usp=drive_link'],
|
||||||
</style>
|
['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>
|
<template>
|
||||||
<div class="d-block d-md-none">
|
<div class="d-block d-md-none">
|
||||||
<div class="mb-3">
|
<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>
|
||||||
<div class="btn-group-vertical w-100" role="group">
|
<div class="btn-group-vertical w-100" role="group">
|
||||||
<a class="btn btn-outline-info">CD Projekt Red</a>
|
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="d-none d-md-block">
|
<div class="d-none d-md-flex align-items-start">
|
||||||
<a class="btn btn-outline-primary me-3">CSV / Resume</a>
|
<a class="btn btn-outline-danger me-3" :href="resume" target="_blank">Resume</a>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a class="btn btn-outline-info">CD Projekt Red</a>
|
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</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>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
6
src/environments/environment.ts
Normal file
6
src/environments/environment.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const devMode = location?.port == '5173';
|
||||||
|
|
||||||
|
export const environment = {
|
||||||
|
apiUrl: devMode ? 'http://localhost' : location.origin,
|
||||||
|
devMode
|
||||||
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
::-webkit-scrollbar { width: 10px; }
|
::-webkit-scrollbar { width: 10px; }
|
||||||
::-webkit-scrollbar-track { background: #333; }
|
::-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; }
|
::-webkit-scrollbar-thumb:hover { background: #aaa; }
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
@@ -14,11 +14,11 @@ html, body {
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
font-family: Roboto,sans-serif;
|
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 {
|
a:not(.btn), a:not(.btn):visited {
|
||||||
color: #007bff;
|
color: #006FE6;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
|
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'
|
import Home from '@/views/Home.vue'
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
history: createWebHistory((<any>import.meta).env.BASE_URL),
|
||||||
routes: [
|
routes: [
|
||||||
{path: '/', name: 'home', component: Home}
|
{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();
|
||||||
|
});
|
@@ -1,96 +1,56 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import Card from '@/components/card.vue';
|
||||||
import Contact from '@/components/contact.vue';
|
import Contact from '@/components/contact.vue';
|
||||||
import Icon from '@/components/icon.vue';
|
|
||||||
import Konsole from '@/components/konsole.vue';
|
import Konsole from '@/components/konsole.vue';
|
||||||
import Projects from '@/components/projects.vue';
|
import Projects from '@/components/projects.vue';
|
||||||
import Refrences from '@/components/refrences.vue';
|
import Refrences from '@/components/refrences.vue';
|
||||||
|
import {momentum} from '@/services/momentum.service';
|
||||||
|
import {ref} from 'vue';
|
||||||
|
|
||||||
const services: Projects[] = [
|
// Get favorites
|
||||||
{name: 'Formula Manager', icon: 'https://git.zakscode.com/avatars/7ec6bfd66b2bf9bad5c43c75a33f9cb3f6609b05c33a31f5d1e524a567cd09c1?size=280', link: 'https://screenprintingsuppliescanada.com/formulation-manager', description: 'Screen Printing Supplies'},
|
const products = ref<any[]>([]);
|
||||||
{name: 'Map Alliance', icon: 'https://maps.zakscode.com/assets/images/logo.png', link: 'https://maps.zakscode.com', description: 'Online GIS & map editing solution'},
|
const openSource = ref<any[]>([]);
|
||||||
{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'},
|
momentum.data.read('Repos').then(resp =>
|
||||||
];
|
resp.forEach((r: any) => (r.product ? products : openSource).value.push(r)));
|
||||||
|
|
||||||
const openSource: Projects[] = [
|
// Get repository count
|
||||||
{name: 'Formula Manager', icon: 'https://git.zakscode.com/avatars/7ec6bfd66b2bf9bad5c43c75a33f9cb3f6609b05c33a31f5d1e524a567cd09c1?size=280', link: 'https://screenprintingsuppliescanada.com/formulation-manager', description: 'Screen Printing Supplies'},
|
let remainder = ref(0);
|
||||||
{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'},
|
fetch('https://git.zakscode.com/api/v1/repos/search?limit=1000', {
|
||||||
{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'},
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-3">
|
<div class="p-3">
|
||||||
<konsole class="mb-5" style="max-height: 300px" />
|
<!-- Terminal -->
|
||||||
|
<konsole class="mb-5" />
|
||||||
|
|
||||||
|
<!-- Steps -->
|
||||||
<div class="mb-5 pt-5">
|
<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">
|
<hr class="mb-4">
|
||||||
<div class="text-center my-5">
|
<div class="text-center my-5">
|
||||||
<img src="/cycle.svg" alt="Development Cycle" style="width: 100%; max-width: 600px; height: auto;">
|
<img src="/cycle.svg" alt="Development Cycle" style="width: 100%; max-width: 600px; height: auto;">
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex flex-wrap justify-content-around">
|
||||||
<div class="d-flex flex-column align-items-center border m-3 p-3 text-center" style="flex: 1 0 0;">
|
<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."/>
|
||||||
<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">
|
<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."/>
|
||||||
<icon name="clipboard"/>
|
<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."/>
|
||||||
</div>
|
<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."/>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- About Section -->
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<h3 class="mb-0">About</h3>
|
<h3 class="mb-0">About</h3>
|
||||||
<hr class="mb-4">
|
<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%;">
|
<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>
|
<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>
|
||||||
Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for
|
<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>
|
||||||
computers since he was born & taught him self to code at the age of 13. Since then, he has gone to school
|
|
||||||
for computer science & has worked for both small businesses and large corporations as a developer and team lead.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Zak specializes in full-stack web development & server infrastructure, and primarily works on large enterprise
|
|
||||||
grade "Software as a service" (SaaS) products. As a software architect & team lead he is able to work with
|
|
||||||
business's to create a road map of their needs, build enterprise grade software solutions that meet those
|
|
||||||
needs & work with clients to host & deliver automatic updates at scale using continuous integration.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<h4 class="mb-3 text-muted">CSV & References</h4>
|
<h4 class="mb-3 text-muted">CSV & References</h4>
|
||||||
@@ -98,19 +58,22 @@ const openSource: Projects[] = [
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Projects List -->
|
||||||
<div class="mb-5">
|
<div class="mb-5">
|
||||||
<h3 class="m-0">Projects</h3>
|
<h3 class="m-0">Projects</h3>
|
||||||
<hr class="mb-4">
|
<hr class="mb-4">
|
||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h4 class="mb-3 text-muted">Products & Services</h4>
|
<h4 class="mb-3 text-muted">Products & Services</h4>
|
||||||
<projects :projects="services"/>
|
<projects :projects="products"/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h4 class="mb-3 text-muted">Open Source</h4>
|
<h4 class="mb-3 text-muted">Open Source</h4>
|
||||||
<projects :projects="openSource"/>
|
<projects :projects="openSource"/>
|
||||||
</div>
|
</div>
|
||||||
|
<a v-if="remainder" class="float-end m-2" href="https://git.zakscode.com/explore" target="_blank">See {{remainder}} More...</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Contact Form -->
|
||||||
<div>
|
<div>
|
||||||
<h3 class="m-0">Contact</h3>
|
<h3 class="m-0">Contact</h3>
|
||||||
<hr class="mb-4">
|
<hr class="mb-4">
|
||||||
|
@@ -9,9 +9,11 @@
|
|||||||
"src/**/__tests__/*"
|
"src/**/__tests__/*"
|
||||||
],
|
],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"composite": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
"composite": true,
|
||||||
|
"lib": ["DOM", "ESNext"],
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"noEmit": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": [
|
"@/*": [
|
||||||
"./src/*"
|
"./src/*"
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import vue from '@vitejs/plugin-vue';
|
|
||||||
import {fileURLToPath, URL} from 'node:url';
|
|
||||||
|
|
||||||
import {defineConfig} from 'vite';
|
import {defineConfig} from 'vite';
|
||||||
|
import {fileURLToPath, URL} from 'node:url';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -12,5 +11,6 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
envPrefix: 'APP',
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user