Compare commits
37 Commits
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 | |||
4b3e89fa59 | |||
0e277a1478 |
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}}
|
74
.gitignore
vendored
@ -1,48 +1,30 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
# Only exists if Bazel was run
|
||||
/bazel-out
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
node_modules
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
/package-lock.json
|
||||
/.angular/
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
@ -1,79 +0,0 @@
|
||||
image: node:16
|
||||
|
||||
npm:
|
||||
stage: build
|
||||
artifacts:
|
||||
paths:
|
||||
- dist
|
||||
expire_in: 1 week
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- package.json
|
||||
paths:
|
||||
- node_modules
|
||||
- package-lock.json
|
||||
policy: pull-push
|
||||
- key: $CI_PIPELINE_ID
|
||||
paths:
|
||||
- dist
|
||||
policy: push
|
||||
script:
|
||||
- npm i
|
||||
- npm run build
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
audit:
|
||||
stage: test
|
||||
cache:
|
||||
- key:
|
||||
files:
|
||||
- package.json
|
||||
paths:
|
||||
- node_modules
|
||||
policy: pull
|
||||
script:
|
||||
- AUDIT=$(npm audit)
|
||||
- echo "vulnerabilities_high $(echo $AUDIT | grep -oE '[0-9]+ high' | grep -oE '[0-9]+' || echo 0)" > metrics.txt
|
||||
- echo "vulnerabilities_medium $(echo $AUDIT | grep -oE '[0-9]+ moderate' | grep -oE '[0-9]+' || echo 0)" >> metrics.txt
|
||||
- echo "vulnerabilities_low $(echo $AUDIT | grep -oE '[0-9]+ low' | grep -oE '[0-9]+' || echo 0)" >> metrics.txt
|
||||
- echo "$AUDIT"
|
||||
artifacts:
|
||||
reports:
|
||||
metrics: metrics.txt
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
registry:
|
||||
stage: deploy
|
||||
image: docker
|
||||
cache:
|
||||
- key: $CI_PIPELINE_ID
|
||||
paths:
|
||||
- dist
|
||||
policy: pull
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_JOB_TOKEN $CI_REGISTRY
|
||||
script:
|
||||
- TAG=$([ "$CI_COMMIT_BRANCH" = "$CI_DEFAULT_BRANCH" ] && echo "latest" || echo "$CI_COMMIT_BRANCH" | sed -E "s/[_/]/-/g")
|
||||
- docker build --no-cache -t "$CI_REGISTRY_IMAGE:$TAG" .
|
||||
- docker push "$CI_REGISTRY_IMAGE:$TAG"
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
tag:
|
||||
stage: deploy
|
||||
image:
|
||||
name: alpine/git
|
||||
entrypoint: [ "" ]
|
||||
cache: [ ]
|
||||
before_script:
|
||||
- git remote set-url origin "https://ReleaseBot:$DEPLOY_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git"
|
||||
script:
|
||||
- VERSION=$(cat package.json | grep version | grep -Eo ':.+' | grep -Eo '[[:alnum:]\.\/\-]+')
|
||||
- git tag -f $VERSION $CI_COMMIT_SHA
|
||||
- git push -f origin $VERSION
|
||||
rules:
|
||||
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
||||
allow_failure: true
|
20
Dockerfile
@ -1,25 +1,21 @@
|
||||
FROM node:16 as build
|
||||
FROM node:20-alpine as build
|
||||
|
||||
# Variables
|
||||
ARG NODE_ENV=prod
|
||||
ARG NODE_OPTIONS=""
|
||||
ARG NODE_OPTIONS="--max_old_space_size=4096"
|
||||
ENV NG_CLI_ANALYTICS=ci \
|
||||
NODE_ENV=${NODE_ENV} \
|
||||
NODE_OPTIONS=${NODE_OPTIONS}
|
||||
|
||||
# Setup
|
||||
RUN npm config set unsafe-perm true && \
|
||||
mkdir /app
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
# Install & build
|
||||
RUN if [ ! -d "dist" ] && [ ! -d "node_modules" ]; then npm install; fi
|
||||
RUN if [ ! -d "dist" ]; then 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/zakscode /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
@ -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
@ -1,18 +1,93 @@
|
||||
# ZaksCode
|
||||
Business website for ZaksCode. Provides some background information, contact inforation & a portfolio of projects.
|
||||
[View here](https://zakscode.com).
|
||||
<!-- Header -->
|
||||
<div id="top" align="center">
|
||||
<br />
|
||||
|
||||
Built using [Angular 13](https://angular.io).
|
||||
<!-- Logo -->
|
||||
<img src="./public/logo.png" alt="Logo" width="200" height="200">
|
||||
|
||||
<!-- Title -->
|
||||
### ZaksCode
|
||||
|
||||
<!-- Description -->
|
||||
Source Code for ZaksCode Website
|
||||
|
||||
<!-- Repo badges -->
|
||||
[](https://git.zakscode.com/ztimson/zakscode/tags)
|
||||
[](https://git.zakscode.com/ztimson/zakscode/pulls)
|
||||
[](https://git.zakscode.com/ztimson/zakscode/issues)
|
||||
|
||||
<!-- Links -->
|
||||
|
||||
---
|
||||
<div>
|
||||
<a href="https://git.zakscode.com/ztimson/zakscode/releases" target="_blank">Release Notes</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/zakscode/issues/new?template=.github%2fissue_template%2fbug.md" target="_blank">Report a Bug</a>
|
||||
• <a href="https://git.zakscode.com/ztimson/zakscode/issues/new?template=.github%2fissue_template%2fenhancement.md" target="_blank">Request a Feature</a>
|
||||
</div>
|
||||
|
||||
---
|
||||
</div>
|
||||
|
||||
## Table of Contents
|
||||
[[_TOC_]]
|
||||
- [ZaksCode](#top)
|
||||
- [About](#about)
|
||||
- [Demo](#demo)
|
||||
- [Built With](#built-with)
|
||||
- [Setup](#setup)
|
||||
- [Production](#production)
|
||||
- [Development](#development)
|
||||
- [License](#license)
|
||||
|
||||
## Cheatsheet
|
||||
```bash
|
||||
# Start Angular development server
|
||||
npm run start
|
||||
# Build production site
|
||||
npm run build
|
||||
# Build docker image
|
||||
docker build -t gitlab.zakscode.com:5050/zakcode/zakscode:latest .
|
||||
```
|
||||
## About
|
||||
|
||||
This the source code for `zakscode.com`.
|
||||
|
||||
ZaksCode is the personal website of Zakary Timson & is used as his business page & portfolio.
|
||||
### Demo
|
||||
|
||||
Website: https://zakscode.com
|
||||
|
||||
### Built With
|
||||
[](https://vuejs.org/)
|
||||
[](https://getbootstrap.com)
|
||||
[](https://docker.com/)
|
||||
[](https://typescriptlang.org/)
|
||||
|
||||
## Setup
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="production" style="display: inline">
|
||||
Production
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Docker](https://docs.docker.com/install/)
|
||||
|
||||
#### Instructions
|
||||
1. Run the docker image: `docker run -p 80:80 git.zakscode.com/ztimson/zakscode:latest`
|
||||
2. Open [http://localhost](http://localhost)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>
|
||||
<h3 id="development" style="display: inline">
|
||||
Development
|
||||
</h3>
|
||||
</summary>
|
||||
|
||||
#### Prerequisites
|
||||
- [Node.js](https://nodejs.org/en/download)
|
||||
|
||||
#### Instructions
|
||||
1. Install the dependencies: `npm install`
|
||||
2. Start the dev server: `npm run start`
|
||||
3. Open [http://localhost:4200](http://localhost:5173)
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
Copyright © 2023 Zakary Timson | All Rights Reserved
|
||||
|
||||
See the [license](./LICENSE) for more information.
|
||||
|
111
angular.json
@ -1,111 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"zakscode": {
|
||||
"projectType": "application",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"style": "scss"
|
||||
},
|
||||
"@schematics/angular:application": {
|
||||
"strict": true
|
||||
}
|
||||
},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/zakscode",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kb",
|
||||
"maximumError": "1mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
],
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"buildOptimizer": false,
|
||||
"optimization": false,
|
||||
"vendorChunk": true,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"namedChunks": true
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "zakscode:build:production"
|
||||
},
|
||||
"development": {
|
||||
"browserTarget": "zakscode:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "zakscode:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"karmaConfig": "karma.conf.js",
|
||||
"inlineStyleLanguage": "scss",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "zakscode"
|
||||
}
|
@ -2,38 +2,30 @@ worker_processes auto;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
gzip on;
|
||||
gzip_proxied any;
|
||||
gzip_types text/plain text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript application/json application/x-font-woff;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
|
||||
gzip on;
|
||||
gzip_proxied any;
|
||||
gzip_types text/plain text/css application/xml application/xhtml+xml application/rss+xml application/javascript application/x-javascript application/json application/x-font-woff;
|
||||
gzip_vary on;
|
||||
gzip_disable "MSIE [1-6]\.(?!.*SV1)";
|
||||
|
||||
sendfile off;
|
||||
keepalive_timeout 65;
|
||||
sendfile off;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
index index.html;
|
||||
root /usr/share/nginx/html;
|
||||
autoindex off;
|
||||
server {
|
||||
listen 80;
|
||||
index index.html;
|
||||
root /usr/share/nginx/html;
|
||||
autoindex off;
|
||||
|
||||
location / {
|
||||
try_files $uri$args $uri$args/ /index.html;
|
||||
}
|
||||
|
||||
location ~ \.css {
|
||||
add_header Content-Type text/css;
|
||||
}
|
||||
|
||||
location ~ \.js {
|
||||
add_header Content-Type application/x-javascript;
|
||||
}
|
||||
}
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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 "$@"
|
26
index.html
Normal file
@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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 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>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,44 +0,0 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/1.0/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular-devkit/build-angular'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage'),
|
||||
require('@angular-devkit/build-angular/plugins/karma')
|
||||
],
|
||||
client: {
|
||||
jasmine: {
|
||||
// you can add configuration options for Jasmine here
|
||||
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
|
||||
// for example, you can disable the random execution with `random: false`
|
||||
// or set a specific seed with `seed: 4321`
|
||||
},
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
jasmineHtmlReporter: {
|
||||
suppressAll: true // removes the duplicated traces
|
||||
},
|
||||
coverageReporter: {
|
||||
dir: require('path').join(__dirname, './coverage/zakscode'),
|
||||
subdir: '.',
|
||||
reporters: [
|
||||
{ type: 'html' },
|
||||
{ type: 'text-summary' }
|
||||
]
|
||||
},
|
||||
reporters: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false,
|
||||
restartOnFileChange: true
|
||||
});
|
||||
};
|
1970
package-lock.json
generated
Normal file
69
package.json
@ -1,41 +1,30 @@
|
||||
{
|
||||
"name": "zakscode",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"watch": "ng build --watch --configuration development",
|
||||
"test": "ng test"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~13.3.0",
|
||||
"@angular/cdk": "^13.3.5",
|
||||
"@angular/common": "~13.3.0",
|
||||
"@angular/compiler": "~13.3.0",
|
||||
"@angular/core": "~13.3.0",
|
||||
"@angular/forms": "~13.3.0",
|
||||
"@angular/material": "^13.3.5",
|
||||
"@angular/platform-browser": "~13.3.0",
|
||||
"@angular/platform-browser-dynamic": "~13.3.0",
|
||||
"@angular/router": "~13.3.0",
|
||||
"rxjs": "~7.5.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~13.3.4",
|
||||
"@angular/cli": "~13.3.4",
|
||||
"@angular/compiler-cli": "~13.3.0",
|
||||
"@types/jasmine": "~3.10.0",
|
||||
"@types/node": "^12.11.1",
|
||||
"jasmine-core": "~4.0.0",
|
||||
"karma": "~6.3.0",
|
||||
"karma-chrome-launcher": "~3.1.0",
|
||||
"karma-coverage": "~2.1.0",
|
||||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "~1.7.0",
|
||||
"typescript": "~4.6.2"
|
||||
}
|
||||
}
|
||||
"name": "zakscode",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node18": "^18.2.2",
|
||||
"@types/node": "^18.19.3",
|
||||
"@vitejs/plugin-vue": "^4.5.2",
|
||||
"@vue/tsconfig": "^0.5.0",
|
||||
"bootstrap": "^5.3.2",
|
||||
"npm-run-all2": "^6.1.1",
|
||||
"sass": "^1.69.7",
|
||||
"typescript": "~5.3.0",
|
||||
"vite": "5.4.6",
|
||||
"vue-tsc": "^1.8.25"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
1
public/cycle.svg
Normal file
After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
BIN
public/logo.png
Normal file
After Width: | Height: | Size: 113 KiB |
BIN
public/logo.psd
Normal file
BIN
public/profile.jpg
Normal file
After Width: | Height: | Size: 198 KiB |
28
src/App.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import Foot from '@/components/foot.vue';
|
||||
import Profile from '@/components/profile.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Spacer -->
|
||||
<div class="w-100" style="height: min(75vh, 500px)"></div>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="cap-width mb-3 bg-white">
|
||||
<!-- Header -->
|
||||
<header class="px-4 d-flex justify-content-center justify-content-sm-start" style="background: #732222">
|
||||
<profile style="transform: translateY(-33%)" />
|
||||
</header>
|
||||
|
||||
<!-- Body -->
|
||||
<main class="p-3">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<foot />
|
||||
</div>
|
||||
|
||||
<!-- Spacer -->
|
||||
<div class="d-none d-lg-block w-100" style="height: 40px"></div>
|
||||
</template>
|
@ -1,36 +0,0 @@
|
||||
import {HttpClientModule} from '@angular/common/http';
|
||||
import {BrowserModule} from '@angular/platform-browser';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {ContactFormComponent} from './components/contact-form/contact-form.component';
|
||||
import {ProjectsComponent} from './components/projects/projects.component';
|
||||
import {HomeComponent} from './views/home/home.component';
|
||||
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
|
||||
import {MaterialModule} from './material.module';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {TypewriterComponent} from './components/typewriter/typewriter.component';
|
||||
import {SlideShowComponent} from './components/slideShow/slideShow.component';
|
||||
import {AppComponent} from './views/app/app.component';
|
||||
import {AppRouting} from './app.routing';
|
||||
import {ConsoleComponent} from './components/console/console.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
ConsoleComponent,
|
||||
ContactFormComponent,
|
||||
HomeComponent,
|
||||
ProjectsComponent,
|
||||
SlideShowComponent,
|
||||
TypewriterComponent
|
||||
],
|
||||
imports: [
|
||||
AppRouting,
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
MaterialModule,
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
@ -1,14 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {HomeComponent} from './views/home/home.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forRoot([
|
||||
{path: '', component: HomeComponent},
|
||||
{path: '**', redirectTo: ''}
|
||||
])
|
||||
],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRouting {}
|
@ -1,10 +0,0 @@
|
||||
<div class="p-3 d-flex flex-column justify-content-end console overflow-hidden" [style.height]="height">
|
||||
<div>
|
||||
<h4 class="m-0" *ngFor="let o of output">{{o}}</h4>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<h4 class="m-0">
|
||||
{{prompt}} <typewriter class="pl-2" [text]="input" (done)="done()"></typewriter>
|
||||
</h4>
|
||||
</div>
|
||||
</div>
|
@ -1,5 +0,0 @@
|
||||
.console {
|
||||
background-color: #333;
|
||||
color: #00ff00;
|
||||
font-family: monospace !important;
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
import {Component, Input, ViewChild} from '@angular/core';
|
||||
import {sleep} from '../../misc/utils';
|
||||
import {TypewriterComponent} from '../typewriter/typewriter.component';
|
||||
|
||||
@Component({
|
||||
selector: 'console',
|
||||
templateUrl: './console.component.html',
|
||||
styleUrls: ['./console.component.scss']
|
||||
})
|
||||
export class ConsoleComponent {
|
||||
done = () => {};
|
||||
input = '';
|
||||
output: string[] = [];
|
||||
prompt = '>'
|
||||
|
||||
@Input() height: string = 'auto';
|
||||
|
||||
@ViewChild(TypewriterComponent) typewriter!: TypewriterComponent;
|
||||
|
||||
clear() { this.output = []; }
|
||||
|
||||
exec(input: string, output: () => any, pause = 1000) {
|
||||
return new Promise<void>(res => {
|
||||
this.done = async () => {
|
||||
await sleep(pause);
|
||||
this.input = '';
|
||||
this.output.push(`${this.prompt} ${input}`);
|
||||
const out = output();
|
||||
if(typeof out == 'string') this.output.push(out);
|
||||
res();
|
||||
};
|
||||
this.input = input;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
<form>
|
||||
<div *ngIf="success" class="alert alert-success">Email sent!</div>
|
||||
<div *ngIf="error" class="alert alert-danger">Email failed to send</div>
|
||||
<div class="form-group">
|
||||
<label for="emailInput">Email</label>
|
||||
<input type="email" class="form-control" id="emailInput" name="email" placeholder="username@example.com" [(ngModel)]="email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="subjectInput">Subject</label>
|
||||
<input type="text" class="form-control" id="subjectInput" name="subject" placeholder="Interested in Services" [(ngModel)]="subject">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="messageInput">Message</label>
|
||||
<textarea class="form-control" id="messageInput" name="message" rows="5" [(ngModel)]="message"></textarea>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary float-right" (click)="send()" [disabled]="loading || !email || !subject || !message">
|
||||
Send
|
||||
</button>
|
||||
</form>
|
@ -1,44 +0,0 @@
|
||||
import {ChangeDetectorRef, Component} from '@angular/core';
|
||||
import {EmailService} from '../../services/email.service';
|
||||
|
||||
@Component({
|
||||
selector: 'contact-form',
|
||||
templateUrl: './contact-form.component.html'
|
||||
})
|
||||
export class ContactFormComponent {
|
||||
email = '';
|
||||
error = false;
|
||||
loading = false;
|
||||
message = '';
|
||||
subject = '';
|
||||
success = false;
|
||||
|
||||
constructor(private changeRef: ChangeDetectorRef, private emailService: EmailService) { }
|
||||
|
||||
async send() {
|
||||
this.error = false;
|
||||
this.success = false;
|
||||
if(this.loading || !this.email || !this.subject || !this.message) return;
|
||||
this.loading = true;
|
||||
this.emailService.send(`ZaksCode: ${this.subject}`, `From: ${this.email}\n\n${this.message}`
|
||||
).then(() => {
|
||||
this.email = '';
|
||||
this.message = '';
|
||||
this.success = true;
|
||||
this.subject = '';
|
||||
}).catch(err => {
|
||||
// Postmail seems to always return an error message
|
||||
if(200 <= err.status && err.status < 300) {
|
||||
this.email = '';
|
||||
this.message = '';
|
||||
this.success = true;
|
||||
this.subject = '';
|
||||
} else {
|
||||
this.error = true;
|
||||
}
|
||||
}).finally(() => {
|
||||
this.loading = false;
|
||||
this.changeRef.detectChanges();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<mat-list dense>
|
||||
<ng-container *ngFor="let p of projectsService.all; let first = first">
|
||||
<mat-divider *ngIf="!first"></mat-divider>
|
||||
<mat-list-item>
|
||||
<img *ngIf="p.avatar_url" mat-list-avatar [src]="p.avatar_url" alt="Project icon">
|
||||
<img *ngIf="!p.avatar_url" mat-list-avatar src="/assets/img/git.png" alt="Project icon">
|
||||
<a mat-line [href]="p.web_url" target="_blank">{{p.name}}</a>
|
||||
<div mat-line>{{p.description}}</div>
|
||||
</mat-list-item>
|
||||
</ng-container>
|
||||
</mat-list>
|
@ -1,10 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {ProjectsService} from '../../services/projects.service';
|
||||
|
||||
@Component({
|
||||
selector: 'projects',
|
||||
templateUrl: './projects.component.html'
|
||||
})
|
||||
export class ProjectsComponent {
|
||||
constructor(public projectsService: ProjectsService) { }
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<div id="carouselIndicators" class="carousel slide h-100" data-ride="carousel">
|
||||
<ol class="carousel-indicators">
|
||||
<li *ngFor="let ignore of slides; let i = index" data-target="#carouselIndicators" [attr.data-slide-to]="i"></li>
|
||||
</ol>
|
||||
<div class="carousel-inner">
|
||||
<div *ngFor="let slide of slides" class="carousel-item">
|
||||
<img class="d-block w-100" [src]="slide.image" alt="project.name">
|
||||
<div class="carousel-caption d-none d-md-block">
|
||||
<h5>{{slide.title}}</h5>
|
||||
<p>{{slide.description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a class="carousel-control-prev" href="#carouselIndicators" role="button" data-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
<a class="carousel-control-next" href="#carouselIndicators" role="button" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="true"></span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</div>
|
@ -1,15 +0,0 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
export type Slide = {
|
||||
title: string;
|
||||
description: string;
|
||||
image: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'slideshow',
|
||||
templateUrl: 'slideShow.component.html'
|
||||
})
|
||||
export class SlideShowComponent {
|
||||
@Input() slides: Slide[] = [];
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
.typewriter {
|
||||
& {
|
||||
overflow: hidden;
|
||||
margin: 0 auto;
|
||||
letter-spacing: .15em;
|
||||
animation: blink-caret 1s step-end infinite;
|
||||
}
|
||||
|
||||
@keyframes typing {
|
||||
from { width: 0 }
|
||||
to { width: 100% }
|
||||
}
|
||||
|
||||
@keyframes blink-caret {
|
||||
from, to { border-right: .15em solid; }
|
||||
50% { border: none; }
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {Observable, of, timer} from 'rxjs';
|
||||
import {filter, map, tap} from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'typewriter',
|
||||
template: `<span class="typewriter">{{output | async}}</span>`,
|
||||
styleUrls: ['typewriter.component.scss']
|
||||
})
|
||||
export class TypewriterComponent {
|
||||
output?: Observable<string>;
|
||||
|
||||
@Input() delay = 1500;
|
||||
@Input() speed = 100;
|
||||
@Input() set text(text: string) {
|
||||
if(!text) {
|
||||
this.output = of('');
|
||||
} else {
|
||||
this.output = timer(this.delay, this.speed).pipe(
|
||||
filter(n => n <= (text.length || 0)),
|
||||
tap(n => {
|
||||
if(n == text.length) this.done.emit();
|
||||
}),
|
||||
map(n => text.slice(0, n))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Output() done = new EventEmitter<void>();
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {MatButtonModule} from '@angular/material/button';
|
||||
import {MatCardModule} from '@angular/material/card';
|
||||
import {MatDividerModule} from '@angular/material/divider';
|
||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||
import {MatInputModule} from '@angular/material/input';
|
||||
import {MatListModule} from '@angular/material/list';
|
||||
|
||||
const MODULES = [
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatDividerModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
MatListModule,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [MODULES],
|
||||
exports: [MODULES]
|
||||
})
|
||||
export class MaterialModule {}
|
@ -1,30 +0,0 @@
|
||||
/**
|
||||
* Convert data into a form encoded format.
|
||||
*
|
||||
* @param {any} data - data to convert
|
||||
* @returns {string} - Ecodeded form data
|
||||
*/
|
||||
export function formEncode(data: any): string {
|
||||
return Object.entries(data).map(([key, value]) =>
|
||||
encodeURIComponent(key) + '=' + encodeURIComponent(<any>value)
|
||||
).join('&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Use with await to pause the script for a specified amount of time (in miliseconds).
|
||||
*
|
||||
* **Example:**
|
||||
* ```
|
||||
* async () => {
|
||||
* ...
|
||||
* await sleep(1000) // Wait 1 second
|
||||
* ...
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @param {number} ms - Time to pause in miliseconds
|
||||
* @returns {Promise<unknown>} - Promise you should await
|
||||
*/
|
||||
export function sleep(ms: number) {
|
||||
return new Promise(res => setTimeout(res, ms));
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Injectable} from '@angular/core';
|
||||
import {formEncode} from '../misc/utils';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class EmailService {
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
/**
|
||||
* Send an email to website admin.
|
||||
*
|
||||
* @param {string} subject - Email subject line
|
||||
* @param {string} message - Email body
|
||||
* @returns {Promise<Object | undefined>} - Response from Postmail API
|
||||
*/
|
||||
send(subject: string, message: string) {
|
||||
return this.http.post('https://postmail.invotes.com/send', formEncode({
|
||||
access_token: 's7uhce84sx6fayy5xlq0nrtx',
|
||||
subject: subject,
|
||||
text: message
|
||||
}), {
|
||||
headers: {'Content-Type': 'application/x-www-form-urlencoded'}
|
||||
}).toPromise();
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
export type Project = {
|
||||
avatar_url: string;
|
||||
created_at: string;
|
||||
default_branch: string;
|
||||
description: string;
|
||||
forks_count: number;
|
||||
http_url_to_repo: string;
|
||||
id: number;
|
||||
last_activity_at: string;
|
||||
name: string;
|
||||
name_with_namespace: string;
|
||||
namespace: {
|
||||
full_path: string;
|
||||
id: number;
|
||||
kind: string;
|
||||
name: string;
|
||||
parent_id: number;
|
||||
path: string;
|
||||
web_url: string;
|
||||
};
|
||||
path: string;
|
||||
path_with_namespace: string;
|
||||
readme_url: string;
|
||||
ssh_url_to_repo: string;
|
||||
star_count: number;
|
||||
tag_list: string[];
|
||||
topics: string[];
|
||||
web_url: string;
|
||||
}
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class ProjectsService {
|
||||
all: Project[] = [];
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
this.http.get<Project[]>('https://gitlab.zakscode.com/api/v4/projects').toPromise().then(projects => {
|
||||
this.all = (projects || []).sort((a, b) => {
|
||||
if(a.name > b.name) return 1;
|
||||
if(a.name < b.name) return -1;
|
||||
return 0;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
|
||||
@Injectable({providedIn: 'root'})
|
||||
export class QuoteService {
|
||||
readonly quotes = [
|
||||
'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!"',
|
||||
'Anxiety is like when video game combat music is playing but you can\'t find the enemy',
|
||||
'Why do kamikaze pilots wear helmets?',
|
||||
'The cake is a lie!',
|
||||
'How are unicorns fake but giraffes real?',
|
||||
'The number of people older than you never goes up',
|
||||
'When you brush your teeth you are cleaning your skeleton',
|
||||
'Pregenancy 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'
|
||||
];
|
||||
|
||||
/**
|
||||
* Return random quote
|
||||
*
|
||||
* @returns {string} - Quote
|
||||
*/
|
||||
random() {
|
||||
return this.quotes[Math.round(Math.random() * (this.quotes.length - 1))];
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
<router-outlet></router-outlet>
|
@ -1,17 +0,0 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {Title} from '@angular/platform-browser';
|
||||
import {ActivatedRoute} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: 'app.component.html'
|
||||
})
|
||||
export class AppComponent {
|
||||
constructor(private route: ActivatedRoute, private title: Title) {
|
||||
this.route.url.subscribe(() => this.setTitle(''));
|
||||
}
|
||||
|
||||
setTitle(title: string) {
|
||||
this.title.setTitle(`Zak's Code${title ? ` - ${title}` : ''}`);
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
<div class="w-100">
|
||||
<div class="w-100" style="height: calc(100vh - 140px)"></div>
|
||||
<div class="container p-0 bg-white">
|
||||
<div class="px-3" style="background-color: #732222">
|
||||
<mat-card style="max-width: 600px; transform: translateY(-33%)">
|
||||
<mat-card-content class="d-flex p-3">
|
||||
<div class="d-none d-md-block pr-3">
|
||||
<img src="assets/img/portrait.jpg" width="150px" height="150px" style="border-radius: 50%" alt="Zakary Timson">
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="mb-0">Zakary Timson</h1>
|
||||
<h5 class="text-muted">DEVOPS & SOFTWARE ENGINEER</h5>
|
||||
<div class="mt-3">
|
||||
<div><i class="mr-2 fa fa-map-marker-alt"></i> London Ontario, Canada</div>
|
||||
<div><i class="mr-2 fa fa-envelope"></i> <a href="mailto:zaktimson@gmail.com">zaktimson@gmail.com</a></div>
|
||||
<div><i class="mr-2 fa-brands fa-gitlab"></i> <a href="https://gitlab.zakscode.com/explore/projects" target="_blank">gitlab.zakscode.com</a></div>
|
||||
<div><i class="mr-2 fa-brands fa-github"></i> <a href="https://github.com/ztimson" target="_blank">github.com/ztimson</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<console height="12rem"></console>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h3>About Me</h3>
|
||||
<div>
|
||||
<img alt="Childhood" class="float-right m-3 m-md-0 ml-md-3" src="assets/img/keyboard-in-hand.jpg" height="150px" width="auto" style="border-radius: 50%">
|
||||
<p>
|
||||
Zak was born with a keyboard in hand and was learning his first programming language by thirteen. Nearly
|
||||
entirely self taught, Zak challenged his programming courses through both high-school and college while
|
||||
working in the industry to gain professional experience. He is very passionate about technology and as a
|
||||
lifelong learner it has opened the door to many other hobbies like robotics, space and physics. Some of his
|
||||
personal projects include a full sized arcade machine, home automation and a power wall for a home solar
|
||||
system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 overflow-hidden">
|
||||
<h3>Resume & References</h3>
|
||||
<div class="d-md-none">
|
||||
<ul class="list-group">
|
||||
<a class="list-group-item list-group-item-action border-primary text-primary" href="https://docs.google.com/document/d/1xP6HASPerXKMJM_x6-PhHVvoYgq-Hym5IRO7g47EX8o/edit?usp=sharing" target="_blank">Resume</a>
|
||||
</ul>
|
||||
<ul class="list-group mt-3">
|
||||
<a class="list-group-item list-group-item-action border-info text-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyNWw0UDFzT0ZTeVU/view?usp=sharing" target="_blank">Manager</a>
|
||||
<a class="list-group-item list-group-item-action border-info text-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyaFBhcXBEaGp6YWc/view?usp=sharing" target="_blank">Contractor</a>
|
||||
<a class="list-group-item list-group-item-action border-info text-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyM0YtTWcxQzk0dEE/view?usp=sharing" target="_blank">Teacher</a>
|
||||
<a class="list-group-item list-group-item-action border-info text-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyX2owd0xURjh3RlE/view?usp=sharing" target="_blank">Principle</a>
|
||||
<a class="list-group-item list-group-item-action border-info text-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyMHdaM1BjZ1MwbWxva2lOY290NElwanN4b2JV/view?usp=sharing" target="_blank">CD Projekt Red</a>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="d-none d-md-inline-block pt-1">
|
||||
<a class="btn btn-outline-primary" href="https://docs.google.com/document/d/1xP6HASPerXKMJM_x6-PhHVvoYgq-Hym5IRO7g47EX8o/edit?usp=sharing" target="_blank">Resume</a>
|
||||
<div class="ml-3 btn-group" role="group" aria-label="Basic example">
|
||||
<a class="btn btn-outline-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyNWw0UDFzT0ZTeVU/view?usp=sharing" target="_blank">Manager</a>
|
||||
<a class="btn btn-outline-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyaFBhcXBEaGp6YWc/view?usp=sharing" target="_blank">Contractor</a>
|
||||
<a class="btn btn-outline-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyM0YtTWcxQzk0dEE/view?usp=sharing" target="_blank">Teacher</a>
|
||||
<a class="btn btn-outline-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyX2owd0xURjh3RlE/view?usp=sharing" target="_blank">Principle</a>
|
||||
<a class="btn btn-outline-info" href="https://drive.google.com/file/d/0B_iz0vkzXmAyMHdaM1BjZ1MwbWxva2lOY290NElwanN4b2JV/view?usp=sharing" target="_blank">CD Projekt Red</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
<h3>Projects & Repositories</h3>
|
||||
<projects></projects>
|
||||
</div>
|
||||
<div class="p-4 overflow-hidden">
|
||||
<h3>Contact</h3>
|
||||
<contact-form></contact-form>
|
||||
</div>
|
||||
<footer class="p-1 bg-dark text-center" style="color: grey">
|
||||
Copyright © ZaksCode 2022 | All Rights Reserved
|
||||
<br>
|
||||
Created by <a href="https://zakscode.com">Zak Timson</a>
|
||||
</footer>
|
||||
<div class="d-none d-sm-block skirt"></div>
|
||||
</div>
|
||||
</div>
|
@ -1,25 +0,0 @@
|
||||
import {AfterViewInit, Component, ViewChild} from '@angular/core';
|
||||
import {ConsoleComponent} from '../../components/console/console.component';
|
||||
import {QuoteService} from '../../services/quote.service';
|
||||
import {sleep} from '../../misc/utils';
|
||||
|
||||
@Component({
|
||||
selector: 'home',
|
||||
templateUrl: './home.component.html'
|
||||
})
|
||||
export class HomeComponent implements AfterViewInit {
|
||||
@ViewChild(ConsoleComponent) console!: ConsoleComponent;
|
||||
|
||||
constructor(private quotes: QuoteService) { }
|
||||
|
||||
ngAfterViewInit() { this.animateConsole(); }
|
||||
|
||||
animateConsole() {
|
||||
setTimeout(async () => {
|
||||
await this.console.exec('bash ./random-thought.sh', () => this.quotes.random());
|
||||
await sleep(10000);
|
||||
await this.console.exec('clear', async () => this.console.clear());
|
||||
this.animateConsole();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 123 KiB |
Before Width: | Height: | Size: 29 KiB |
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>
|
104
src/components/contact.vue
Normal file
@ -0,0 +1,104 @@
|
||||
<script setup lang="ts">
|
||||
import Icon from '@/components/icon.vue';
|
||||
import {momentum} from '@/services/momentum.service';
|
||||
import {ref} from 'vue';
|
||||
|
||||
const disable = ref(false);
|
||||
const done = ref(false);
|
||||
const data = ref({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
message: '',
|
||||
});
|
||||
const errors = ref<any>({
|
||||
banner: false,
|
||||
name: false,
|
||||
email: false,
|
||||
subject: false,
|
||||
message: false,
|
||||
});
|
||||
|
||||
function reset() {
|
||||
disable.value = false;
|
||||
done.value = false;
|
||||
data.value = {name: '', email: '', subject: '', message: ''};
|
||||
errors.value = {banner: false, name: false, email: false, subject: false, message: false};
|
||||
}
|
||||
|
||||
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} <${email}><br><br>${message}`
|
||||
});
|
||||
}
|
||||
|
||||
function submit() {
|
||||
disable.value = true;
|
||||
const d = data.value;
|
||||
|
||||
errors.value = {
|
||||
banner: false,
|
||||
name: !d.name,
|
||||
email: !d.email || !validateEmail(d.email),
|
||||
subject: !d.subject,
|
||||
message: !d.message
|
||||
};
|
||||
|
||||
if(errors.value.name || errors.value.email || errors.value.subject || errors.value.message) return 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 will be intouch shortly.</span>
|
||||
</div>
|
||||
<!-- Error Banner -->
|
||||
<div v-if="errors?.banner" class="alert alert-danger">
|
||||
Error: {{ (errors as any).banner }}
|
||||
</div>
|
||||
<!-- Contact Form -->
|
||||
<form>
|
||||
<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="disable" required>
|
||||
</div>
|
||||
</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="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>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
21
src/components/foot.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
footer {
|
||||
background: #343a40;
|
||||
color: #A3A3A3;
|
||||
|
||||
a {
|
||||
color: #75B8FF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<footer class="p-2 text-center">
|
||||
Copyright © ZaksCode 2024 | All Rights Reserved
|
||||
<br>
|
||||
Created by <a href="https://zakscode.com" target="_blank">Zak Timson</a>
|
||||
</footer>
|
||||
</template>
|
10
src/components/icon.vue
Normal file
@ -0,0 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
const props = defineProps({
|
||||
class: {type: String, default: ''},
|
||||
name: {type: String, required: true},
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<i :class="props.class + ' fa fa-' + props.name"></i>
|
||||
</template>
|
33
src/components/konsole.vue
Normal file
@ -0,0 +1,33 @@
|
||||
<script setup lang="ts">
|
||||
import '../modules/konsole';
|
||||
import {onMounted, ref} from 'vue';
|
||||
|
||||
const animate = ref(true);
|
||||
|
||||
function sleep(time: number) {
|
||||
return new Promise(res => setTimeout(res, time));
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
(<any>window).cli.build('#konsole');
|
||||
while(animate) {
|
||||
if(!animate.value) break;
|
||||
await sleep(3000);
|
||||
if(!animate.value) break;
|
||||
await showerThought();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div id="konsole" @click="animate = false"></div>
|
||||
</template>
|
30
src/components/profile.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import Icon from '@/components/icon.vue';
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.card {
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 1px -1px #0003;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div class="card d-inline-flex flex-column flex-sm-row text-center text-sm-start bg-white p-4">
|
||||
<div class="mb-4 mb-sm-4 me-sm-4">
|
||||
<img src="/profile.jpg" width="150px" height="150px" alt="Zakary Timson" class="rounded-circle">
|
||||
</div>
|
||||
<div class="me-sm-5">
|
||||
<h1 class="m-0" style="font-size: 2.5rem">Zakary Timson</h1>
|
||||
<h2 class="mt-0 text-muted" style="font-size: 1.25rem">
|
||||
DEVOPS & SOFTWARE ENGINEER
|
||||
</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" 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>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
31
src/components/projects.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
defineProps({
|
||||
projects: {type: Array as () => any[], required: true}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.invert {
|
||||
filter: invert(100%);
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div v-for="(p, i) in projects">
|
||||
<div class="d-flex align-items-center border p-2" :class="i == 0 ? '' : 'border-top-0'">
|
||||
<div class="me-2">
|
||||
<img :src="p.icon || '/git.png'" alt="Logo" :class="{invert: p.invertIcon}" style="height: 40px; width: 40px;">
|
||||
</div>
|
||||
<div class="d-flex flex-column">
|
||||
<div>
|
||||
<a :href="p.link" target="_blank" class="me-2">{{p.name}}</a>
|
||||
<a v-if="p.source" :href="p.source" target="_blank" class="text-small" style="font-size: 0.75em">[source]</a>
|
||||
</div>
|
||||
<div class="text-muted">{{p.description}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
30
src/components/refrences.vue
Normal file
@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
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-danger w-100" :href="resume" target="_blank">Resume</a>
|
||||
</div>
|
||||
<div class="btn-group-vertical w-100" role="group">
|
||||
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-none d-md-flex align-items-start">
|
||||
<a class="btn btn-outline-danger me-3" :href="resume" target="_blank">Resume</a>
|
||||
<div class="btn-group" role="group">
|
||||
<a v-for="ref in refrences" class="btn btn-outline-primary" :href="ref[1]" target="_blank">{{ref[0]}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,3 +0,0 @@
|
||||
export const environment = {
|
||||
production: true
|
||||
};
|
@ -1,16 +1,6 @@
|
||||
// This file can be replaced during build by using the `fileReplacements` array.
|
||||
// `ng build` replaces `environment.ts` with `environment.prod.ts`.
|
||||
// The list of file replacements can be found in `angular.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
||||
|
||||
/*
|
||||
* For easier debugging in development mode, you can import the following file
|
||||
* to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
|
||||
*
|
||||
* This import should be commented out in production mode because it will have a negative impact
|
||||
* on performance if an error is thrown.
|
||||
*/
|
||||
// import 'zone.js/plugins/zone-error'; // Included with Angular CLI.
|
||||
const devMode = location?.port == '5173';
|
||||
|
||||
export const environment = {
|
||||
apiUrl: devMode ? 'http://localhost' : location.origin,
|
||||
devMode
|
||||
}
|
||||
|
@ -1,28 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Zaks Code</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name=”robots” content=”index,nofollow” />
|
||||
<meta property=”og:type” content=”website” />
|
||||
<meta property=”og:title” content=”Zakary Timson” />
|
||||
<meta property=”og:description” content=”Cloud Architecture & Software Engineering” />
|
||||
<meta property=”og:image” content=”https://zakscode.com/assets/img/ZaksCode.png” />
|
||||
<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=”Cloud Architecture & Software Engineering” />
|
||||
<meta name=”twitter:image” content=”https://zakscode.com/assets/img/ZaksCode.png” />
|
||||
|
||||
<link rel="icon" type="image/png" href="assets/img/logo.png">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
34
src/main.scss
Normal file
@ -0,0 +1,34 @@
|
||||
@import url("https://use.fontawesome.com/releases/v6.1.1/css/all.css");
|
||||
@import url("https://fonts.googleapis.com/css2?family=Roboto");
|
||||
@import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
|
||||
|
||||
::-webkit-scrollbar { width: 10px; }
|
||||
::-webkit-scrollbar-track { background: #333; }
|
||||
::-webkit-scrollbar-thumb { background: #555; border-radius: 5px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: #aaa; }
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
font-family: Roboto,sans-serif;
|
||||
background: #354B72 url('/cloud.gif') no-repeat fixed right 50% top -10vh;
|
||||
}
|
||||
|
||||
a:not(.btn), a:not(.btn):visited {
|
||||
color: #006FE6;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: #0062ce;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.cap-width {
|
||||
width: min(100%, 1100px);
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
17
src/main.ts
@ -1,12 +1,9 @@
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import './main.scss'
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
const app = createApp(App)
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.error(err));
|
||||
app.use(router)
|
||||
app.mount('#app')
|
||||
|
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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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
@ -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('');
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
* This file includes polyfills needed by Angular and is loaded before the app.
|
||||
* You can add your own extra polyfills to this file.
|
||||
*
|
||||
* This file is divided into 2 sections:
|
||||
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
|
||||
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
|
||||
* file.
|
||||
*
|
||||
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
|
||||
* automatically update themselves. This includes recent versions of Safari, Chrome (including
|
||||
* Opera), Edge on the desktop, and iOS and Chrome on mobile.
|
||||
*
|
||||
* Learn more in https://angular.io/guide/browser-support
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* BROWSER POLYFILLS
|
||||
*/
|
||||
|
||||
/**
|
||||
* By default, zone.js will patch all possible macroTask and DomEvents
|
||||
* user can disable parts of macroTask/DomEvents patch by setting following flags
|
||||
* because those flags need to be set before `zone.js` being loaded, and webpack
|
||||
* will put import in the top of bundle, so user need to create a separate file
|
||||
* in this directory (for example: zone-flags.ts), and put the following flags
|
||||
* into that file, and then add the following code before importing zone.js.
|
||||
* import './zone-flags';
|
||||
*
|
||||
* The flags allowed in zone-flags.ts are listed here.
|
||||
*
|
||||
* The following flags will work for all browsers.
|
||||
*
|
||||
* (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
|
||||
* (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
|
||||
* (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
|
||||
*
|
||||
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
|
||||
* with the following flag, it will bypass `zone.js` patch for IE/Edge
|
||||
*
|
||||
* (window as any).__Zone_enable_cross_context_check = true;
|
||||
*
|
||||
*/
|
||||
|
||||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
11
src/router/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Home from '@/views/Home.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory((<any>import.meta).env.BASE_URL),
|
||||
routes: [
|
||||
{path: '/', name: 'home', component: Home}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
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,53 +0,0 @@
|
||||
@use '@angular/material' as mat;
|
||||
|
||||
@import url("https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.3.1/css/bootstrap.min.css");
|
||||
@import url("https://use.fontawesome.com/releases/v6.1.1/css/all.css");
|
||||
@import url("https://fonts.googleapis.com/icon?family=Material+Icons|Roboto:300,400,500");
|
||||
|
||||
@include mat.core();
|
||||
|
||||
$zakscode-primary: mat.define-palette(mat.$blue-palette, 900);
|
||||
$zakscode-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
|
||||
$zakscode-warn: mat.define-palette(mat.$red-palette);
|
||||
$zakscode-theme: mat.define-light-theme((
|
||||
color: (
|
||||
primary: $zakscode-primary,
|
||||
accent: $zakscode-accent,
|
||||
warn: $zakscode-warn,
|
||||
)
|
||||
));
|
||||
@include mat.all-component-themes($zakscode-theme);
|
||||
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: #354B72;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
background-color: #354B72;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 5px;
|
||||
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.5);
|
||||
background-color: #354B72;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
background: #354B72 url("/assets/img/ZaksCode.gif") no-repeat fixed center;
|
||||
background-position: right 50% top -10vh;
|
||||
}
|
||||
|
||||
.skirt {
|
||||
height: 3rem;
|
||||
width: 100%;
|
||||
background-color: #354B72;
|
||||
}
|
||||
|
26
src/test.ts
@ -1,26 +0,0 @@
|
||||
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
|
||||
|
||||
import 'zone.js/testing';
|
||||
import { getTestBed } from '@angular/core/testing';
|
||||
import {
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting
|
||||
} from '@angular/platform-browser-dynamic/testing';
|
||||
|
||||
declare const require: {
|
||||
context(path: string, deep?: boolean, filter?: RegExp): {
|
||||
<T>(id: string): T;
|
||||
keys(): string[];
|
||||
};
|
||||
};
|
||||
|
||||
// First, initialize the Angular testing environment.
|
||||
getTestBed().initTestEnvironment(
|
||||
BrowserDynamicTestingModule,
|
||||
platformBrowserDynamicTesting(),
|
||||
);
|
||||
|
||||
// Then we find all the tests.
|
||||
const context = require.context('./', true, /\.spec\.ts$/);
|
||||
// And load the modules.
|
||||
context.keys().map(context);
|
85
src/views/Home.vue
Normal file
@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import Card from '@/components/card.vue';
|
||||
import Contact from '@/components/contact.vue';
|
||||
import Konsole from '@/components/konsole.vue';
|
||||
import Projects from '@/components/projects.vue';
|
||||
import Refrences from '@/components/refrences.vue';
|
||||
import {momentum} from '@/services/momentum.service';
|
||||
import {ref} from 'vue';
|
||||
|
||||
// Get favorites
|
||||
const products = ref<any[]>([]);
|
||||
const openSource = ref<any[]>([]);
|
||||
momentum.data.read('Repos').then(resp =>
|
||||
resp.forEach((r: any) => (r.product ? products : openSource).value.push(r)));
|
||||
|
||||
// Get repository count
|
||||
let remainder = ref(0);
|
||||
fetch('https://git.zakscode.com/api/v1/repos/search?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">
|
||||
<!-- Terminal -->
|
||||
<konsole class="mb-5" />
|
||||
|
||||
<!-- Steps -->
|
||||
<div class="mb-5 pt-5">
|
||||
<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 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 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>
|
||||
<refrences />
|
||||
</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="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">
|
||||
<div>
|
||||
<contact />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
@ -1,16 +1,23 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts",
|
||||
"src/polyfills.ts",
|
||||
"src/app/misc/utils.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": [
|
||||
"env.d.ts",
|
||||
"src/**/*",
|
||||
"src/**/*.vue"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"composite": true,
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"moduleResolution": "Node",
|
||||
"noEmit": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +1,11 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"compileOnSave": false,
|
||||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"outDir": "./dist/out-tsc",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"sourceMap": true,
|
||||
"declaration": false,
|
||||
"downlevelIteration": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"target": "es2017",
|
||||
"module": "es2020",
|
||||
"lib": [
|
||||
"es2020",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"angularCompilerOptions": {
|
||||
"enableI18nLegacyMessageIdFormat": false,
|
||||
"strictInjectionParameters": true,
|
||||
"strictInputAccessModifiers": true,
|
||||
"strictTemplates": true
|
||||
}
|
||||
"files": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
19
tsconfig.node.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "@tsconfig/node18/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"noEmit": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": [
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/spec",
|
||||
"types": [
|
||||
"jasmine"
|
||||
]
|
||||
},
|
||||
"files": [
|
||||
"src/test.ts",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
16
vite.config.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {defineConfig} from 'vite';
|
||||
import {fileURLToPath, URL} from 'node:url';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
}
|
||||
},
|
||||
envPrefix: 'APP',
|
||||
});
|