diff --git a/frontend-vue/bun.lockb b/frontend-vue/bun.lockb index 7999c8e..d136a17 100755 Binary files a/frontend-vue/bun.lockb and b/frontend-vue/bun.lockb differ diff --git a/frontend-vue/package.json b/frontend-vue/package.json index fb4f9ca..367eef7 100644 --- a/frontend-vue/package.json +++ b/frontend-vue/package.json @@ -14,7 +14,11 @@ "format": "prettier --write src/" }, "dependencies": { + "jdenticon": "^3.3.0", + "node-vibrant": "^4.0.3", "pinia": "^3.0.3", + "sass": "^1.89.2", + "sass-loader": "^16.0.5", "vue": "^3.5.17", "vue-router": "^4.5.1" }, @@ -31,6 +35,7 @@ "@vue/tsconfig": "^0.7.0", "eslint": "^9.29.0", "eslint-plugin-vue": "~10.2.0", + "fibers": "^5.0.3", "jiti": "^2.4.2", "jsdom": "^26.1.0", "npm-run-all2": "^8.0.4", diff --git a/frontend-vue/src/App.vue b/frontend-vue/src/App.vue index 7905b05..e35255f 100644 --- a/frontend-vue/src/App.vue +++ b/frontend-vue/src/App.vue @@ -1,22 +1,11 @@ diff --git a/frontend-vue/src/assets/base.css b/frontend-vue/src/assets/base.css index 7e10f38..2e7efbc 100644 --- a/frontend-vue/src/assets/base.css +++ b/frontend-vue/src/assets/base.css @@ -1,18 +1,28 @@ @media (prefers-color-scheme: light) { - :root { - --text: #0d0f0b; - --background: #f6f7f3; - --primary: #4a5733; - --secondary: #b5c898; - --accent: #7c9b4b; - } + :root { + --text: #0d0f0b; + --background: #f6f7f3; + --primary: #4a5733; + --secondary: #b5c898; + --accent: #7c9b4b; + } } @media (prefers-color-scheme: dark) { - :root { - --text: #f2f4f0; - --background: #0b0c08; - --primary: #bfcca8; - --secondary: #546737; - --accent: #96b464; - } + :root { + --text: #f2f4f0; + --background: #0b0c08; + --primary: #bfcca8; + --secondary: #546737; + --accent: #96b464; + } } +:root { + --border: var(--text); +} + +svg.black-white { + fill: var(--text); + stroke: var(--text); +} + +/*# sourceMappingURL=base.css.map */ diff --git a/frontend-vue/src/assets/base.css.map b/frontend-vue/src/assets/base.css.map new file mode 100644 index 0000000..d9050d5 --- /dev/null +++ b/frontend-vue/src/assets/base.css.map @@ -0,0 +1 @@ +{"version":3,"sourceRoot":"","sources":["base.scss"],"names":[],"mappings":"AAGA;EACC;IACC;IACA;IACA;IACA;IACA;;;AAIF;EACC;IACC;IACA;IACA;IACA;IACA;;;AAKF;EACE;;;AAGF;EACI;EACA","file":"base.css"} \ No newline at end of file diff --git a/frontend-vue/src/assets/base.scss b/frontend-vue/src/assets/base.scss new file mode 100644 index 0000000..4fd2864 --- /dev/null +++ b/frontend-vue/src/assets/base.scss @@ -0,0 +1,32 @@ +@use "sass:color"; + +// Base colors here +@media (prefers-color-scheme: light) { + :root { + --text: #0d0f0b; + --background: #f6f7f3; + --primary: #4a5733; + --secondary: #b5c898; + --accent: #7c9b4b; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #f2f4f0; + --background: #0b0c08; + --primary: #bfcca8; + --secondary: #546737; + --accent: #96b464; + } +} + +// And calculate the rest here +:root { + --border: var(--text); +} + +svg.black-white { + fill: var(--text); + stroke: var(--text); +} diff --git a/frontend-vue/src/assets/main.css b/frontend-vue/src/assets/main.css index 36fb845..c520204 100644 --- a/frontend-vue/src/assets/main.css +++ b/frontend-vue/src/assets/main.css @@ -1,4 +1,12 @@ -@import './base.css'; +@import 'base.scss'; + +body { + background-color: var(--background); +} + +p, h1, h2, h3, h4, h5, h6 { + color: var(--text); +} #app { max-width: 1280px; @@ -10,7 +18,7 @@ a, .green { text-decoration: none; - color: hsla(160, 100%, 37%, 1); + color: var(--accent); transition: 0.4s; padding: 3px; } diff --git a/frontend-vue/src/components/WelcomeItem.vue b/frontend-vue/src/components/WelcomeItem.vue index 6d7086a..df3e839 100644 --- a/frontend-vue/src/components/WelcomeItem.vue +++ b/frontend-vue/src/components/WelcomeItem.vue @@ -21,7 +21,8 @@ .details { flex: 1; - margin-left: 1rem; + margin-left: 2rem; + color: var(--text); } i { @@ -31,28 +32,28 @@ i { width: 32px; height: 32px; - color: var(--color-text); + color: var(--text); } h3 { font-size: 1.2rem; font-weight: 500; margin-bottom: 0.4rem; - color: var(--color-heading); + color: var(--text); } @media (min-width: 1024px) { .item { margin-top: 0; - padding: 0.4rem 0 1rem calc(var(--section-gap) / 2); + padding: 0.4rem 0 1rem; } i { top: calc(50% - 25px); left: -26px; position: absolute; - border: 1px solid var(--color-border); - background: var(--color-background); + border: 1px solid var(--text); + background: var(--background); border-radius: 8px; width: 50px; height: 50px; @@ -60,7 +61,7 @@ h3 { .item:before { content: ' '; - border-left: 1px solid var(--color-border); + border-left: 1px solid var(--text); position: absolute; left: 0; bottom: calc(50% + 25px); @@ -69,7 +70,7 @@ h3 { .item:after { content: ' '; - border-left: 1px solid var(--color-border); + border-left: 1px solid var(--text); position: absolute; left: 0; top: calc(50% + 25px); diff --git a/frontend-vue/src/components/global/Header.vue b/frontend-vue/src/components/global/Header.vue new file mode 100644 index 0000000..3af8548 --- /dev/null +++ b/frontend-vue/src/components/global/Header.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend-vue/src/components/user/Header.vue b/frontend-vue/src/components/user/Header.vue deleted file mode 100644 index e69de29..0000000 diff --git a/frontend-vue/src/components/user/PreviewHeader.vue b/frontend-vue/src/components/user/PreviewHeader.vue new file mode 100644 index 0000000..2902884 --- /dev/null +++ b/frontend-vue/src/components/user/PreviewHeader.vue @@ -0,0 +1,30 @@ + + + + diff --git a/frontend-vue/src/components/user/ProfilePicture.vue b/frontend-vue/src/components/user/ProfilePicture.vue new file mode 100644 index 0000000..23188b6 --- /dev/null +++ b/frontend-vue/src/components/user/ProfilePicture.vue @@ -0,0 +1,57 @@ + + + diff --git a/frontend-vue/src/components/util/FormattedText.vue b/frontend-vue/src/components/util/FormattedText.vue index f6f73bb..04d40d4 100644 --- a/frontend-vue/src/components/util/FormattedText.vue +++ b/frontend-vue/src/components/util/FormattedText.vue @@ -1,5 +1,4 @@ -

{{props.rawText}}

+ diff --git a/frontend-vue/src/router/index.ts b/frontend-vue/src/router/index.ts index 3e49915..fa57489 100644 --- a/frontend-vue/src/router/index.ts +++ b/frontend-vue/src/router/index.ts @@ -17,6 +17,11 @@ const router = createRouter({ // which is lazy-loaded when the route is visited. component: () => import('../views/AboutView.vue'), }, + { + path: '/testing/note', + name: 'note-testing', + component: () => import('../views/NoteTestView.vue'), + }, ], }) diff --git a/frontend-vue/src/stores/media.ts b/frontend-vue/src/stores/media.ts new file mode 100644 index 0000000..5a78894 --- /dev/null +++ b/frontend-vue/src/stores/media.ts @@ -0,0 +1,10 @@ +export interface MediaMetadata { + id: string; + ownedBy: string; + remote: boolean; + url: string; + mediaType: string; + name: string; + alt: string; + blurred: boolean; +} diff --git a/frontend-vue/src/stores/userdata.ts b/frontend-vue/src/stores/userdata.ts new file mode 100644 index 0000000..95d0397 --- /dev/null +++ b/frontend-vue/src/stores/userdata.ts @@ -0,0 +1,10 @@ +import type { MediaMetadata } from '@/stores/media.ts' + +export interface User { + username: string + displayName: string + description: string + profilePicture?: MediaMetadata + bannerPicture?: MediaMetadata + backgroundPicture?: MediaMetadata +} diff --git a/frontend-vue/src/utils/identiheart.ts b/frontend-vue/src/utils/identiheart.ts new file mode 100644 index 0000000..9df1ec8 --- /dev/null +++ b/frontend-vue/src/utils/identiheart.ts @@ -0,0 +1,492 @@ +class Crusher { + hash(s: any): number { + return String(s).split("").reduce(function(a, b) { + a = ((a << 5) - a) + b.charCodeAt(0); + return a & a + }, 0); + } + isDOMElement(o:any): boolean { + return ( + typeof HTMLElement === "object" ? o instanceof HTMLElement : + o && typeof o === "object" && true && o.nodeType === 1 && typeof o.nodeName==="string" + ); + } +} + +enum BlockType { + ONE = 1, + TWO = 2, + THREE = 3, +} + +class Block { + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + type: BlockType; + primary: string; + accent: string; + cellSize: number; + margin: number; + scale: number; + pos: Position; + hash: number; + + constructor( + c: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + type: BlockType, + primary: string, + accent: string, + hash: number, + cellSize: number, + margin: number, + scale: number, + pos: Position, + ) { + this.canvas = c; + this.context = ctx; + this.type = type; + this.primary = primary; + this.accent = accent; + this.hash = hash; + this.cellSize = cellSize; + this.margin = margin; + this.scale = scale; + this.pos = pos; + } + + offset() { + this.context.save(); + this.context.translate(0.6 * this.scale, -0.6 * this.scale); + } + + resetOffset() { + this.context.restore(); + } + + makePath(hash: number, offset: number) { + const mod = Math.abs(hash + offset) % 4; + + switch(mod) { + case 0: + // top + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize); + this.context.closePath(); + break; + case 1: + // right + this.context.beginPath(); + this.context.moveTo(this.pos.x + this.cellSize, this.pos.y); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize); + this.context.lineTo(this.pos.x, this.pos.y + this.cellSize); + this.context.closePath(); + break; + case 2: + // bottom + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x, this.pos.y + this.cellSize); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y + this.cellSize); + this.context.closePath(); + break; + case 3: + // left + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y); + this.context.lineTo(this.pos.x, this.pos.y + this.cellSize); + this.context.closePath(); + break; + default: + // top + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + this.cellSize, this.pos.y); + this.context.lineTo(this.pos.x, this.pos.y + this.cellSize); + this.context.closePath(); + } + } + + draw() { + this.offset(); + + if (this.type === BlockType.ONE) { + this.makePath(this.hash, this.hash % 3); + this.context.fillStyle = this.primary; + this.context.fill(); + + this.makePath(this.hash, this.hash % 5); + this.context.fillStyle = this.accent; + this.context.fill(); + } else if (this.type === BlockType.TWO) { + this.makePath(this.hash, this.hash % 4); + this.context.fillStyle = this.accent; + this.context.fill(); + + this.makePath(this.hash, this.hash % 3); + this.context.fillStyle = this.primary; + this.context.fill(); + } else { + this.makePath(this.hash, this.hash % 7); + this.context.fillStyle = this.accent; + this.context.fill(); + + this.makePath(this.hash, this.hash % 8); + this.context.fillStyle = this.primary; + this.context.fill(); + } + + this.resetOffset(); + } +} + +class Position { + x: number; + y: number; + constructor(x: number, y: number) { + this.x = x; + this.y = y; + } +} + +class Shape { + canvas: HTMLCanvasElement; + context: CanvasRenderingContext2D; + hash: number; + primary: string; + accent: string; + pos: Position; + scale: number; + cellSize: number; + strokeColor: string; + + constructor( + c: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + hash: number, + primary: string, + accent: string, + pos: Position, + scale: number, + cellSize: number, + strokeColor: string + ) { + this.canvas = c; + this.context = ctx; + this.hash = hash; + this.primary = primary; + this.accent = accent; + this.pos = pos; + this.scale = scale; + this.cellSize = cellSize; + this.strokeColor = strokeColor; + } + + getColor() { + return [this.primary, this.accent][Math.abs(this.hash % 2)]; + } + + makePath() { + const mod = Math.abs(this.hash + 1) % 4; + + switch(mod) { + case 0: + // square + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y); + this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2)); + this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2)); + this.context.closePath(); + break; + case 1: + //circle + this.context.beginPath(); + this.context.arc( + this.pos.x + (this.cellSize / Math.PI) - 5, + this.pos.y - (this.cellSize / Math.PI) + 5, + this.cellSize / 3, + 0, + Math.PI * 2, + true + ); + break; + case 2: + // triangle + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + (this.cellSize * 0.65), this.pos.y); + this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize * 0.65)); + this.context.closePath(); + break; + case 3: + // oval + this.context.beginPath(); + this.context.moveTo(this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2)); + this.context.quadraticCurveTo(this.pos.x + (this.cellSize * 0.4), this.pos.y, this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5)); + this.context.moveTo(this.pos.x + (this.cellSize * 0.5), this.pos.y - (this.cellSize * 0.5)); + this.context.quadraticCurveTo(this.pos.x , this.pos.y - (this.cellSize * 0.4), this.pos.x - (this.cellSize * 0.2), this.pos.y + (this.cellSize * 0.2)); + break; + default: + //square + this.context.beginPath(); + this.context.moveTo(this.pos.x, this.pos.y); + this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y); + this.context.lineTo(this.pos.x + (this.cellSize / 2), this.pos.y - (this.cellSize / 2)); + this.context.lineTo(this.pos.x, this.pos.y - (this.cellSize / 2)); + this.context.closePath(); + } + } + + draw(hasStroke: boolean, strokeWeight: number) { + const color = this.getColor(); + this.context.globalCompositeOperation = "source-over"; + + this.makePath(); + this.context.fillStyle = color; + this.context.strokeStyle = this.strokeColor; + this.context.lineWidth = this.scale * ((4/5 * strokeWeight) / this.canvas.width); + this.context.lineJoin = "round"; + this.context.lineCap = "round"; + this.context.fill(); + + if (hasStroke) { + this.context.stroke(); + } + } +} + + +/** + * @name IdentiHeart + * @author Schlipak + * @copyright Apache license 2015 Guillaume de Matos + */ +export class Identiheart { + canvas: HTMLCanvasElement + context: CanvasRenderingContext2D + margin: number + crusher: Crusher + palette = [ + '#F44336', '#E91E63', '#9C27B0', '#673AB7', '#3F51B5', '#2196F3', + '#03A9F4', '#00BCD4', '#009688', '#4CAF50', '#8BC34A', '#CDDC39', + '#FFEB3B', '#FFC107', '#FF9800', '#FF5722', '#795548', '#607D8B' + ]; + + primary: string = "#F44336"; + accent: string = "#E91E63"; + scale: number; + cellSize: number; + hash: number = 0; + blocks: Block[] = []; + shape?: Shape; + hasStroke: boolean = true; + strokeWeight = 500; + strokeColor = "#000000"; + compositeOperation: GlobalCompositeOperation = 'multiply'; + + constructor(c: HTMLCanvasElement, margin?: number, scale?: number, ctx?: CanvasRenderingContext2D) { + this.canvas = c; + this.context = ctx ?? c.getContext("2d")!; + this.margin = margin || 5; + this.scale = scale || 20; + this.crusher = new Crusher(); + this.cellSize = (this.canvas.width / 2) - (this.margin * this.scale); + } + + setUsername(username: string) { + this.hash = this.crusher.hash(username); + return this; + } + + setPalette(palette:string[]) { + if (palette.length < 2) { + throw new Error('Palette must be more than two elements long'); + } + + this.palette = palette; + return this; + } + + setHasStroke(b: boolean) { + this.hasStroke = b; + return this; + } + + setStrokeWeight(weight: number) { + this.strokeWeight = weight; + return this; + } + + setStrokeColor(color: string) { + this.strokeColor = color; + return this; + } + + setCompositeOperation(operation: GlobalCompositeOperation) { + this.compositeOperation = operation; + return this; + } + + setCanvas(canvas: HTMLCanvasElement) { + this.canvas = canvas; + this.context = canvas.getContext('2d')!; + return this; + } + + init() { + // Purge the block array + this.blocks = new Array(); + // Purge the shape + this.shape = undefined; + + // Generate colors + const crusher = new Crusher(); + const subHash = crusher.hash(this.hash); + this.accent = this.palette[Math.abs(subHash % this.palette.length)]; + + // Clear the canvas + this.context.globalCompositeOperation = "source-over"; + this.context.clearRect(0, 0, this.canvas.width, this.canvas.height); + this.context.globalCompositeOperation = this.compositeOperation; + + return this; + } + + draw() { + this.init(); + + // Rotate the canvas -45deg + this.context.save(); + this.context.translate(this.canvas.width/2, this.canvas.height/2); + this.context.rotate(- Math.PI / 4); + this.context.translate(-this.canvas.width/2, -this.canvas.height/2); + + this.generateBlocks(); + this.drawBlocks(); + + if (this.hasStroke) { + this.drawOutline(); + } + + this.shape = new Shape(this.canvas, this.context, this.hash, this.primary, this.accent, { + x: (this.margin * this.scale) + 1.5 * this.cellSize, + y: (this.margin * this.scale) + 0.5 * this.cellSize + }, this.scale, this.cellSize, this.strokeColor); + this.shape.draw(this.hasStroke, this.strokeWeight); + + // Restore the original matrix + this.context.restore(); + + return this; + } + + offset() { + this.context.save(); + this.context.translate(0.6 * this.scale, - 0.6 * this.scale); + } + + resetOffset() { + this.context.restore(); + } + + drawOutline() { + this.offset(); + this.context.globalCompositeOperation = "source-over"; + + // Outer lines + this.context.beginPath(); + this.context.moveTo(this.margin * this.scale, this.margin * this.scale); + this.context.lineTo(this.margin * this.scale, this.canvas.height - (this.margin * this.scale)); + this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height - (this.margin * this.scale)); + this.context.lineTo(this.canvas.width - (this.margin * this.scale), this.canvas.height / 2); + this.context.lineTo(this.canvas.width / 2, this.canvas.height / 2); + this.context.lineTo(this.canvas.width / 2, this.margin * this.scale); + this.context.closePath(); + + this.context.strokeStyle = this.strokeColor; + this.context.lineWidth = this.scale * (this.strokeWeight / this.canvas.width); + this.context.lineJoin = "round"; + this.context.lineCap = "round"; + this.context.stroke(); + + // Inner lines + this.context.beginPath(); + this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2); + this.context.lineTo(this.margin * this.scale, this.canvas.height / 2); + this.context.moveTo(this.canvas.width / 2, this.canvas.height / 2); + this.context.lineTo(this.canvas.width / 2, this.canvas.height - (this.margin * this.scale)); + + this.context.stroke(); + + this.resetOffset(); + this.context.globalCompositeOperation = this.compositeOperation; + } + + generateBlocks() { + /* + c: HTMLCanvasElement, + ctx: CanvasRenderingContext2D, + type: BlockType, + primary: string, + accent: string, + hash: number, + cellSize: number, + margin: number, + scale: number, + pos: Position, + * */ + let b1 = new Block( + this.canvas, + this.context, + BlockType.ONE, + this.primary, + this.accent, + this.hash, + this.cellSize, + this.margin, + this.scale, + new Position(this.margin * this.scale,this.margin * this.scale)); + this.blocks.push(b1); + + let b2 = new Block( + this.canvas, + this.context, + BlockType.TWO, + this.primary, this.accent, + this.hash, + this.cellSize, + this.margin, + this.scale, + new Position(this.margin * this.scale,this.canvas.height / 2) + ); + this.blocks.push(b2); + + let b3 = new Block( + this.canvas, + this.context, + BlockType.THREE, + this.primary, + this.accent, + this.hash, + this.cellSize, + this.margin, + this.scale, + new Position(this.canvas.width / 2, this.canvas.height / 2) + ); + this.blocks.push(b3); + } + + drawBlocks() { + if (this.blocks.length == 0) { + return false; + } + + for (var i = 0; i < this.blocks.length; i++) { + this.blocks[i].draw(); + } + } +} diff --git a/frontend-vue/src/views/AboutView.vue b/frontend-vue/src/views/AboutView.vue index 756ad2a..2b3dbb0 100644 --- a/frontend-vue/src/views/AboutView.vue +++ b/frontend-vue/src/views/AboutView.vue @@ -10,6 +10,7 @@ min-height: 100vh; display: flex; align-items: center; + color: var(--text); } } diff --git a/frontend-vue/src/views/NoteTestView.vue b/frontend-vue/src/views/NoteTestView.vue new file mode 100644 index 0000000..60b0cbd --- /dev/null +++ b/frontend-vue/src/views/NoteTestView.vue @@ -0,0 +1,29 @@ + + + + +