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 |