From f8cb42962a9beab42e56d3e2574067730d427d3e Mon Sep 17 00:00:00 2001 From: CoGomu Date: Sun, 11 May 2025 19:35:39 +0200 Subject: [PATCH] added calendar and rough structure of calendar --- bun.lock | 20 +++ package.json | 8 + web/bun.lock | 5 + web/components/ui/Sidebar.vue | 49 ++++-- web/components/ui/calendar/Calendar.vue | 113 ++++++++++++++ .../ui/calendar/CalendarCollumn.vue | 119 +++++++++++++++ web/components/ui/calendar/CalendarEvent.vue | 50 ++++++ web/components/ui/calendar/CalendarHeader.vue | 24 +++ .../ui/calendar/CalendarSeperator.vue | 20 +++ web/package.json | 1 + web/pages/index.vue | 12 +- web/utils/event.ts | 142 ++++++++++++++++++ web/utils/lib.ts | 42 ++++++ 13 files changed, 591 insertions(+), 14 deletions(-) create mode 100644 bun.lock create mode 100644 package.json create mode 100644 web/components/ui/calendar/Calendar.vue create mode 100644 web/components/ui/calendar/CalendarCollumn.vue create mode 100644 web/components/ui/calendar/CalendarEvent.vue create mode 100644 web/components/ui/calendar/CalendarHeader.vue create mode 100644 web/components/ui/calendar/CalendarSeperator.vue create mode 100644 web/utils/event.ts create mode 100644 web/utils/lib.ts diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..4f54ffc --- /dev/null +++ b/bun.lock @@ -0,0 +1,20 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "dependencies": { + "moment": "^2.30.1", + }, + "devDependencies": { + "@iconify-json/lucide": "^1.2.42", + }, + }, + }, + "packages": { + "@iconify-json/lucide": ["@iconify-json/lucide@1.2.42", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-exkRygF4yd6e5q966TXJQc/b+MAu3iQb8LeExCjl2JoP4/RlpudkYpg1AIZVCVCjAiElDYmVJYDWiJXPLkFN/g=="], + + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="], + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..f094d75 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "moment": "^2.30.1" + }, + "devDependencies": { + "@iconify-json/lucide": "^1.2.42" + } +} \ No newline at end of file diff --git a/web/bun.lock b/web/bun.lock index ca2df90..586c0c1 100644 --- a/web/bun.lock +++ b/web/bun.lock @@ -10,6 +10,7 @@ "@nuxt/ui": "3.1.1", "eslint": "^9.0.0", "nuxt": "^3.17.2", + "nuxt-app": "file:", "typescript": "^5.6.3", "vue": "^3.5.13", "vue-router": "^4.5.1", @@ -1445,6 +1446,8 @@ "nuxt": ["nuxt@3.17.2", "", { "dependencies": { "@nuxt/cli": "^3.25.0", "@nuxt/devalue": "^2.0.2", "@nuxt/devtools": "^2.4.0", "@nuxt/kit": "3.17.2", "@nuxt/schema": "3.17.2", "@nuxt/telemetry": "^2.6.6", "@nuxt/vite-builder": "3.17.2", "@unhead/vue": "^2.0.8", "@vue/shared": "^3.5.13", "c12": "^3.0.3", "chokidar": "^4.0.3", "compatx": "^0.2.0", "consola": "^3.4.2", "cookie-es": "^2.0.0", "defu": "^6.1.4", "destr": "^2.0.5", "devalue": "^5.1.1", "errx": "^0.1.0", "esbuild": "^0.25.3", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "exsolve": "^1.0.5", "globby": "^14.1.0", "h3": "^1.15.3", "hookable": "^5.5.3", "ignore": "^7.0.4", "impound": "^1.0.0", "jiti": "^2.4.2", "klona": "^2.0.6", "knitwork": "^1.2.0", "magic-string": "^0.30.17", "mlly": "^1.7.4", "mocked-exports": "^0.1.1", "nanotar": "^0.2.0", "nitropack": "^2.11.11", "nypm": "^0.6.0", "ofetch": "^1.4.1", "ohash": "^2.0.11", "on-change": "^5.0.1", "oxc-parser": "^0.68.1", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.1.0", "radix3": "^1.1.2", "scule": "^1.3.0", "semver": "^7.7.1", "std-env": "^3.9.0", "strip-literal": "^3.0.0", "tinyglobby": "0.2.13", "ufo": "^1.6.1", "ultrahtml": "^1.6.0", "uncrypto": "^0.1.3", "unctx": "^2.4.1", "unimport": "^5.0.1", "unplugin": "^2.3.2", "unplugin-vue-router": "^0.12.0", "unstorage": "^1.16.0", "untyped": "^2.0.0", "vue": "^3.5.13", "vue-bundle-renderer": "^2.1.1", "vue-devtools-stub": "^0.1.0", "vue-router": "^4.5.1" }, "peerDependencies": { "@parcel/watcher": "^2.1.0", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "optionalPeers": ["@parcel/watcher", "@types/node"], "bin": { "nuxi": "bin/nuxt.mjs", "nuxt": "bin/nuxt.mjs" } }, "sha512-zPEGeGlHoMCFf+Y9I7iEZKhdfsRq0Zf2qE8wEEcjP9T6omzm776h9KVzoj3+qPL1v0rGzSyCFslFtxk+Ey6neA=="], + "nuxt-app": ["nuxt-app@file:", { "dependencies": { "@internationalized/date": "^3.8.0", "@nuxt/eslint": "1.3.0", "@nuxt/test-utils": "3.18.0", "@nuxt/ui": "3.1.1", "eslint": "^9.0.0", "nuxt": "^3.17.2", "nuxt-app": "file:", "typescript": "^5.6.3", "vue": "^3.5.13", "vue-router": "^4.5.1" } }], + "nypm": ["nypm@0.6.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "pathe": "^2.0.3", "pkg-types": "^2.0.0", "tinyexec": "^0.3.2" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -2197,6 +2200,8 @@ "nuxt/unimport": ["unimport@5.0.1", "", { "dependencies": { "acorn": "^8.14.1", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", "local-pkg": "^1.1.1", "magic-string": "^0.30.17", "mlly": "^1.7.4", "pathe": "^2.0.3", "picomatch": "^4.0.2", "pkg-types": "^2.1.0", "scule": "^1.3.0", "strip-literal": "^3.0.0", "tinyglobby": "^0.2.13", "unplugin": "^2.3.2", "unplugin-utils": "^0.2.4" } }, "sha512-1YWzPj6wYhtwHE+9LxRlyqP4DiRrhGfJxdtH475im8ktyZXO3jHj/3PZ97zDdvkYoovFdi0K4SKl3a7l92v3sQ=="], + "nuxt-app/nuxt-app": ["nuxt-app@file:.", {}], + "nypm/tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], "p-event/p-timeout": ["p-timeout@5.1.0", "", {}, "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew=="], diff --git a/web/components/ui/Sidebar.vue b/web/components/ui/Sidebar.vue index e1528c8..75316e6 100644 --- a/web/components/ui/Sidebar.vue +++ b/web/components/ui/Sidebar.vue @@ -5,6 +5,10 @@ import Title1 from './Title1.vue'; const selectedDate = shallowRef(new CalendarDate(2024, 6, 29)); +const dropDownItems = ref([ + {label: "Profile", icon: "i-lucide-user"} +]) + defineProps<{ todos: string[] }>() @@ -12,20 +16,39 @@ defineProps<{ diff --git a/web/components/ui/calendar/Calendar.vue b/web/components/ui/calendar/Calendar.vue new file mode 100644 index 0000000..942ebf7 --- /dev/null +++ b/web/components/ui/calendar/Calendar.vue @@ -0,0 +1,113 @@ + + + diff --git a/web/components/ui/calendar/CalendarCollumn.vue b/web/components/ui/calendar/CalendarCollumn.vue new file mode 100644 index 0000000..9303714 --- /dev/null +++ b/web/components/ui/calendar/CalendarCollumn.vue @@ -0,0 +1,119 @@ + + + + + + diff --git a/web/components/ui/calendar/CalendarEvent.vue b/web/components/ui/calendar/CalendarEvent.vue new file mode 100644 index 0000000..220b5f2 --- /dev/null +++ b/web/components/ui/calendar/CalendarEvent.vue @@ -0,0 +1,50 @@ + + + diff --git a/web/components/ui/calendar/CalendarHeader.vue b/web/components/ui/calendar/CalendarHeader.vue new file mode 100644 index 0000000..9127e7f --- /dev/null +++ b/web/components/ui/calendar/CalendarHeader.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/web/components/ui/calendar/CalendarSeperator.vue b/web/components/ui/calendar/CalendarSeperator.vue new file mode 100644 index 0000000..1ac311b --- /dev/null +++ b/web/components/ui/calendar/CalendarSeperator.vue @@ -0,0 +1,20 @@ + + + + + diff --git a/web/package.json b/web/package.json index 08d7c44..7e52ce2 100644 --- a/web/package.json +++ b/web/package.json @@ -16,6 +16,7 @@ "@nuxt/ui": "3.1.1", "eslint": "^9.0.0", "nuxt": "^3.17.2", + "nuxt-app": "file:", "typescript": "^5.6.3", "vue": "^3.5.13", "vue-router": "^4.5.1" diff --git a/web/pages/index.vue b/web/pages/index.vue index 6328873..f3e711e 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -1,14 +1,24 @@ diff --git a/web/utils/event.ts b/web/utils/event.ts new file mode 100644 index 0000000..4d4b1a8 --- /dev/null +++ b/web/utils/event.ts @@ -0,0 +1,142 @@ +import type { Moment } from "moment" +import moment from "moment" + +export class Event { + private static readonly MINUTES_IN_DAY = 24 * 60 + + constructor( + public readonly title: string, + public from: Moment, + public to: Moment + ) { } + + + getPercentDimensions(): EventDimensions { + const start_of_day = moment(this.from).startOf('day') + const from_percentage = this.from.diff(start_of_day, 'minutes') / Event.MINUTES_IN_DAY + const to_percentage = this.to.diff(start_of_day, 'minutes') / Event.MINUTES_IN_DAY + return { + from: from_percentage * 100, + to: to_percentage * 100 + } + } + + getPixelDimensions(maxHeight: number): EventDimensions { + const percentDimensions = this.getPercentDimensions() + return { + from: (percentDimensions.from / 100) * maxHeight, + to: (percentDimensions.to / 100) * maxHeight + } + } + + static fromSimple(event: SimpleEvent): Event { + return new Event(event.title, event.from, event.to) + } + + static fromSerializable(event: SerializableEvent) { + return new Event(event.title, moment(event.from), moment(event.to)) + } + + static fromPercentDimensions(title: string, dimensions: EventDimensions, date: Moment): Event { + return new Event( + title, + moment(date).startOf('day').minutes((dimensions.from / 100) * Event.MINUTES_IN_DAY), + moment(date).startOf('day').minutes((dimensions.to / 100) * Event.MINUTES_IN_DAY) + ) + } + + static fromPixelDimensions(title: string, dimensions: EventDimensions, height: number, date: Moment): Event { + const percentDimensions: EventDimensions = { + from: dimensions.from * 100 / height, + to: dimensions.to * 100 / height + } + + return Event.fromPercentDimensions(title, percentDimensions, date) + } + + static fromDraggedEvent(draggedEvent: DraggedEvent, height: number): Event { + const pixelDimensions: EventDimensions = { + from: draggedEvent.top, + to: draggedEvent.top + draggedEvent.height + } + + return Event.fromPixelDimensions(draggedEvent.target.title, pixelDimensions, height, draggedEvent.date) + } + + updateWithDraggedEvent(draggedEvent: DraggedEvent, height: number): Event { + const newEventData = Event.fromDraggedEvent(draggedEvent, height) + + this.from = newEventData.from + this.to = newEventData.to + + return this + } + + + toSimple(): SimpleEvent { + return { + title: this.title, + from: this.from, + to: this.to + } + } + + toSerializable(): SerializableEvent { + return { + title: this.title, + from: this.from.toISOString(), + to: this.to.toISOString() + } + } + + withCollisions(collisions: number = 0): CollissionWrapper { + return { + event: this, + collisions + } + } + + toDraggedEvent(height: number, mouseY: number): DraggedEvent { + const pixelDimensions = this.getPixelDimensions(height) + const offset = mouseY - pixelDimensions.from + + return { + date: moment(this.from).startOf('day'), + top: pixelDimensions.from, + height: pixelDimensions.to - pixelDimensions.from, + target: this, + offset: offset + } + } + +} + +export type EventDimensions = { + from: number, + to: number +} + +export type SimpleEvent = { + title: string, + from: Moment, + to: Moment +} + +export type SerializableEvent = { + title: string, + from: string, + to: string +} + +export type CollissionWrapper = { + event: Event, + collisions: number +} + +export type DraggedEvent = { + date: Moment, + top: number, + height: number, + target: Event, + offset: number +} diff --git a/web/utils/lib.ts b/web/utils/lib.ts new file mode 100644 index 0000000..4f1ab57 --- /dev/null +++ b/web/utils/lib.ts @@ -0,0 +1,42 @@ +import type { Moment } from "moment" + +export type Seperator = { + text: string, + time: Moment +} + +export type Timespan = { + from: number, + to: number +} + +export type Event = AnonymousEvent & { + title: string +} + +export type AnonymousEvent = { + from: Moment, + to: Moment +} + +export type SerializableEvent = { + title: string, + from: string, + to: string +} + +export type EventWithCollisions = Event & { + collisions: number +} + +export type EventDimensions = { + from: number, + to: number +} + +export function percentToPixelDimensions(dimensions: EventDimensions, totalHeight: number): EventDimensions { + return { + from: (dimensions.from / 100) * totalHeight, + to: (dimensions.to / 100) * totalHeight + } +}