Compare commits

..

No commits in common. "2eea51828a32bb5851fbe231763105ff0e6d337e" and "f36bd0423f152bd90f4e023e970a00198981ddd8" have entirely different histories.

33 changed files with 898 additions and 5501 deletions

View File

@ -13,4 +13,3 @@ steps:
image: busybox image: busybox
commands: commands:
- echo hello-world - echo hello-world

View File

@ -17,37 +17,29 @@ const app = express()
app.use(morgan('combined')) app.use(morgan('combined'))
app.use(bodyParser.json()) app.use(bodyParser.json())
app.use(cors()) app.use(cors())
app.get('/database', (req, res) => {
// app.get('/database', (req, res) => { const promise = databases.listDocuments('lifetracker-db', 'ryan');
// const promise = databases.listDocuments('lifetracker-db', 'ryan'); promise.then(function ({ documents } = response) {
// promise.then(function ({ documents } = response) {
// res.send(documents) res.send(documents)
// }, function (error) { }, function (error) {
// console.log(error); console.log(error);
// }); });
// }) })
// app.get('/entry/:date', (req, res) => { app.get('/entry/:date', (req, res) => {
// var date = new Date(req.params['date']).toISOString().replace(/T.*/,""); var date = new Date(req.params['date']).toISOString().replace(/T.*/,"");
// console.log("Fetching entry for " + date); console.log("Fetching entry for " + date);
// const promise = databases.listDocuments('lifetracker-db', 'ryan'); const promise = databases.listDocuments('lifetracker-db', 'ryan');
// promise.then(function ({ documents } = response) { promise.then(function ({ documents } = response) {
// entries = documents.map(e => e.Date) entries = documents.map(e => e.Date)
// res.send(entries) res.send(entries)
// }, function (error) { }, function (error) {
// console.log(error); console.log(error);
// }); });
// }) })
const path = __dirname + '/dist/'
app.use(express.static(path))
app.get(/.*/, function (req,res) {
res.sendFile(path + "index.html");
});
app.listen(process.env.PORT || 8081) app.listen(process.env.PORT || 8081)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,15 +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>Life Tracker Expanded</title>
<script type="module" crossorigin src="/assets/index-64281767.js"></script>
<link rel="stylesheet" href="/assets/index-b41ae507.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

View File

@ -8,12 +8,8 @@ const client = new sdk.Client();
const databases = new sdk.Databases(client); const databases = new sdk.Databases(client);
function importCSV(){
console.log("Worked.");
}
function importsCSV(){ var data = fs.readFileSync('data.csv')
var data = fs.readFileSync('data.csv')
.toString() // convert Buffer to string .toString() // convert Buffer to string
.split('\n') // split string to lines .split('\n') // split string to lines
.slice(1) // remove first line .slice(1) // remove first line
@ -45,6 +41,5 @@ function importsCSV(){
console.log(error); console.log(error);
}); });
}) })
}
exports.entries = importCSV; exports.entries = data;

View File

@ -1,20 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
"eslint:recommended",
"plugin:vue/vue3-recommended",
"standard-with-typescript",
"prettier",
],
overrides: [],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
project: ["tsconfig.json"],
},
plugins: ["vue"],
rules: {},
};

View File

@ -1 +0,0 @@
{}

View File

@ -13,8 +13,8 @@ TypeScript cannot handle type information for `.vue` imports by default, so we r
If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps:
1. Disable the built-in TypeScript Extension 1. Disable the built-in TypeScript Extension
1. Run `Extensions: Show Built-in Extensions` from VSCode's command palette 1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette
2. Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` 2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)`
2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette.
## Customize configuration ## Customize configuration

View File

@ -1,13 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8">
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Life Tracker Expanded</title> <title>Life Tracker Expanded</title>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="module" src="main.ts"></script> <script type="module" src="/src/main.ts"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load Diff

View File

@ -4,8 +4,6 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src",
"format": "prettier . --write",
"build": "run-p type-check build-only", "build": "run-p type-check build-only",
"preview": "vite preview", "preview": "vite preview",
"build-only": "vite build", "build-only": "vite build",
@ -13,18 +11,13 @@
}, },
"dependencies": { "dependencies": {
"@handsontable/vue3": "^12.3.3", "@handsontable/vue3": "^12.3.3",
"@vitejs/plugin-react-refresh": "^1.3.6",
"appwrite": "^11.0.0", "appwrite": "^11.0.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"eslint-config-standard-with-typescript": "^34.0.1",
"eslint-plugin-vue": "^9.12.0",
"handsontable": "^12.3.3", "handsontable": "^12.3.3",
"luxon": "^3.3.0", "luxon": "^3.3.0",
"moment": "^2.29.4", "moment": "^2.29.4",
"node": "^20.0.0", "node": "^20.0.0",
"pinia": "^2.0.35", "pinia": "^2.0.35",
"semver": "^7.5.1",
"vite-plugin-eslint": "^1.8.1",
"vue": "^3.2.47", "vue": "^3.2.47",
"vue-router": "^4.1.6" "vue-router": "^4.1.6"
}, },
@ -32,10 +25,8 @@
"@types/node": "^18.14.2", "@types/node": "^18.14.2",
"@vitejs/plugin-vue": "^4.0.0", "@vitejs/plugin-vue": "^4.0.0",
"@vue/tsconfig": "^0.1.3", "@vue/tsconfig": "^0.1.3",
"eslint-config-prettier": "^8.8.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"prettier": "2.8.8", "typescript": "~4.8.4",
"typescript": "^5.1.0-dev.20230515",
"vite": "^4.1.4", "vite": "^4.1.4",
"vue-tsc": "^1.2.0" "vue-tsc": "^1.2.0"
} }

View File

@ -1,13 +1,15 @@
<script setup lang="ts"> <script setup lang="ts">
import { RouterLink, RouterView } from "vue-router"; import { RouterLink, RouterView } from 'vue-router'
import AuthNav from "./components/AuthNav.vue"; import AuthNav from './components/AuthNav.vue'
</script> </script>
<template> <template>
<header> <header>
<div class="title"> <div class="title">
<h1> <h1>
<RouterLink to="/"> Tracker Expanded </RouterLink> <RouterLink to="/">
{{ year }} Tracker Expanded
</RouterLink>
</h1> </h1>
</div> </div>
<nav> <nav>
@ -18,6 +20,9 @@ import AuthNav from "./components/AuthNav.vue";
</header> </header>
<RouterView /> <RouterView />
</template> </template>
<style scoped></style> <style scoped>
</style>

View File

@ -63,46 +63,46 @@ body {
background: var(--color-background); background: var(--color-background);
transition: color 0.5s, background-color 0.5s; transition: color 0.5s, background-color 0.5s;
line-height: 1.6; line-height: 1.6;
font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
sans-serif;
font-size: 15px; font-size: 15px;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
header { header{
display: flex; display: flex;
gap: 2em; gap: 2em;
align-items: center; align-items: center;
/* border-bottom: 1px solid var(--vt-c-white-mute); */ /* border-bottom: 1px solid var(--vt-c-white-mute); */
} }
h1 { h1{
color: var(--vt-c-text-dark-1); color: var(--vt-c-text-dark-1);
} }
nav { nav{
display: flex; display:flex;
gap: 1em; gap: 1em;
} }
a { a{
color: var(--color-text); color: var(--color-text);
text-decoration: none; text-decoration: none;
} }
a.router-link-active { a.router-link-active{
font-weight: bold; font-weight: bold;
border-bottom: 1px solid var(--color-text); border-bottom: 1px solid var(--color-text);
} }
a:hover { a:hover{
color: white; color: white;
} }
div#app { div#app{
height: 95vh; height: 95vh;
} }
div#table { div#table{
height: 100%; height: 100%;
} }

View File

@ -1,4 +1,4 @@
:root { :root{
--white: white; --white: white;
--black: #273036; --black: #273036;
--red: #c71634; --red: #c71634;
@ -13,13 +13,11 @@
--lime: #bfff55; --lime: #bfff55;
} }
.handsontable td.align-left, .handsontable td.align-left, .handsontable th:last-child div {
.handsontable th:last-child div {
text-align: left !important; text-align: left !important;
} }
.ht__active_highlight, .ht__active_highlight, .ht__highlight {
.ht__highlight {
background: unset; background: unset;
} }
@ -30,11 +28,11 @@
.invert { .invert {
filter: invert(); filter: invert();
} }
.active-hour { .active-hour{
background: white !important; background: white !important;
} }
.day-of-week { .day-of-week{
text-transform: uppercase; text-transform: uppercase;
} }
@ -45,51 +43,50 @@
font-size: 13px; font-size: 13px;
} }
.handsontable td, .handsontable td, .handsontable th{
.handsontable th {
font-family: monospace; font-family: monospace;
color: var(--white); color: var(--white);
background: var(--black); background: var(--black);
} }
th { th{
color: gray !important; color: gray !important;
} }
td.color-black { td.color-black{
background: var(--black); background: var(--black);
} }
td.color-red { td.color-red{
background: var(--red); background: var(--red);
} }
td.color-blue { td.color-blue{
background: var(--blue); background: var(--blue);
} }
td.color-green { td.color-green{
background: var(--green); background: var(--green);
} }
td.color-purple { td.color-purple{
background: var(--purple); background: var(--purple);
} }
td.color-lime { td.color-lime{
background: var(--lime); background: var(--lime);
color: var(--black); color: var(--black);
} }
td.color-cyan { td.color-cyan{
background: var(--cyan); background: var(--cyan);
} }
td.color-darkred { td.color-darkred{
background: var(--darkred); background: var(--darkred);
} }
td.color-pink { td.color-pink{
background: var(--pink); background: var(--pink);
} }
td.color-orange { td.color-orange{
background: var(--orange); background: var(--orange);
} }
td.color-yellow { td.color-yellow{
background: var(--yellow); background: var(--yellow);
color: var(--black); color: var(--black);
} }
h2 { h2{
color: green; color:green;
} }

View File

@ -1,4 +1,4 @@
@import "./base.css"; @import './base.css';
#app { #app {
padding: 1em 2rem; padding: 1em 2rem;

View File

@ -8,7 +8,9 @@ storeSession.connect();
<template> <template>
<div v-if="!storeSession.isConnected"> <div v-if="!storeSession.isConnected">
<button @click="storeSession.loginAsRyan()">Connect</button> <button @click="storeSession.loginAsRyan()">
Connect
</button>
</div> </div>
<div v-if="storeSession.isConnected"> <div v-if="storeSession.isConnected">
Connected as {{ storeSession.session.userId }}. Connected as {{ storeSession.session.userId }}.

View File

@ -0,0 +1,20 @@
<script setup lang="ts">
defineProps<{
msg: string
}>()
</script>
<template>
<div class="greetings">
<h1 class="green">{{ msg }}</h1>
<h3>
Youve successfully created a project with
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
</h3>
</div>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,13 @@
<script setup lang="ts">
defineProps<{
year: string
}>()
</script>
<template>
<h1 class="green">{{ year }} Tracker Expanded</h1>
</template>
<style scoped>
</style>

View File

@ -0,0 +1,86 @@
<template>
<div class="item">
<i>
<slot name="icon"></slot>
</i>
<div class="details">
<h3>
<slot name="heading"></slot>
</h3>
<slot></slot>
</div>
</div>
</template>
<style scoped>
.item {
margin-top: 2rem;
display: flex;
}
.details {
flex: 1;
margin-left: 1rem;
}
i {
display: flex;
place-items: center;
place-content: center;
width: 32px;
height: 32px;
color: var(--color-text);
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-bottom: 0.4rem;
color: var(--color-heading);
}
@media (min-width: 1024px) {
.item {
margin-top: 0;
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
}
i {
top: calc(50% - 25px);
left: -26px;
position: absolute;
border: 1px solid var(--color-border);
background: var(--color-background);
border-radius: 8px;
width: 50px;
height: 50px;
}
.item:before {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
bottom: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:after {
content: ' ';
border-left: 1px solid var(--color-border);
position: absolute;
left: 0;
top: calc(50% + 25px);
height: calc(50% - 25px);
}
.item:first-of-type:before {
display: none;
}
.item:last-of-type:after {
display: none;
}
}
</style>

View File

@ -1,12 +1,12 @@
import { createApp } from "vue"; import { createApp } from 'vue';
import { createPinia } from "pinia"; import { createPinia } from 'pinia';
import App from "./App.vue"; import App from './App.vue';
import router from "./router"; import router from './router';
import "./assets/main.css"; import './assets/main.css';
const pinia = createPinia(); const pinia = createPinia();
const app = createApp(App).use(router).use(pinia); const app = createApp(App).use(router).use(pinia);
app.mount("#app"); app.mount('#app');

View File

@ -1,25 +1,39 @@
import { createRouter, createWebHistory } from "vue-router"; import { createRouter, createWebHistory } from 'vue-router'
import TableView from "../views/TableView.vue"; import TableView from '../views/TableView.vue'
import DatabaseView from "../views/DatabaseView.vue"; import DatabaseView from '../views/DatabaseView.vue'
import AboutView from '../views/AboutView.vue'
import { useSessionStore } from "../stores/session";
const router = createRouter({ const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL), history: createWebHistory(import.meta.env.BASE_URL),
routes: [ routes: [
{ {
path: "/", path: '/',
redirect: "/table", redirect: '/table'
}, },
{ {
path: "/table", path: '/table',
name: "table", name: 'table',
component: TableView, component: TableView
}, },
{ {
path: "/database", path: '/database',
name: "database", name: 'database',
component: DatabaseView, component: DatabaseView
}, }
], ]
}); })
export default router; export default router
// router.beforeEach(async (to) => {
// // redirect to login page if not logged in and trying to access a restricted page
// const publicPages = ['/database'];
// const authRequired = !publicPages.includes(to.path);
// const auth = useSessionStore();
// if (authRequired && !auth.session['id']) {
// return '/database';
// }
// });

View File

@ -1,7 +1,7 @@
import axios from "axios"; import axios from 'axios'
export default () => { export default() => {
return axios.create({ return axios.create({
baseURL: `http://localhost:8081`, baseURL: `http://localhost:8081`
}); })
}; }

View File

@ -2,98 +2,83 @@ import { Client, Databases, Account, Query } from "appwrite";
import { DateTime } from "luxon"; import { DateTime } from "luxon";
class AppwriteService { class AppwriteService {
databaseId = "lifetracker-db"; databaseId = 'lifetracker-db';
collectionId = "ryan"; collectionId = "ryan";
constructor() { constructor() {
this.appwrite = new Client() this.appwrite = new Client().setEndpoint("http://ryanpandya.com:8080/v1").setProject("lifetracker")
.setEndpoint("http://ryanpandya.com:8080/v1")
.setProject("lifetracker");
this.database = new Databases(this.appwrite); this.database = new Databases(this.appwrite);
this.account = new Account(this.appwrite); this.account = new Account(this.appwrite);
} }
subscribe = (callback) => { subscribe = (callback) => {
return this.appwrite.subscribe( return this.appwrite.subscribe('databases.lifetracker-db.collections.ryan.documents', callback);
"databases.lifetracker-db.collections.ryan.documents",
callback
);
// "databases.${this.databaseId}.collections.${this.collectionId}.documents", callback); // "databases.${this.databaseId}.collections.${this.collectionId}.documents", callback);
}; }
addEntry = async ({ date, hours, mood, comments }) => { addEntry = async ({ date, hours, mood, comments }) => {
await this.database.createDocument( await this.database.createDocument(this.databaseId, this.collectionId,
this.databaseId,
this.collectionId,
date, date,
{ date: new Date(date), hours, mood, comments } { date: new Date(date), hours: hours, mood: mood, comments: comments });
); }
};
deleteEntry = async (entryId) => { deleteEntry = async (entryId) => {
await this.database.deleteDocument( await this.database.deleteDocument(this.databaseId, this.collectionId, entryId);
this.databaseId, }
this.collectionId,
entryId
);
};
updateEntry = async ({ date, hours, mood, comments }) => { updateEntry = async ({ date, hours, mood, comments }) => {
hours = JSON.parse("[" + hours + "]"); var hours = JSON.parse("[" + hours + "]")
await this.database.updateDocument( await this.database.updateDocument(this.databaseId, this.collectionId,
this.databaseId,
this.collectionId,
date, date,
{ date: new Date(date), hours, mood, comments } { date: new Date(date), hours: hours, mood: mood, comments: comments });
); }
};
getUser = async () => { getUser = async () => {
return await this.account.get(); return await this.account.get();
}; }
login = async () => { login = async () => {
await this.account.createAnonymousSession(); await this.account.createAnonymousSession();
return await this.getUser(); return await this.getUser();
}; }
getEntries = async (date = null, numEntries = null) => { getEntries = async (date=null, numEntries=null) => {
if (date == null) { if(date == null){
date = DateTime.fromObject({ date = DateTime.fromObject({
year: DateTime.now().toFormat("y"), year: DateTime.now().toFormat("y"),
month: 1, month: 1,
day: 2, day: 2
}); });
} else { }
else{
date = DateTime.fromISO(date); date = DateTime.fromISO(date);
} }
const firstEntry = ( var firstEntry = (await this.database.listDocuments(
await this.database.listDocuments(this.databaseId, this.collectionId, [ this.databaseId, this.collectionId,
Query.orderAsc("date"), [Query.orderAsc("date"),Query.limit(1)])
Query.limit(1),
])
).documents[0]; ).documents[0];
const referenceDate = DateTime.fromISO(firstEntry.date).toUTC(); var referenceDate = DateTime.fromISO(firstEntry.date).toUTC();
const offset = Math.floor(date.diff(referenceDate).as("days")) - 1; var offset = Math.floor(date.diff(referenceDate).as("days")) - 1;
if (numEntries == null) { if (numEntries == null) {
numEntries = numEntries = Math.floor(DateTime.now().diff(referenceDate).as("days")) + 7;
Math.floor(DateTime.now().diff(referenceDate).as("days")) + 7;
} }
return ( return (await this.database.listDocuments(
await this.database.listDocuments(this.databaseId, this.collectionId, [ this.databaseId, this.collectionId,
[
Query.orderAsc("date"), Query.orderAsc("date"),
Query.offset(offset), Query.offset(offset),
Query.limit(numEntries), Query.limit(numEntries)
]) ]
)
).documents; ).documents;
}; }
logout = () => { logout = () => {
return this.appwrite.account.deleteSession("current"); return this.appwrite.account.deleteSession('current');
}; }
} }
export default new AppwriteService(); export default new AppwriteService()

View File

@ -1,23 +1,23 @@
import type { IEntry } from "@/types/entry"; import type { IEntry } from "@/types/entry";
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import Api from "@/services/Api"; import Api from "@/services/Api"
export const useDatabaseStore = defineStore({ export const useDatabaseStore = defineStore({
id: "databaseState", id: 'databaseState',
state: () => ({ state: () => ({
entries: [] as IEntry[], entries: [] as IEntry[]
}), }),
getters: { getters: {
length: (state) => state.entries.length, length: (state) => state.entries.length
}, },
actions: { actions: {
async fetchEntries() { async fetchEntries(){
try { try{
const response = await Api().get("database"); const response = await Api().get("database");
this.entries = response.data; this.entries = response.data;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
}, }
}, }
}); })

View File

@ -1,33 +1,34 @@
import type { ISession } from "@/types/session"; import type { ISession } from '@/types/session';
import { defineStore } from "pinia"; import { defineStore } from 'pinia';
import { Client, Account } from "appwrite"; import { Client, Account, ID } from 'appwrite';
const appwriteclient = new Client() const appwriteclient = new Client()
.setEndpoint("http://ryanpandya.com:8080/v1") .setEndpoint('http://ryanpandya.com:8080/v1')
.setProject("lifetracker"); .setProject('lifetracker');
const account = new Account(appwriteclient); const account = new Account(appwriteclient);
export const useSessionStore = defineStore({ export const useSessionStore = defineStore({
id: "sessionState", id: 'sessionState',
state: () => ({ state: () => ({
session : {} as ISession session: [] as ISession[],
}), }),
getters: { getters: {
isConnected: (state) => state.session.userId, isConnected: (state) => state.session['userId'],
}, },
actions: { actions: {
logout() { logout() {
const promise = account.deleteSession(this.session.id); const promise = account.deleteSession(this.session['id']);
const self = this; var self = this;
promise.then( promise.then(
function (response) { function (response) {
self.session = self.session = [
{ {
email: "", email: '',
userId: "", userId: '',
id: "", id: '',
} },
];
console.log(response); // Success console.log(response); // Success
}, },
function (error) { function (error) {
@ -36,49 +37,52 @@ export const useSessionStore = defineStore({
); );
}, },
connect() { connect() {
const promise = account.getSession("current"); const promise = account.getSession('current');
const session: ISession = { var session: ISession = {
email: "", email: '',
userId: "", userId: '',
id: "", id: '',
}; };
const self = this; var self = this;
promise.then( promise.then(
function (response) { function (response) {
session.email = response.providerUid; session.email = response.providerUid;
session.userId = response.userId; session.userId = response.userId;
session.id = response.$id; session.id = response.$id;
console.log("Connected to existing session"); console.log('Connected to existing session');
self.session = session; self.session = session;
}, },
function (error) { function (error) {
console.log("No existing session; starting fresh."); session.email = null;
session.userId = null;
session.id = '';
console.log('No existing session; starting fresh.');
self.session = session; self.session = session;
} }
); );
}, },
loginAsRyan() { loginAsRyan() {
this.login("ryan@ryanpandya.com", "A(84)o9@38appwrite"); this.login('ryan@ryanpandya.com', 'A(84)o9@38appwrite');
}, },
login(email: string, password: string) { login(email: string, password: string) {
console.log(this.session); console.log(this.session);
const promise = account.createEmailSession(email, password); const promise = account.createEmailSession(email, password);
const session: ISession = { var session: ISession = {
email: "", email: '',
userId: "", userId: '',
id: "", id: '',
}; };
const self = this; var self = this;
promise.then( promise.then(
function (response) { function (response) {
session.email = response.providerUid; session.email = response.providerUid;
session.userId = response.userId; session.userId = response.userId;
session.id = response.$id; session.id = response.$id;
console.log("Logged in"); console.log('Logged in');
self.session = session; self.session = session;
}, },
function (error) { function (error) {
console.log("Error"); console.log('Error');
} }
); );
}, },

View File

@ -1,6 +1,6 @@
export interface IEntry { export interface IEntry {
date: Date; date: Date,
hours: Number[]; hours: Number[],
mood: Number; mood: Number,
note: String; note: String
} }

View File

@ -1,33 +1,33 @@
<script> <script>
import { defineComponent } from "vue"; import { defineComponent } from 'vue';
import { HotTable } from "@handsontable/vue3"; import { HotTable } from '@handsontable/vue3';
import { registerAllModules } from "handsontable/registry"; import { registerAllModules } from 'handsontable/registry';
import "handsontable/dist/handsontable.full.css"; import 'handsontable/dist/handsontable.full.css';
// register Handsontable's modules // register Handsontable's modules
registerAllModules(); registerAllModules();
const ExampleComponent = defineComponent({ const ExampleComponent = defineComponent({
components: {
HotTable,
},
data() { data() {
return { return {
hotSettings: { hotSettings: {
data: [ data: [
["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", "I1", "J1"], ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1', 'H1', 'I1', 'J1'],
["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", "I2", "J2"], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2', 'H2', 'I2', 'J2'],
["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", "I3", "J3"], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3', 'H3', 'I3', 'J3'],
["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", "I4", "J4"], ['A4', 'B4', 'C4', 'D4', 'E4', 'F4', 'G4', 'H4', 'I4', 'J4'],
["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", "I5", "J5"], ['A5', 'B5', 'C5', 'D5', 'E5', 'F5', 'G5', 'H5', 'I5', 'J5'],
["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", "I6", "J6"], ['A6', 'B6', 'C6', 'D6', 'E6', 'F6', 'G6', 'H6', 'I6', 'J6'],
], ],
colHeaders: true, colHeaders: true,
height: "auto", height: 'auto',
licenseKey: "non-commercial-and-evaluation", licenseKey: 'non-commercial-and-evaluation'
}, }
}; };
}, },
components: {
HotTable,
}
}); });
export default ExampleComponent; export default ExampleComponent;
@ -38,4 +38,5 @@ export default ExampleComponent;
</div> </div>
</template> </template>
<style></style> <style>
</style>

View File

@ -0,0 +1,9 @@
<script setup lang="ts">
import TheWelcome from '../components/TheWelcome.vue'
</script>
<template>
<main>
<TheWelcome />
</main>
</template>

View File

@ -1,32 +1,34 @@
<script setup> <script setup>
import appwrite from "@/services/appwrite"; import { useDatabaseStore } from "@/stores/database"
import Api from "@/services/Api"
import appwrite from '@/services/appwrite';
import { DateTime } from "luxon"; import { DateTime } from "luxon";
</script> </script>
<script> <script>
import { defineComponent } from "vue"; import { defineComponent, toRaw, reactive } from 'vue';
import { HotTable } from "@handsontable/vue3"; import { HotTable } from '@handsontable/vue3';
import { registerAllModules } from "handsontable/registry"; import { registerAllModules } from 'handsontable/registry';
import "handsontable/dist/handsontable.full.css"; import 'handsontable/dist/handsontable.full.css';
import "../assets/colors.css"; import '../assets/colors.css';
import Handsontable from 'handsontable/base';
// register Handsontable's modules // register Handsontable's modules
registerAllModules(); registerAllModules();
// function debounce(func, wait, immediate) { function debounce(func, wait, immediate) {
// let timeout; var timeout;
// return function () { return function() {
// const context = this; var context = this, args = arguments;
// const args = arguments; var later = function() {
// const later = function () { timeout = null;
// timeout = null; if (!immediate) func.apply(context, args);
// if (!immediate) func.apply(context, args); };
// }; var callNow = immediate && !timeout;
// const callNow = immediate && !timeout; clearTimeout(timeout);
// clearTimeout(timeout); timeout = setTimeout(later, wait);
// timeout = setTimeout(later, wait); if (callNow) func.apply(context, args);
// if (callNow) func.apply(context, args); };
// }; };
// }
function getHourClass(i) { function getHourClass(i) {
const colorMapping = { const colorMapping = {
@ -40,14 +42,15 @@ function getHourClass(i) {
7: "orange", 7: "orange",
8: "purple", 8: "purple",
9: "darkred", 9: "darkred",
10: "lime", 10: "lime"
}; }
if (i > 10) { if(i > 10){
i = Math.ceil(i / 10) - 1; i = Math.ceil(i/10) - 1
} }
if (i == null) { if (i == null) {
return ""; return "";
} else { }
else {
return "color-" + colorMapping[i]; return "color-" + colorMapping[i];
} }
} }
@ -55,24 +58,21 @@ function getHourClass(i) {
function hourCol(t) { function hourCol(t) {
return { return {
data: "hours." + t, data: "hours." + t,
type: "numeric", type: 'numeric',
renderer(instance, td, row, col, prop, value) { renderer(instance, td, row, col, prop, value) {
td.className = getHourClass(value); td.className = getHourClass(value);
td.innerText = value == null ? value : parseFloat(value); td.innerText = value == null ? value : parseFloat(value);
return td; return td;
}, }
}; }
} }
const colHeaders = ["DATE", "DAY", "12 AM"] const colHeaders = ["DATE", "DAY", "12 AM"]
.concat(Array.from(new Array(11), (x, y) => y + 1 + " AM")) .concat(Array.from(new Array(11), (x, y) => y+1 + " AM") )
.concat(Array.from(new Array(12), (x, y) => y + 1 + " PM")) .concat(Array.from(new Array(12), (x, y) => y+1 + " PM") )
.concat(["Mood", "Comments"]); .concat(["Mood", "Comments"])
const ExampleComponent = defineComponent({ const ExampleComponent = defineComponent({
components: {
HotTable,
},
data() { data() {
return { return {
entries: [], entries: [],
@ -85,248 +85,223 @@ const ExampleComponent = defineComponent({
readOnly: true, readOnly: true,
data: "date", data: "date",
renderer(instance, td, row, col, prop, value) { renderer(instance, td, row, col, prop, value) {
const date = DateTime.fromISO(value).toUTC().toFormat("MM/dd"); var date = DateTime.fromISO(value).toUTC().toFormat("MM/dd");
const today = DateTime.now().toFormat("MM/dd"); var today = DateTime.now().toFormat("MM/dd");
if (today === date) { if(today == date){
td.className = "color-black invert"; td.className = "color-black invert";
} else {
td.className = "color-black";
} }
else{td.className = "color-black";}
td.innerText = date; td.innerText = date;
return td; return td;
}, }
}, },
{ {
readOnly: true, readOnly: true,
data: "date", data: "date",
renderer(instance, td, row, col, prop, value) { renderer(instance, td, row, col, prop, value) {
const date = DateTime.fromISO(value).toUTC(); var date = DateTime.fromISO(value).toUTC();
const today = DateTime.now().toFormat("MM/dd"); var today = DateTime.now().toFormat("MM/dd");
if (date.toFormat("MM/dd") === today) { if(date.toFormat("MM/dd") == today){
td.className = "color-black invert day-of-week"; td.className = "color-black invert day-of-week";
} else {
td.className = "color-black day-of-week";
} }
else{td.className = "color-black day-of-week";}
td.innerText = date.toFormat("EEE"); td.innerText = date.toFormat("EEE");
return td; return td;
}, }
}, },
Array.from(new Array(24), (_, t) => hourCol(t)), Array.from(new Array(24), (_, t) => hourCol(t)),
{ data: "mood", type: "numeric" }, { data: "mood", type: "numeric" },
{ data: "comments", className: "align-left" }, { data: "comments", className: "align-left" }
].flat(), ].flat(),
colHeaders, colHeaders: colHeaders,
rowHeaders: false, rowHeaders: false,
readOnly: false, readOnly: false,
width: "100%", width: '100%',
height: "100%", height: '100%',
rowHeights: "22px", rowHeights: '22px',
colWidths(i) { colWidths(i) {
if (i > 1 && i < 26) { if((i > 1) && (i < 26)){
return "50px"; return "50px";
} else if (i === 27) { }
else if(i == 27){
return "1000px"; return "1000px";
} else { }
else{
return "50px"; return "50px";
} }
}, },
afterSelection: ( afterSelection: (row, column, row2, column2, preventScrolling, selectionLayerLevel) => {
row,
column, },
row2,
column2,
preventScrolling,
selectionLayerLevel
) => {},
afterChange: (changes) => { afterChange: (changes) => {
this.fixSelectionBug(); this.fixSelectionBug();
// this.setActiveHourHeader(); //this.setActiveHourHeader();
if (changes != null) { if (changes != null) {
const entry = this.entries[changes[0][0]]; var entry = this.entries[changes[0][0]];
entry.date = entry.date.replace(/T.*/, ""); entry.date = entry.date.replace(/T.*/, "");
appwrite.updateEntry(entry); appwrite.updateEntry(entry);
} }
}, },
licenseKey: "non-commercial-and-evaluation", licenseKey: 'non-commercial-and-evaluation'
}, },
hotRef: null, hotRef: null
}; }
}, },
async mounted() { async mounted(){
this.user = await appwrite.getUser(); this.user = await appwrite.getUser();
this.entries = await appwrite.getEntries(); this.entries = await appwrite.getEntries();
this.updateTable(); this.updateTable();
this.subscribe(); this.subscribe();
this.fixSelectionBug(); this.fixSelectionBug();
// this.lazyLoadEntries(); //this.lazyLoadEntries();
this.scrollToEnd(); this.scrollToEnd();
this.selectCurrentCell(); this.selectCurrentCell();
}, },
created() { created () {
// window.addEventListener('wheel', debounce(this.handleScroll, 500, true)); // window.addEventListener('wheel', debounce(this.handleScroll, 500, true));
}, },
unmounted() { unmounted () {
// window.removeEventListener('wheel', debounce(this.handleScroll, 500, true)); // window.removeEventListener('wheel', debounce(this.handleScroll, 500, true));
}, },
updated() { updated() {
this.scrollToEnd(); this.scrollToEnd();
}, },
methods: { methods: {
selectCurrentCell() { selectCurrentCell(){
const nowRow = var nowRow = this.entries.findIndex((e) =>
this.entries.findIndex( DateTime.fromISO(e.date).toISODate() == DateTime.now().toISODate()) - 1;
(e) => var nowCol = parseInt(DateTime.now().toFormat("HH")) + 1;
DateTime.fromISO(e.date).toISODate() === DateTime.now().toISODate()
) - 1;
const nowCol = parseInt(DateTime.now().toFormat("HH")) + 1;
this.hotRef.selectCell(nowRow, nowCol); this.hotRef.selectCell(nowRow, nowCol);
}, },
scrollToEnd() { scrollToEnd() {
this.hotRef.scrollViewportTo(this.entries.length - 10); this.hotRef.scrollViewportTo(
this.entries.length - 10
);
}, },
async lazyLoadEntries() { async lazyLoadEntries(){
const earliestDate = DateTime.fromObject({ var earliestDate = DateTime.fromObject({
year: DateTime.now().toFormat("y"), year: DateTime.now().toFormat("y"),
month: 1, month: 1,
day: 1, day: 1
}); });
const numEntries = Math.floor( var numEntries = Math.floor(DateTime.now().diff(earliestDate).as("days"));
DateTime.now().diff(earliestDate).as("days") console.log("Grabbing " + numEntries + " entries starting from " + earliestDate.toISODate());
); var newEntries = await appwrite.getEntries(
console.log( earliestDate.plus({days: 1}),
"Grabbing " + numEntries);
numEntries + var entries = this.entries;
" entries starting from " +
earliestDate.toISODate()
);
const newEntries = await appwrite.getEntries(
earliestDate.plus({ days: 1 }),
numEntries
);
const entries = this.entries;
newEntries.reverse().forEach((e) => { newEntries.reverse().forEach((e) => {
entries.unshift(e); entries.unshift(e);
}); })
this.entries = entries; this.entries = entries;
this.hotRef.scrollViewportTo(129); this.hotRef.scrollViewportTo(129);
}, },
setActiveHourHeader() { setActiveHourHeader(){
const timeString = DateTime.now().toFormat("h a"); var timeString = DateTime.now().toFormat("h a");
console.log(timeString); console.log(timeString);
const moot = Array.from(document.querySelectorAll("th")); var moot = Array.from(document.querySelectorAll("th"));
const poot = moot.filter((e) => { var poot = moot.filter((e) => { return e.innerText == timeString});
return e.innerText === timeString;
});
poot[0].classList.add("active-hour"); poot[0].classList.add("active-hour");
}, },
async handleScroll(e) { async handleScroll(e){
if (e.deltaY > 0) { if(e.deltaY > 0){
const lastDate = DateTime.fromISO( var lastDate = DateTime.fromISO(
this.entries[this.hotRef.getPlugin("autoRowSize").getLastVisibleRow()] this.entries[
.date this.hotRef
) .getPlugin('autoRowSize')
.toUTC() .getLastVisibleRow()
.plus({ days: 1 }); ].date).toUTC().plus({ days: 1 });
console.log("Grabbing 5 more entries starting at " + lastDate.toISO()); console.log("Grabbing 5 more entries starting at " + lastDate.toISO());
const newEntries = await appwrite.getEntries( var newEntries = await appwrite.getEntries(
lastDate.plus({ days: 1 }), lastDate.plus({ days: 1 })
5 , 5);
);
newEntries.forEach((e) => { newEntries.forEach((e) => {
this.entries.push(e); this.entries.push(e);
}); })
} else { }
const firstDate = DateTime.fromISO( else{
var firstDate = DateTime.fromISO(
this.entries[ this.entries[
this.hotRef.getPlugin("autoRowSize").getFirstVisibleRow() this.hotRef
].date .getPlugin('autoRowSize')
) .getFirstVisibleRow()
.toUTC() ].date).toUTC().minus({ days: 1 });
.minus({ days: 1 }); console.log("Grabbing 5 previous entries ending at " + firstDate.toISO());
console.log( var newEntries = await appwrite.getEntries(
"Grabbing 5 previous entries ending at " + firstDate.toISO() firstDate.minus({ days: 3 })
); , 5);
const newEntries = await appwrite.getEntries( var entries = this.entries;
firstDate.minus({ days: 3 }),
5
);
const entries = this.entries;
newEntries.reverse().forEach((e) => { newEntries.reverse().forEach((e) => {
entries.unshift(e); entries.unshift(e);
}); })
this.entries = entries; this.entries = entries;
} }
}, },
fixSelectionBug() { fixSelectionBug(){
if (this.hotRef) { if(this.hotRef){
const offset = var offset = (this.hotRef.getRowHeight()) * (this.entries.length + 1) + 2;//document.querySelector(".wtHider").clientHeight;
this.hotRef.getRowHeight() * (this.entries.length + 1) + 2; // document.querySelector(".wtHider").clientHeight; document.querySelector(".htBorders div").style.top = "-" + offset + "px";
document.querySelector(".htBorders div").style.top =
"-" + offset + "px";
} }
}, },
subscribe() { subscribe(){
console.log("Subscribing to realtime.");
appwrite.subscribe((payload) => { appwrite.subscribe((payload) => {
const event = payload.events var event = payload.events.filter((e) =>
.filter((e) =>
e.match(/databases\.\*\.collections\.\*\.documents\.\*\.\w+/) e.match(/databases\.\*\.collections\.\*\.documents\.\*\.\w+/)
)[0] )[0].replace(/.+\./,"");
.replace(/.+\./, "");
switch (event) { switch (event) {
case "create": case 'create':
this.entries.push(payload.payload); this.entries.push(payload.payload)
this.updateTable(); this.entries = this.entries
break; break
case "update": case 'update':
console.log("Updating");
this.entries = this.entries.map((day) => { this.entries = this.entries.map((day) => {
if (day.$id === payload.payload.$id) { if (day.$id === payload.payload.$id) {
return payload.payload; return payload.payload
} else { } else {
return day; return day
} }
}); })
this.updateTable(); break
break; case 'delete':
case "delete": this.entries = this.entries.filter((day) => day.$id !== payload.payload.$id)
this.entries = this.entries.filter( break
(day) => day.$id !== payload.payload.$id
);
this.updateTable();
break;
} }
}); })
}, },
updateTable() { updateTable(){
this.hotRef = this.$refs.wrapper.hotInstance; this.hotRef = this.$refs.wrapper.hotInstance;
this.hotRef.loadData(this.entries); this.hotRef.loadData(this.entries);
}, },
rewrite(d) { rewrite(d){
const emptyEntry = { var emptyEntry = {
date: d.toISODate(), date: d.toISODate(),
hours: [], hours: [],
mood: null, mood: null,
comments: "", comments: ""
}; }
appwrite.updateEntry(emptyEntry); appwrite.updateEntry(emptyEntry);
console.log("Updated " + d.toISODate()); console.log("Updated " + d.toISODate());
}, },
rewriteEntries() { rewriteEntries(){
const startDate = DateTime.fromISO("2023-12-10"); const startDate = DateTime.fromISO("2023-12-10");
const endDate = DateTime.fromISO("2023-12-31"); const endDate = DateTime.fromISO("2023-12-31");
for (let d = startDate; d <= endDate; d = d.plus({ days: 1 })) { for(var d = startDate; d <= endDate; d = d.plus({days: 1})){
setTimeout(this.rewrite(d), 500); setTimeout(this.rewrite(d), 500);
} }
}
}, },
}, components: {
HotTable,
}
}); });
export default ExampleComponent; export default ExampleComponent;
</script> </script>
<template> <template>
<!-- <div>{{ entries }}</div> <button @click="this.setActiveHourHeader()">Fuck</button>
<!-- <div>{{ entries }}</div>
<ul> <ul>
<li v-for="e in entries" :key="e"> <li v-for="e in entries" :key="e">
@ -334,12 +309,10 @@ export default ExampleComponent;
</li> </li>
</ul> --> </ul> -->
<div id="table"> <div id="table">
<hot-table <hot-table ref="wrapper" :settings="hotSettings" :data="entries"></hot-table>
ref="wrapper"
:settings="hotSettings"
:data="entries"
></hot-table>
</div> </div>
</template> </template>
<style></style> <style>
</style>

View File

@ -6,9 +6,7 @@
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },
"noImplicitAny": false, "noImplicitAny": false
"ignoreDeprecations": "5.0",
"allowJs": true
}, },
"references": [ "references": [

View File

@ -1,14 +1,8 @@
{ {
"extends": "@vue/tsconfig/tsconfig.node.json", "extends": "@vue/tsconfig/tsconfig.node.json",
"include": [ "include": ["vite.config.*", "vitest.config.*", "cypress.config.*", "playwright.config.*"],
"vite.config.*",
"vitest.config.*",
"cypress.config.*",
"playwright.config.*"
],
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"types": ["node"], "types": ["node"]
"ignoreDeprecations": "5.0"
} }
} }

View File

@ -1,24 +1,14 @@
import { fileURLToPath, URL } from "node:url"; import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from "vite";
import eslintPlugin from "vite-plugin-eslint"; import { defineConfig } from 'vite'
import vue from "@vitejs/plugin-vue"; import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {
"@": fileURLToPath(new URL("./src", import.meta.url)), '@': fileURLToPath(new URL('./src', import.meta.url))
},
},
root: "src",
build: {
outDir: "../../lifetracker-server/dist",
emptyOutDir: true,
},
server: {
hmr: {
overlay: false
} }
} }
}); })