From a8e3eaea46ef3f3fe088dc7cd9bde3123b779865 Mon Sep 17 00:00:00 2001 From: thepaperpilot Date: Sun, 7 Apr 2024 17:02:19 -0500 Subject: [PATCH] WIP --- .eslintignore | 1 + .eslintrc.cjs | 56 ++++++ .gitignore | 1 + .prettierrc.json | 7 + README.md | 9 + bun.lockb | Bin 0 -> 120689 bytes index.html | 16 ++ package.json | 34 ++++ postcss.config.js | 6 + public/vite.svg | 1 + src/App.vue | 16 ++ src/assets/vue.svg | 1 + src/components/Avatar.vue | 32 ++++ src/components/CategoryHeader.vue | 17 ++ src/components/Item.vue | 80 ++++++++ src/components/ItemGroup.vue | 22 +++ src/components/Modal.vue | 134 ++++++++++++++ src/components/Nav.vue | 191 +++++++++++++++++++ src/components/Thread.vue | 65 +++++++ src/components/Todo.vue | 67 +++++++ src/main.ts | 18 ++ src/state.ts | 294 ++++++++++++++++++++++++++++++ src/style.css | 13 ++ src/utils.ts | 17 ++ src/vite-env.d.ts | 1 + tailwind.config.js | 15 ++ tsconfig.json | 27 +++ tsconfig.node.json | 11 ++ vite.config.ts | 8 + 29 files changed, 1160 insertions(+) create mode 100644 .eslintignore create mode 100644 .eslintrc.cjs create mode 100644 .gitignore create mode 100644 .prettierrc.json create mode 100644 README.md create mode 100644 bun.lockb create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/vite.svg create mode 100644 src/App.vue create mode 100644 src/assets/vue.svg create mode 100644 src/components/Avatar.vue create mode 100644 src/components/CategoryHeader.vue create mode 100644 src/components/Item.vue create mode 100644 src/components/ItemGroup.vue create mode 100644 src/components/Modal.vue create mode 100644 src/components/Nav.vue create mode 100644 src/components/Thread.vue create mode 100644 src/components/Todo.vue create mode 100644 src/main.ts create mode 100644 src/state.ts create mode 100644 src/style.css create mode 100644 src/utils.ts create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 vite.config.ts diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..cf64a52 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +.eslintrc.cjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..b13d1ba --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,56 @@ +require("@rushstack/eslint-patch/modern-module-resolution"); + +module.exports = { + root: true, + env: { + node: true + }, + parser: '@typescript-eslint/parser', + plugins: ["@typescript-eslint"], + overrides: [ + { + files: ['*.ts', '*.tsx'], + extends: [ + "plugin:vue/vue3-essential", + "@vue/eslint-config-typescript/recommended", + "@vue/eslint-config-prettier" + ], + parserOptions: [ + { + ecmaVersion: 2020, + project: "./tsconfig.json" + }, + { + ecmaVersion: 2020, + project: "./tsconfig.node.json" + }, + ], + } + ], + ignorePatterns: ["src/lib"], + rules: { + "no-console": process.env.NODE_ENV === "production" ? "warn" : "off", + "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off", + "vue/script-setup-uses-vars": "warn", + "vue/no-mutating-props": "off", + "vue/multi-word-component-names": "off", + "@typescript-eslint/strict-boolean-expressions": [ + "error", + { + allowNullableObject: true, + allowNullableBoolean: true + } + ], + "eqeqeq": [ + "error", + "always", + { + "null": "never" + } + ] + }, + globals: { + defineProps: "readonly", + defineEmits: "readonly" + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..008a2f9 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,7 @@ +{ + "arrowParens": "avoid", + "endOfLine": "auto", + "printWidth": 100, + "tabWidth": 4, + "trailingComma": "none" +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..0bfecb0 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Vue 3 + TypeScript + Vite + +This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 ` + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..60bb06d --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "commune-mock", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.5.1", + "@fortawesome/free-solid-svg-icons": "^6.5.1", + "@fortawesome/vue-fontawesome": "latest-3", + "autoprefixer": "^10.4.19", + "flowbite": "^2.3.0", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.3", + "vue": "^3.4.21", + "vue-router": "4" + }, + "devDependencies": { + "@rushstack/eslint-patch": "^1.1.0", + "@typescript-eslint/parser": "^7.2.0", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/eslint-config-prettier": "^9.0.0", + "@vue/eslint-config-typescript": "^13.0.0", + "eslint": "^8.6.0", + "typescript": "^5.2.2", + "prettier": "^3.2.5", + "vite": "^5.2.0", + "vue-tsc": "^2.0.6" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2e7af2b --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/vite.svg b/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..66cb2a0 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/src/assets/vue.svg b/src/assets/vue.svg new file mode 100644 index 0000000..770e9d3 --- /dev/null +++ b/src/assets/vue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/Avatar.vue b/src/components/Avatar.vue new file mode 100644 index 0000000..e394cae --- /dev/null +++ b/src/components/Avatar.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/components/CategoryHeader.vue b/src/components/CategoryHeader.vue new file mode 100644 index 0000000..e309b27 --- /dev/null +++ b/src/components/CategoryHeader.vue @@ -0,0 +1,17 @@ + + + diff --git a/src/components/Item.vue b/src/components/Item.vue new file mode 100644 index 0000000..29698a0 --- /dev/null +++ b/src/components/Item.vue @@ -0,0 +1,80 @@ + + + + + \ No newline at end of file diff --git a/src/components/ItemGroup.vue b/src/components/ItemGroup.vue new file mode 100644 index 0000000..c69257b --- /dev/null +++ b/src/components/ItemGroup.vue @@ -0,0 +1,22 @@ + + + \ No newline at end of file diff --git a/src/components/Modal.vue b/src/components/Modal.vue new file mode 100644 index 0000000..143703a --- /dev/null +++ b/src/components/Modal.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/components/Nav.vue b/src/components/Nav.vue new file mode 100644 index 0000000..5ecd4af --- /dev/null +++ b/src/components/Nav.vue @@ -0,0 +1,191 @@ + + + + + diff --git a/src/components/Thread.vue b/src/components/Thread.vue new file mode 100644 index 0000000..90a0b30 --- /dev/null +++ b/src/components/Thread.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/components/Todo.vue b/src/components/Todo.vue new file mode 100644 index 0000000..5d6c6b2 --- /dev/null +++ b/src/components/Todo.vue @@ -0,0 +1,67 @@ + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..f68c8ac --- /dev/null +++ b/src/main.ts @@ -0,0 +1,18 @@ +import { createApp } from "vue"; +import { createMemoryHistory, createRouter } from "vue-router"; +import type { RouteRecordRaw } from "vue-router"; +import "./style.css"; +import App from "./App.vue"; +import Todo from "./components/Todo.vue"; + +const routes: RouteRecordRaw[] = [ + { path: '/', component: Todo }, + { path: '/todo/:source(\\d+)?/:sourceItem(\\d+)?/:thread(\\d+)?', component: Todo }, +]; + +const router = createRouter({ + history: createMemoryHistory(), + routes +}); + +createApp(App).use(router).mount("#app"); diff --git a/src/state.ts b/src/state.ts new file mode 100644 index 0000000..57550fb --- /dev/null +++ b/src/state.ts @@ -0,0 +1,294 @@ +import { computed, ref, watch } from "vue"; +import { mergeSortedLists } from "./utils"; + +export type SourceType = "email" | "matrix" | "xmpp" | "rss" | "site"; + +export interface Item { + source: number; + sourceItem: number; + updatedAt: number; + threads: Record; +} + +export interface ItemThread { + category?: number; + count: number; + preview: string; + contact?: number; + snoozedUntil?: number; + updatedAt: number; +} + +export interface SourceItem { + id: number; + title: string; + image?: string; + contacts?: number[]; + threads: Record; +} + +export interface SourceThread { + id: number; + title: string; + timeline: ThreadEvent[]; +} + +export interface MessageEvent { + type: "message"; + contact: number; + time: number; + message: string; +} + +export interface CreateThreadEvent { + type: "create-thread"; + contact: number; + time: number; + thread: number; +} + +export type ThreadEvent = MessageEvent | CreateThreadEvent; + +export type ContactStatus = "online" | "away" | "offline" | "unknown"; + +export interface Contact { + id: number; + name: string; + image?: string; + status: ContactStatus; +} + +export interface Source { + id: number; + name: string; + type: SourceType; + items: Record; + contacts: Record; + selfContact: number; +} + +export interface LogicalRule { + type: "any" | "all" | "none"; + rules: Rule[]; +} + +export interface IsDmRule { + type: "isDM"; + inverted: boolean; +} + +export type Rule = LogicalRule | IsDmRule; + +export type Priority = "urgent" | "notify" | "todo" | "garden"; + +export interface Category { + id: number; + name: string; + priority: Priority; + activeItems: Item[]; +} + +export interface ThreadRef { + source: number; + sourceItem: number; + thread: number; +} + +export const sources = ref>({ + 0: { + id: 0, + items: { + 0: { + id: 0, + title: "Test Person", + threads: { + 0: { + id: 0, + title: "Thread 1", + timeline: [ + { type: "message", contact: 0, time: Date.now(), message: "Howdy!" } + ] + } + } + }, + 1: { + id: 1, + title: "Announcements", + threads: { + 0: { + id: 0, + title: "Thread 2", + timeline: [ + { type: "message", contact: 0, time: Date.now(), message: "Howdy!" } + ] + } + } + }, + 2: { + id: 2, + title: "C", + threads: { + 0: { + id: 0, + title: "Thread 3", + timeline: [ + { type: "message", contact: 0, time: Date.now(), message: "Howdy!" } + ] + } + } + }, + 3: { + id: 3, + title: "Another Person", + threads: { + 0: { + id: 0, + title: "Thread 4", + timeline: [ + { type: "message", contact: 0, time: Date.now(), message: "Howdy!" }, + { type: "message", contact: 1, time: Date.now(), message: "Howdy back!" }, + { type: "message", contact: 0, time: Date.now(), message: "Howdy 2!" }, + { type: "message", contact: 0, time: Date.now(), message: "Howdy 3!" }, + { type: "create-thread", contact: 0, time: Date.now(), thread: 1 } + ] + }, + 1: { + id: 1, + title: "Side Thread", + timeline: [ + { type: "message", contact: 0, time: Date.now(), message: "Howdy!" } + ] + } + } + } + }, + name: "Test Source", + type: "matrix", + contacts: { + 0: { + id: 0, + name: "Test Person", + status: "online", + image: "https://picsum.photos/seed/avatar0/64" + }, + 1: { + id: 1, + name: "Me", + status: "online", + image: "https://picsum.photos/seed/avatar1/64" + } + }, + selfContact: 1 + } +}); +export const items = ref([ + { + source: 0, + sourceItem: 0, + updatedAt: Date.now(), + threads: { + 0: { + category: 0, + count: 1, + preview: "Did you see the announcement?", + contact: 0, + updatedAt: Date.now() + } + } + }, + { + source: 0, + sourceItem: 1, + updatedAt: Date.now() - 60 * 1000, + threads: { + 0: { + category: 0, + count: 2, + preview: "Fire!", + updatedAt: Date.now() - 60 * 1000, + } + } + }, + { + source: 0, + sourceItem: 3, + updatedAt: Date.now(), + threads: { + 0: { + category: 1, + count: 6, + preview: "Did you get that?", + contact: 0, + updatedAt: Date.now() + }, + 1: { + category: 1, + count: 2, + preview: "Let's circle back", + updatedAt: Date.now() + } + } + } +]); +export const unsortedItems = ref([]); +export const rules = ref<{ category?: string; rules: Rule[] }[]>([]); +export const categories = ref([ + { + id: 0, + name: "Urgent", + priority: "urgent", + activeItems: [] + }, + { + id: 1, + name: "DMs", + priority: "notify", + activeItems: [] + } +]); +export const favorites = ref([ + { source: 0, sourceItem: 2, thread: 0 }, + { source: 0, sourceItem: 1, thread: 0 }, + { source: 0, sourceItem: 3, thread: 0 } +]); +export const urgentItems = computed(() => + categories.value + .filter(cat => cat.priority === "urgent") + .reduce((acc, curr) => mergeSortedLists(acc, curr.activeItems), [] as Item[]) +); +export const todoItems = computed(() => + todoCategories.value + .reduce((acc, curr) => mergeSortedLists(acc, curr.activeItems), [] as Item[]) +); +export const todoCategories = computed(() => categories.value.filter(c => ["urgent", "notify", "todo"].includes(c.priority))); + +watch( + items, + items => { + const mappedItems: Record = {}; + const unsorted: Item[] = []; + + items.forEach(item => { + let includeInUnsorted = false; + const includeInCategories = new Set(); + Object.values(item.threads).forEach(t => { + if (t.category == null) { + includeInUnsorted = true; + } else { + includeInCategories.add(t.category); + } + }); + if (includeInUnsorted) { + unsorted.push(item); + } + includeInCategories.forEach(cat => mappedItems[cat] = [...(mappedItems[cat] ?? []), item]); + }); + + categories.value.forEach(cat => { + // Noting here that activeItems must be sorted by most recently updated + cat.activeItems = mappedItems[cat.id]?.sort((a, b) => a.updatedAt - b.updatedAt) ?? []; + }); + unsortedItems.value = unsorted; + }, + { immediate: true } +); diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..9dc7a22 --- /dev/null +++ b/src/style.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +.center { + top: 50%; + left: 50%; + transform: translate(-50%, -50%); +} + +* { + transition: all .2s ease; +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..16a00f3 --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,17 @@ +// Modified from https://www.basedash.com/blog/how-to-merge-two-sorted-lists-in-javascript +export function mergeSortedLists(arr1: Array, arr2: Array): Array { + const merged: Array = []; + let i = 0, + j = 0; + + while (i < arr1.length && j < arr2.length) { + if (arr1[i] < arr2[j]) { + merged.push(arr1[i++]); + } else { + merged.push(arr2[j++]); + } + } + + // Append any remaining elements from arr1 or arr2 + return [...merged, ...arr1.slice(i), ...arr2.slice(j)]; +} diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..4d0911a --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,15 @@ + /** @type {import('tailwindcss').Config} */ +export default { + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + "./node_modules/flowbite/**/*.js" + ], + theme: { + extend: {} + }, + plugins: [ + require('flowbite/plugin') + ], + darkMode: 'selector', +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..89e3ce8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "lib": ["esnext", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "importHelpers": true, + "moduleResolution": "node", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + "jsxImportSource": "vue", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..35966e0 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + base: "/" +})