Compare commits
No commits in common. "0d0b97f6918a189825d37b0fbfa4f90296d870f6" and "9075938d438c2742baf911a47e519ccfcca01439" have entirely different histories.
0d0b97f691
...
9075938d43
|
@ -1,15 +0,0 @@
|
||||||
/* eslint-env node */
|
|
||||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
'extends': [
|
|
||||||
'plugin:vue/vue3-essential',
|
|
||||||
'eslint:recommended',
|
|
||||||
'@vue/eslint-config-typescript',
|
|
||||||
'@vue/eslint-config-prettier/skip-formatting'
|
|
||||||
],
|
|
||||||
parserOptions: {
|
|
||||||
ecmaVersion: 'latest'
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
pnpm-debug.log*
|
|
||||||
lerna-debug.log*
|
|
||||||
|
|
||||||
node_modules
|
|
||||||
.DS_Store
|
|
||||||
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,8 +0,0 @@
|
||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/prettierrc",
|
|
||||||
"semi": false,
|
|
||||||
"tabWidth": 2,
|
|
||||||
"singleQuote": true,
|
|
||||||
"printWidth": 100,
|
|
||||||
"trailingComma": "none"
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": [
|
|
||||||
"Vue.volar",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
# app
|
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
|
||||||
|
|
||||||
## Recommended IDE Setup
|
|
||||||
|
|
||||||
[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
|
||||||
|
|
||||||
## Type Support for `.vue` Imports in TS
|
|
||||||
|
|
||||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
|
||||||
|
|
||||||
## Customize configuration
|
|
||||||
|
|
||||||
See [Vite Configuration Reference](https://vitejs.dev/config/).
|
|
||||||
|
|
||||||
## Project Setup
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compile and Hot-Reload for Development
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Type-Check, Compile and Minify for Production
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lint with [ESLint](https://eslint.org/)
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run lint
|
|
||||||
```
|
|
|
@ -1 +0,0 @@
|
||||||
/// <reference types="vite/client" />
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Andrew Lalis</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app"></div>
|
|
||||||
<script type="module" src="/src/main.ts"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,36 +0,0 @@
|
||||||
{
|
|
||||||
"name": "app",
|
|
||||||
"version": "0.0.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"build-only": "vite build",
|
|
||||||
"type-check": "vue-tsc --build --force",
|
|
||||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
|
||||||
"format": "prettier --write src/"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"pinia": "^2.1.7",
|
|
||||||
"vue": "^3.4.29",
|
|
||||||
"vue-router": "^4.3.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rushstack/eslint-patch": "^1.8.0",
|
|
||||||
"@tsconfig/node20": "^20.1.4",
|
|
||||||
"@types/node": "^20.14.5",
|
|
||||||
"@vitejs/plugin-vue": "^5.0.5",
|
|
||||||
"@vue/eslint-config-prettier": "^9.0.0",
|
|
||||||
"@vue/eslint-config-typescript": "^13.0.0",
|
|
||||||
"@vue/tsconfig": "^0.5.1",
|
|
||||||
"eslint": "^8.57.0",
|
|
||||||
"eslint-plugin-vue": "^9.23.0",
|
|
||||||
"npm-run-all2": "^6.2.0",
|
|
||||||
"prettier": "^3.2.5",
|
|
||||||
"typescript": "~5.4.0",
|
|
||||||
"vite": "^5.3.1",
|
|
||||||
"vue-tsc": "^2.0.21"
|
|
||||||
}
|
|
||||||
}
|
|
Before Width: | Height: | Size: 10 KiB |
|
@ -1,83 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { RouterView } from 'vue-router'
|
|
||||||
import NavLink from './components/NavLink.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="bisection-container">
|
|
||||||
<header class="page-bisection">
|
|
||||||
<h1 class="site-header-text">Andrew Lalis</h1>
|
|
||||||
<nav>
|
|
||||||
<NavLink path="/">Home</NavLink>
|
|
||||||
<NavLink path="/about">About</NavLink>
|
|
||||||
<NavLink path="/software">Software</NavLink>
|
|
||||||
<NavLink path="/gardening">Gardening</NavLink>
|
|
||||||
<NavLink path="/logbook">Logbook</NavLink>
|
|
||||||
<NavLink path="/contact">Contact</NavLink>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<RouterView v-slot="{ Component }">
|
|
||||||
<Transition name="page">
|
|
||||||
<Component :is="Component" class="page-bisection content-page" />
|
|
||||||
</Transition>
|
|
||||||
</RouterView>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.bisection-container {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.page-bisection {
|
|
||||||
width: 50ch;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: top;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
.content-page {
|
|
||||||
text-align: left;
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.site-header-text {
|
|
||||||
font-family: Sacramento;
|
|
||||||
container-type: inline-size;
|
|
||||||
font-size: calc(min(60px, max(5cqw, 42px)));
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-enter-active {
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-enter-from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Activates once the left and right half of the page stack due to width constraints.
|
|
||||||
We remove a lot of the vertical spacing to make things fit better.
|
|
||||||
*/
|
|
||||||
@media (width < calc(100ch - 2.5rem)) {
|
|
||||||
.site-header-text {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
.content-page {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Once the device width is smaller than one of the halves, sync the page half to the device width.
|
|
||||||
*/
|
|
||||||
@media (width < 50ch) {
|
|
||||||
.page-bisection {
|
|
||||||
width: calc(100% - 2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,21 +0,0 @@
|
||||||
@font-face {
|
|
||||||
font-family: Sacramento;
|
|
||||||
src: url('@/assets/fonts/Sacramento-Regular.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: BaskervvilleSC;
|
|
||||||
src: url('@/assets/fonts/BaskervvilleSC-Regular.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: OpenSans;
|
|
||||||
src: url('@/assets/fonts/OpenSans-VariableFont_wdth,wght.ttf');
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: OpenSans;
|
|
||||||
src: url('@/assets/fonts/OpenSans-Italic-VariableFont_wdth,wght.ttf');
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
@import '@/assets/fonts.css';
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--text-color: rgb(224, 224, 224);
|
|
||||||
--background-color: rgb(17, 17, 17);
|
|
||||||
--background-color-2: rgb(34, 34, 34);
|
|
||||||
--code-color: rgb(224, 226, 120);
|
|
||||||
--success-color: rgb(118, 201, 118);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: OpenSans;
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--text-color);
|
|
||||||
background-color: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-family: BaskervvilleSC;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
color: var(--code-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-local {
|
|
||||||
color: var(--success-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.link-local:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-out {
|
|
||||||
color: var(--code-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.link-out:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Message } from '@/views/LogBookView.vue'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
message: Message
|
|
||||||
}>()
|
|
||||||
const date = computed(() => {
|
|
||||||
const b = props.message.createdAt.split(/\D+/)
|
|
||||||
const offsetMult = props.message.createdAt.indexOf('+') !== -1 ? -1 : 1
|
|
||||||
const hrOffset = offsetMult * (+b[7] || 0)
|
|
||||||
const minOffset = offsetMult * (+b[8] || 0)
|
|
||||||
return new Date(
|
|
||||||
Date.UTC(+b[0], +b[1] - 1, +b[2], +b[3] + hrOffset, +b[4] + minOffset, +b[5], +b[6] || 0)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<div class="logbook-message">
|
|
||||||
<p v-text="message.message"></p>
|
|
||||||
<div>
|
|
||||||
<small>sent by <span v-text="message.name" style="color: var(--code-color)"></span></small>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<small><time :datetime="date.toISOString()" v-text="date.toLocaleString()"></time></small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style>
|
|
||||||
.logbook-message {
|
|
||||||
background-color: var(--background-color-2);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 0.75rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.logbook-message > time {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
.logbook-message > p {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,53 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { useRoute } from 'vue-router'
|
|
||||||
|
|
||||||
const route = useRoute()
|
|
||||||
defineProps<{
|
|
||||||
path: string
|
|
||||||
}>()
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="nav-link">
|
|
||||||
<RouterLink :to="path" v-bind:class="route.path === path ? 'nav-link-active' : ''">
|
|
||||||
<slot></slot>
|
|
||||||
</RouterLink>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<style>
|
|
||||||
.nav-link {
|
|
||||||
text-align: right;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link > a {
|
|
||||||
font-family: BaskervvilleSC;
|
|
||||||
font-size: 30px;
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
transition: color 0.25s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link-active {
|
|
||||||
color: var(--success-color) !important;
|
|
||||||
text-decoration: underline !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link > a:hover {
|
|
||||||
color: var(--success-color);
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Activates once the left and right half of the page stack due to width constraints.
|
|
||||||
We remove a lot of the vertical spacing to make things fit better.
|
|
||||||
*/
|
|
||||||
@media (width < calc(100ch - 2.5rem)) {
|
|
||||||
.nav-link {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<template>
|
|
||||||
<p class="note">
|
|
||||||
<slot></slot>
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
<style>
|
|
||||||
.note {
|
|
||||||
font-size: small;
|
|
||||||
font-style: italic;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,80 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
tags?: string[]
|
|
||||||
}>()
|
|
||||||
const sortedTags = computed(() => (!props.tags ? null : props.tags.slice().sort()))
|
|
||||||
|
|
||||||
const COMMON_TAGS: Record<string, string[]> = {
|
|
||||||
Java: ['white', '#e76f00'],
|
|
||||||
Vue: ['white', '#41b883'],
|
|
||||||
Dlang: ['white', '#b03931'],
|
|
||||||
Typescript: ['white', '#3178c6'],
|
|
||||||
Finance: ['white', '#0e4f0a'],
|
|
||||||
Spring: ['white', '#6db33f']
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTagStyle(tag: string) {
|
|
||||||
if (tag in COMMON_TAGS) {
|
|
||||||
return { color: COMMON_TAGS[tag][0], 'background-color': COMMON_TAGS[tag][1] }
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
color: 'white',
|
|
||||||
'background-color': 'gray'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div class="software-project-tile">
|
|
||||||
<h3><a :href="url" v-text="name" class="link-out"></a></h3>
|
|
||||||
<div class="software-project-tile-content">
|
|
||||||
<slot></slot>
|
|
||||||
</div>
|
|
||||||
<div v-if="tags">
|
|
||||||
<span
|
|
||||||
v-for="tag in sortedTags"
|
|
||||||
:key="tag"
|
|
||||||
v-text="tag"
|
|
||||||
class="software-project-tile-tag-badge"
|
|
||||||
:style="getTagStyle(tag)"
|
|
||||||
></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.software-project-tile {
|
|
||||||
background-color: var(--background-color-2);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 1rem;
|
|
||||||
margin-top: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.software-project-tile > h3 {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
font-size: x-large;
|
|
||||||
}
|
|
||||||
|
|
||||||
.software-project-tile-content > p {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.software-project-tile-tag-badge {
|
|
||||||
font-size: 12px;
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
padding-top: 0.1rem;
|
|
||||||
padding-bottom: 0.1rem;
|
|
||||||
border-radius: 10px;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,14 +0,0 @@
|
||||||
import './assets/main.css'
|
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
|
||||||
import { createPinia } from 'pinia'
|
|
||||||
|
|
||||||
import App from './App.vue'
|
|
||||||
import router from './router'
|
|
||||||
|
|
||||||
const app = createApp(App)
|
|
||||||
|
|
||||||
app.use(createPinia())
|
|
||||||
app.use(router)
|
|
||||||
|
|
||||||
app.mount('#app')
|
|
|
@ -1,39 +0,0 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
|
||||||
import HomeView from '../views/HomeView.vue'
|
|
||||||
import AboutView from '@/views/AboutView.vue'
|
|
||||||
import ContactView from '@/views/ContactView.vue'
|
|
||||||
import SoftwareView from '@/views/SoftwareView.vue'
|
|
||||||
import GardeningView from '@/views/GardeningView.vue'
|
|
||||||
import LogBookView from '@/views/LogBookView.vue'
|
|
||||||
|
|
||||||
const router = createRouter({
|
|
||||||
history: createWebHistory(import.meta.env.BASE_URL),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
component: HomeView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/about',
|
|
||||||
component: AboutView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/contact',
|
|
||||||
component: ContactView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/software',
|
|
||||||
component: SoftwareView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/gardening',
|
|
||||||
component: GardeningView
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/logbook',
|
|
||||||
component: LogBookView
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
export default router
|
|
|
@ -1,12 +0,0 @@
|
||||||
import { ref, computed } from 'vue'
|
|
||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useCounterStore = defineStore('counter', () => {
|
|
||||||
const count = ref(0)
|
|
||||||
const doubleCount = computed(() => count.value * 2)
|
|
||||||
function increment() {
|
|
||||||
count.value++
|
|
||||||
}
|
|
||||||
|
|
||||||
return { count, doubleCount, increment }
|
|
||||||
})
|
|
|
@ -1,34 +0,0 @@
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
This page was designed and programmed by myself, Andrew, to be my personal presence on the
|
|
||||||
world-wide-web. I use it as my professional portfolio, in addition to a repository for
|
|
||||||
anything random that interests me and might be interesting for others too.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Feel free to look around, and
|
|
||||||
<RouterLink to="/contact" class="link-local">contact me</RouterLink> if you find anything
|
|
||||||
broken or have any questions about anything you find. Maybe say hello in my
|
|
||||||
<RouterLink to="/logbook" class="link-local">logbook</RouterLink> while you're at it?
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>About This Site, Technically</h3>
|
|
||||||
<p>
|
|
||||||
You can find the source code for this website at
|
|
||||||
<a href="https://git.andrewlalis.com/andrew/homepage" class="link-out">
|
|
||||||
git.andrewlalis.com/andrew/homepage </a
|
|
||||||
>. It's built using <a href="https://vuejs.org/" class="link-out">Vue</a>, and styled
|
|
||||||
completely by hand using plain-old CSS.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
The site is hosted on a small DigitalOcean "droplet" (their branding for a Virtual Private
|
|
||||||
Server), and is served through Nginx and various firewalls.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you actually go and view the source code, you'll see a <code>deploy.sh</code> file.
|
|
||||||
Essentially, all I do is build the site locally, then upload the files. Pretty simple, right?
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style></style>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import { computed, ref } from 'vue'
|
|
||||||
|
|
||||||
const emailText = ref('')
|
|
||||||
const emailCorrect = computed(() => {
|
|
||||||
const testStr = atob('YW5kcmV3bGFsaXNvZmZpY2lhbEBnbWFpbC5jb20=')
|
|
||||||
return emailText.value.trim().toLowerCase() === testStr
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
My preferred method of communication is email, which I usually check daily. Send to
|
|
||||||
<code>first-name + last-name + "official" at gmail dot com</code>.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
See if you can figure it out:
|
|
||||||
<input class="contactEmailInput" type="text" v-model="emailText" />
|
|
||||||
<span v-if="emailCorrect">
|
|
||||||
<br />
|
|
||||||
<small style="color: var(--success-color)"><em>You got it!</em></small>
|
|
||||||
</span>
|
|
||||||
<br />
|
|
||||||
<small><em>I write it like that to avoid getting spammed by bots.</em></small>
|
|
||||||
</p>
|
|
||||||
<p>My Discord username is <code>____andrew____</code></p>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
<style>
|
|
||||||
.contactEmailInput {
|
|
||||||
font-family: OpenSans, sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
width: calc(min(30ch, 100%));
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,5 +0,0 @@
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p><em>This page is a work in progress. Please check back later.</em></p>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
|
@ -1,13 +0,0 @@
|
||||||
<script setup lang="ts"></script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
My name's Andrew, and I'm a software engineer, gardener, runner, home cook, and probably many
|
|
||||||
other things too, depending on who you ask. Welcome to my website!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Click one of the links to check out something more interesting.
|
|
||||||
</p>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
|
@ -1,159 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import LogbookMessage from '@/components/LogbookMessage.vue'
|
|
||||||
import NoteText from '@/components/NoteText.vue'
|
|
||||||
import { onMounted, ref, type Ref } from 'vue'
|
|
||||||
|
|
||||||
const LOGBOOK_URL = 'https://logbook.andrewlalis.com'
|
|
||||||
|
|
||||||
export interface Message {
|
|
||||||
createdAt: string
|
|
||||||
name: string
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages: Ref<Message[]> = ref([])
|
|
||||||
const addingMessage = ref(false)
|
|
||||||
const messageSubmitted = ref(false)
|
|
||||||
const formName = ref('')
|
|
||||||
const formMessage = ref('')
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
messages.value = await getMessages()
|
|
||||||
})
|
|
||||||
|
|
||||||
async function getMessages(): Promise<Message[]> {
|
|
||||||
try {
|
|
||||||
const response = await fetch(LOGBOOK_URL)
|
|
||||||
if (response.ok) {
|
|
||||||
return (await response.json()) as Message[]
|
|
||||||
} else {
|
|
||||||
console.warn('Failed to get logbook messages.', response.status)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to send logbook request.', error)
|
|
||||||
}
|
|
||||||
return [] // In case of error, always return an empty list.
|
|
||||||
}
|
|
||||||
|
|
||||||
async function submitMessage() {
|
|
||||||
const data = {
|
|
||||||
name: formName.value,
|
|
||||||
message: formMessage.value
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const response = await fetch(LOGBOOK_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify(data)
|
|
||||||
})
|
|
||||||
if (response.ok) {
|
|
||||||
messages.value = await getMessages()
|
|
||||||
formName.value = ''
|
|
||||||
formMessage.value = ''
|
|
||||||
addingMessage.value = false
|
|
||||||
messageSubmitted.value = true
|
|
||||||
} else {
|
|
||||||
console.warn('Log message rejected.', response.status)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to send log message.', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
If you'd like, you can leave a message in my online logbook so that I and others might see it.
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
v-if="!messageSubmitted"
|
|
||||||
@click.prevent="addingMessage = !addingMessage"
|
|
||||||
class="button-link"
|
|
||||||
>Add your message.</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<form v-if="addingMessage" class="message-form">
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="nameField">Name</label>
|
|
||||||
<input
|
|
||||||
id="nameField"
|
|
||||||
class="message-form-input"
|
|
||||||
type="text"
|
|
||||||
name="name"
|
|
||||||
minlength="2"
|
|
||||||
maxlength="32"
|
|
||||||
required
|
|
||||||
v-model="formName"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<label for="messageField">Message</label>
|
|
||||||
<textarea
|
|
||||||
id="messageField"
|
|
||||||
class="message-form-input"
|
|
||||||
type="text"
|
|
||||||
name="message"
|
|
||||||
minlength="2"
|
|
||||||
maxlength="255"
|
|
||||||
required
|
|
||||||
v-model="formMessage"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
<div class="form-row">
|
|
||||||
<a href="#" @click.prevent="submitMessage()" class="button-link">Submit your message!</a>
|
|
||||||
</div>
|
|
||||||
<NoteText>
|
|
||||||
Note that I do check all logs and some basic information about who sent them (IP address,
|
|
||||||
browser, etc). Inappropriate comments will be removed and their authors banned.
|
|
||||||
</NoteText>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<TransitionGroup name="messages" tag="div">
|
|
||||||
<LogbookMessage v-for="msg in messages" :key="msg.createdAt" :message="msg" />
|
|
||||||
</TransitionGroup>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
<style>
|
|
||||||
.button-link {
|
|
||||||
color: var(--success-color);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
.button-link:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
.message-form {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
}
|
|
||||||
.form-row {
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
.form-row > label {
|
|
||||||
display: block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
.message-form-input {
|
|
||||||
font-family: OpenSans;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
#messageField {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-enter-active,
|
|
||||||
.messages-leave-active {
|
|
||||||
transition: all 0.5s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-enter-from,
|
|
||||||
.messages-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateX(30px);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,98 +0,0 @@
|
||||||
<script setup lang="ts">
|
|
||||||
import SoftwareProjectTile from '@/components/SoftwareProjectTile.vue'
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<main>
|
|
||||||
<p>
|
|
||||||
Here you'll find a selection of projects I've worked on over the course of my software
|
|
||||||
engineering career.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Beside the projects listed here, you can view my complete portfolio on
|
|
||||||
<a href="https://git.andrewlalis.com" class="link-out">git.andrewlalis.com</a>. I also have a
|
|
||||||
<a href="https://github.com/andrewlalis" class="link-out">GitHub</a> account, but I only use
|
|
||||||
it for collaboration and a projects that rely on GitHub's API integration.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="Perfin"
|
|
||||||
url="https://git.andrewlalis.com/andrew/perfin"
|
|
||||||
:tags="['Java', 'JavaFX', 'Finance']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
A desktop personal finance application that makes it easy to manage your accounts and
|
|
||||||
transactions in a secure manner. Record transactions, track brokerage assets, and view
|
|
||||||
graphs of your spending, total value, and more.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="Guerilla Gardening Map"
|
|
||||||
url="https://guerilla-gardening.andrewlalis.com"
|
|
||||||
:tags="['Dlang', 'Vue', 'Typescript']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
A website for anonymously tracking and sharing information on
|
|
||||||
<a href="https://en.wikipedia.org/wiki/Guerrilla_gardening" class="link-out"
|
|
||||||
>Guerilla-Gardening</a
|
|
||||||
>
|
|
||||||
locations around the world.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="LiteList"
|
|
||||||
url="https://litelist.andrewlalis.com"
|
|
||||||
:tags="['Dlang', 'Vue', 'Typescript']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
An extremely barebones web application to demonstrate the viability of my own HTTP server.
|
|
||||||
It's just your average ToDo list app, with basic JWT-based authentication.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="Handy-Http"
|
|
||||||
url="https://github.com/andrewlalis/handy-httpd"
|
|
||||||
:tags="['Dlang']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
An lightweight and flexible HTTP server implemented in the D programming language. Supports
|
|
||||||
websockets, file serving, complex URL paths with path variables, and more!
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="JavaFX Scene Router"
|
|
||||||
url="https://git.andrewlalis.com/andrew/javafx-scene-router"
|
|
||||||
:tags="['Java', 'JavaFX']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
A <em>router</em> implementation for JavaFX desktop applications. Define a singleton
|
|
||||||
<code>SceneRouter</code>, add some routes to it, and add the router's provided view
|
|
||||||
component to your scene graph. Now you can navigate to different "pages" programmatically or
|
|
||||||
with user-interaction.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="Gymboard"
|
|
||||||
url="https://git.andrewlalis.com/andrew/Gymboard"
|
|
||||||
:tags="['Java', 'Spring', 'Vue', 'Typescript', 'Dlang']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
Proof-of-concept for a local gym leaderboard web application, where users post videos of
|
|
||||||
their lifts to climb to the top of various leaderboards. Built using a
|
|
||||||
<em>modular-monolith</em> software architecture.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
<SoftwareProjectTile
|
|
||||||
name="Streams"
|
|
||||||
url="https://github.com/andrewlalis/streams"
|
|
||||||
:tags="['Dlang']"
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
A library that defines useful stream primitive types and implementations for IO, which
|
|
||||||
serves as a convenient wrapper around platform-specific reading and writing operations for
|
|
||||||
files, sockets, and more.
|
|
||||||
</p>
|
|
||||||
</SoftwareProjectTile>
|
|
||||||
</main>
|
|
||||||
</template>
|
|
||||||
<style></style>
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
||||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
|
||||||
"exclude": ["src/**/__tests__/*"],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.node.json"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "./tsconfig.app.json"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
{
|
|
||||||
"extends": "@tsconfig/node20/tsconfig.json",
|
|
||||||
"include": [
|
|
||||||
"vite.config.*",
|
|
||||||
"vitest.config.*",
|
|
||||||
"cypress.config.*",
|
|
||||||
"nightwatch.conf.*",
|
|
||||||
"playwright.config.*"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"noEmit": true,
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"types": ["node"]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { fileURLToPath, URL } from 'node:url'
|
|
||||||
|
|
||||||
import { defineConfig } from 'vite'
|
|
||||||
import vue from '@vitejs/plugin-vue'
|
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [
|
|
||||||
vue(),
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
Before Width: | Height: | Size: 7.9 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 510 KiB After Width: | Height: | Size: 510 KiB |
Before Width: | Height: | Size: 812 KiB After Width: | Height: | Size: 812 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 645 KiB After Width: | Height: | Size: 645 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 973 KiB After Width: | Height: | Size: 973 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 587 KiB After Width: | Height: | Size: 587 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 751 KiB After Width: | Height: | Size: 751 KiB |