From 65fce4ad46d841f52b22f89c5632b2e8a3c39465 Mon Sep 17 00:00:00 2001
From: Sebastian Mohr <sebastian@mohrenclan.de>
Date: Tue, 13 Aug 2024 15:59:13 +0200
Subject: [PATCH] Fixed a small insert bug with nested snips

---
 .../editor/worker/pageWorker/pageworker.ts    |  3 +-
 .../lib/hooks/useDevicePixelRatio.ts          | 15 +++-
 apps/images/src/render/overrides.ts           | 68 ++-----------------
 apps/socket/src/prefixes/snip/socket.ts       | 15 ++--
 packages/database/src/services/snip.ts        | 13 ++++
 5 files changed, 46 insertions(+), 68 deletions(-)

diff --git a/apps/fullstack/components/editor/worker/pageWorker/pageworker.ts b/apps/fullstack/components/editor/worker/pageWorker/pageworker.ts
index 8d1d3cc0..14cc908f 100644
--- a/apps/fullstack/components/editor/worker/pageWorker/pageworker.ts
+++ b/apps/fullstack/components/editor/worker/pageWorker/pageworker.ts
@@ -33,7 +33,7 @@ const debug = (...args: unknown[]) => {
 export class PageWorker {
     static #instance: PageWorker;
 
-    private constructor() { }
+    private constructor() {}
 
     public static get instance(): PageWorker {
         if (!PageWorker.#instance) {
@@ -221,6 +221,7 @@ export class PageWorker {
             throw new Error("Page needs to be initialized to place snips!");
         }
         debug("[placeOnPage]", "Placing snip", data.snip.id);
+        data.snip.book_id = this.page.data.book_id;
         const snip = await loadSnip(data.snip);
         debug("[placeOnPage]", "Got snip", snip.type);
         snip.socket = this.page.socket;
diff --git a/apps/fullstack/lib/hooks/useDevicePixelRatio.ts b/apps/fullstack/lib/hooks/useDevicePixelRatio.ts
index 24797e11..7f4bba47 100644
--- a/apps/fullstack/lib/hooks/useDevicePixelRatio.ts
+++ b/apps/fullstack/lib/hooks/useDevicePixelRatio.ts
@@ -2,7 +2,7 @@
 import { useCallback, useEffect, useState } from "react";
 
 const useDevicePixelRatio = () => {
-    const [pixelRatio, setPixelRatio] = useState(window?.devicePixelRatio || 1);
+    const [pixelRatio, setPixelRatio] = useState(getDevicePixelRatio());
 
     const handleChange = useCallback(() => {
         setPixelRatio(window.devicePixelRatio);
@@ -31,4 +31,17 @@ const useDevicePixelRatio = () => {
     return pixelRatio;
 };
 
+/**
+ * Returns the current device pixel ratio (DPR) given the passed options
+ *
+ * @param options
+ * @returns current device pixel ratio
+ */
+export function getDevicePixelRatio(): number {
+    const hasDprProp =
+        typeof window !== "undefined" &&
+        typeof window.devicePixelRatio === "number";
+    return hasDprProp ? window.devicePixelRatio : 1; //defaults to 1
+}
+
 export default useDevicePixelRatio;
diff --git a/apps/images/src/render/overrides.ts b/apps/images/src/render/overrides.ts
index ad57975f..27e9b46c 100644
--- a/apps/images/src/render/overrides.ts
+++ b/apps/images/src/render/overrides.ts
@@ -1,9 +1,13 @@
-import { createCanvas, Image } from "canvas";
+import { Canvas, Image } from "canvas";
 import sharp from "sharp";
 
+import { BaseSnip } from "@snip/snips/general/base";
 import { ImageSnip } from "@snip/snips/general/image";
 import { ImageSnipLegacy } from "@snip/snips/general/legacy/image";
-import { TextSnip } from "@snip/snips/general/text";
+
+BaseSnip.prototype.createCanvas = function (width: number, height: number) {
+    return new Canvas(width, height);
+};
 
 // eslint-disable-next-line @typescript-eslint/no-explicit-any
 const get_bitmap = async function (this: any) {
@@ -40,63 +44,3 @@ const get_bitmap = async function (this: any) {
 
 ImageSnipLegacy.prototype.get_bitmap = get_bitmap;
 ImageSnip.prototype.get_bitmap = get_bitmap;
-
-TextSnip.prototype.get_overlap_index_v3 = function (str, maxWidth, font) {
-    if (maxWidth == -1) {
-        return undefined;
-    }
-    const canvas = createCanvas(1, 1);
-    const ctx = canvas.getContext("2d");
-    ctx.font = font;
-
-    let width = 0;
-    for (let c = 0; c < str.length; c++) {
-        const char = str.charAt(c);
-        width += ctx.measureText(char).width;
-        if (width > maxWidth) {
-            return c;
-        }
-    }
-    return undefined;
-};
-
-TextSnip.prototype.splitStringAtWrap = function (
-    str: string,
-    maxWidth: number,
-    font: string,
-): string[] {
-    if (maxWidth == -1) {
-        return [str];
-    }
-
-    const canvas = createCanvas(1, 1);
-    const ctx = canvas.getContext("2d");
-    ctx.font = font;
-
-    let width = 0;
-    const splits = [];
-    for (let c = 0; c < str.length; c++) {
-        const char = str.charAt(c);
-        const cWidth = ctx.measureText(char).width;
-        width += cWidth;
-        if (width >= maxWidth) {
-            splits.push(c);
-            width = cWidth;
-        }
-    }
-
-    // split into segments
-    const segments: string[] = [];
-    let start = 0;
-
-    for (const split of splits) {
-        segments.push(str.slice(start, split));
-        start = split;
-    }
-
-    // Add the last segment if there's any remaining part of the string
-    if (start < str.length) {
-        segments.push(str.slice(start));
-    }
-    return segments;
-};
diff --git a/apps/socket/src/prefixes/snip/socket.ts b/apps/socket/src/prefixes/snip/socket.ts
index 0d2b9f96..405cbc45 100644
--- a/apps/socket/src/prefixes/snip/socket.ts
+++ b/apps/socket/src/prefixes/snip/socket.ts
@@ -212,7 +212,13 @@ async function update(
     await triggerRender(socket.book_id, page_id, server);
 }
 
-async function remove({ socket, server }: SocketData, id: ID, callback) {
+async function remove(
+    { socket, server }: SocketData,
+    id: ID,
+    callback: (
+        res: { success: true } | { success: false; details: string },
+    ) => void,
+) {
     // Snips can only be removed/erased if one has created them oneself
     // and if they are placed in the last 5 mins
     let page_id;
@@ -221,8 +227,9 @@ async function remove({ socket, server }: SocketData, id: ID, callback) {
         page_id = snip.page_id;
         if (
             snip.created_by != socket.data.id ||
-            snip.last_updated.getTime() <
-                Date.now() - client_config.TOOLS.ERASER.TIMEOUT * 1000
+            (snip.last_updated &&
+                snip.last_updated.getTime() <
+                    Date.now() - client_config.TOOLS.ERASER.TIMEOUT * 1000)
         ) {
             throw new Error("Cant delete snip!");
         }
@@ -236,7 +243,7 @@ async function remove({ socket, server }: SocketData, id: ID, callback) {
     } catch (e) {
         callback?.({
             success: false,
-            details: e.message,
+            details: ((e as Error)?.message as string) || "",
         });
     }
 
diff --git a/packages/database/src/services/snip.ts b/packages/database/src/services/snip.ts
index 2222bf7a..d1e6fdec 100644
--- a/packages/database/src/services/snip.ts
+++ b/packages/database/src/services/snip.ts
@@ -97,6 +97,10 @@ export class SnipService implements SnipStrategy {
             console.warn("SnipService.insert: id is set, inserting new snip");
         }
 
+        if (!snipData.created_by) {
+            console.warn(`Inserting without owner (created_by)!`);
+        }
+
         // Check if snip has blob and insert
         let blob_id: number | null = null;
         if (hasOwnProperty(snipData.data, "blob")) {
@@ -118,6 +122,10 @@ export class SnipService implements SnipStrategy {
             // Insert nested snips
             snip_data.snips = await Promise.all(
                 snip_data.snips.map(async (snipd) => {
+                    // Sanity overwrite for book id and created by
+                    snipd.created_by = snipData.created_by;
+                    snipd.book_id = snipData.book_id;
+
                     return await this.insert(snipd);
                 }),
             );
@@ -129,6 +137,11 @@ export class SnipService implements SnipStrategy {
             const snip_data = snipData.data as BaseData & {
                 snip: SnipData<BaseData, BaseView>;
             };
+
+            // Sanity overwrite for book id and created by
+            snip_data.snip.created_by = snipData.created_by;
+            snip_data.snip.book_id = snipData.book_id;
+
             snip_data.snip = await this.insert(snip_data.snip);
             snipData.data = snip_data;
         }
-- 
GitLab