From 83ebd8fddaa824520cfa296ee8cc4d4912682d7e Mon Sep 17 00:00:00 2001
From: Maximilian Giller <m.giller@tu-bs.de>
Date: Fri, 30 Sep 2022 23:42:41 +0200
Subject: [PATCH] A somewhat working visibility date implementation

---
 src/common/graph/node.ts                      |  4 +-
 src/editor/components/datevisibilityinput.css |  8 ++
 src/editor/components/datevisibilityinput.tsx | 95 +++++++++++++++++++
 src/editor/components/nodedetails.css         |  5 +
 src/editor/components/nodedetails.tsx         | 42 ++++----
 src/editor/components/nodetypeentry.css       |  5 +
 src/editor/components/nodetypeentry.tsx       | 12 ++-
 src/editor/components/referenceseditor.tsx    |  1 -
 8 files changed, 147 insertions(+), 25 deletions(-)
 create mode 100644 src/editor/components/datevisibilityinput.css
 create mode 100644 src/editor/components/datevisibilityinput.tsx

diff --git a/src/common/graph/node.ts b/src/common/graph/node.ts
index 2a1126a..cdfd492 100644
--- a/src/common/graph/node.ts
+++ b/src/common/graph/node.ts
@@ -14,7 +14,7 @@ export interface NodeProperties {
     banner?: string;
     video?: string;
     references?: DescriptiveReference[];
-    visibleAfter?: Date;
+    visibleAfter?: string;  // YYYY-MM-DD
 }
 
 export interface NodeData extends NodeProperties {
@@ -76,7 +76,7 @@ export class Node
     public banner: string;
     public video: string;
     public references: DescriptiveReference[];
-    public visibleAfter: Date;
+    public visibleAfter?: string;
 
     public neighbors: Node[];
     public links: Link[];
diff --git a/src/editor/components/datevisibilityinput.css b/src/editor/components/datevisibilityinput.css
new file mode 100644
index 0000000..903a032
--- /dev/null
+++ b/src/editor/components/datevisibilityinput.css
@@ -0,0 +1,8 @@
+div#ks-editor #date-checkbox {
+    width: initial;
+}
+
+div#ks-editor label {
+    vertical-align: text-top;
+    line-height: 0.6;
+}
diff --git a/src/editor/components/datevisibilityinput.tsx b/src/editor/components/datevisibilityinput.tsx
new file mode 100644
index 0000000..623ac5b
--- /dev/null
+++ b/src/editor/components/datevisibilityinput.tsx
@@ -0,0 +1,95 @@
+import React, { useEffect, useState } from "react";
+import "./datevisibilityinput.css";
+
+type DateVisibilityInputProps = {
+    date?: string; // YYYY-MM-DD
+    onDateChange: (date?: string) => void;
+};
+
+function DateVisibilityInput({ date, onDateChange }: DateVisibilityInputProps) {
+    const isDate = (date?: string): boolean => {
+        if (!date) {
+            return false;
+        }
+
+        // Format: YYYY-MM-DD
+        // Try to convert to date and trigger onChange
+        const splits: string[] = date.split("-", 3);
+        if (splits.length != 3) {
+            return false;
+        }
+
+        const year = parseInt(splits[0]);
+        const month = parseInt(splits[1]);
+        const day = parseInt(splits[2]);
+
+        if (isNaN(day) || isNaN(month) || isNaN(year)) {
+            return false;
+        }
+
+        return true;
+    };
+
+    const dateToString = (date: Date): string => {
+        return (
+            date.getFullYear() +
+            "-" +
+            date.getMonth().toString().padStart(2, "0") +
+            "-" +
+            date.getDate().toString().padStart(2, "0")
+        );
+    };
+
+    const initialEditedDate = () => {
+        return isDate(date) ? date : dateToString(new Date());
+    };
+
+    const [editedDate, setEditedDate] = useState<string>(initialEditedDate());
+    const [alwaysVisible, setAlwaysVisible] = useState<boolean>(!isDate(date));
+
+    useEffect(() => {
+        setEditedDate(initialEditedDate());
+        setAlwaysVisible(!isDate(date));
+    }, [date]);
+
+    const updateVisibilityState = (visible: boolean) => {
+        setAlwaysVisible(visible);
+
+        if (alwaysVisible) {
+            onDateChange(undefined);
+        } else {
+            onDateChange(editedDate);
+        }
+    };
+
+    const handleDateChange = (date?: string) => {
+        if (isDate(date)) {
+            setEditedDate(date);
+            updateVisibilityState(false);
+        } else {
+            updateVisibilityState(true);
+        }
+    };
+
+    return (
+        <div className="date-input">
+            <input
+                id="date-checkbox"
+                type={"checkbox"}
+                checked={alwaysVisible}
+                onChange={(e) => updateVisibilityState(e.target.checked)}
+            />
+            <label htmlFor="date-checkbox">Always visible</label>
+            {editedDate}
+            {!alwaysVisible && (
+                <input
+                    type={"date"}
+                    value={editedDate}
+                    onChange={(e) => handleDateChange(e.target.value)}
+                />
+            )}
+        </div>
+    );
+}
+
+export default DateVisibilityInput;
diff --git a/src/editor/components/nodedetails.css b/src/editor/components/nodedetails.css
index 4211813..9357fb8 100644
--- a/src/editor/components/nodedetails.css
+++ b/src/editor/components/nodedetails.css
@@ -16,3 +16,8 @@ div#ks-editor #nodedetails #node-name {
 div#ks-editor #nodedetails .empty-select-option {
     display: none;
 }
+
+div#ks-editor #nodedetails h4 {
+    margin-bottom: 6px;
+    margin-top: 10px;
+}
diff --git a/src/editor/components/nodedetails.tsx b/src/editor/components/nodedetails.tsx
index 8f7981c..5718597 100644
--- a/src/editor/components/nodedetails.tsx
+++ b/src/editor/components/nodedetails.tsx
@@ -4,6 +4,7 @@ import { NodeType } from "../../common/graph/nodetype";
 import "./nodedetails.css";
 import { NodeDataChangeRequest } from "../editor";
 import ReferencesEditor from "./referenceseditor";
+import DateVisibilityInput from "./datevisibilityinput";
 
 type NodeDetailsProps = {
     selectedNodes: Node[];
@@ -47,6 +48,7 @@ function NodeDetails({
         banner: getCollectiveValue((n) => n.banner, undefined),
         references: getCollectiveValue((n) => n.references, undefined),
         type: getCollectiveValue((n) => n.type, undefined),
+        visibleAfter: getCollectiveValue((n) => n.visibleAfter, undefined),
     };
 
     const handleDataChange = function <ValueType>(
@@ -96,9 +98,6 @@ function NodeDetails({
         <div id="nodedetails" onBlur={handleBlur}>
             {selectedNodes.length === 1 ? (
                 <div>
-                    <label htmlFor="node-name" hidden>
-                        Name
-                    </label>
                     <input
                         type="text"
                         id="node-name"
@@ -117,8 +116,7 @@ function NodeDetails({
 
             {selectedNodes.length === 1 && (
                 <div>
-                    <label htmlFor="node-description">Description</label>
-                    <br />
+                    <h4>Description</h4>
                     <textarea
                         id="node-description"
                         name="node-description"
@@ -132,8 +130,7 @@ function NodeDetails({
                 </div>
             )}
             <div>
-                <label htmlFor="node-image">Icon Image</label>
-                <br />
+                <h4>Icon Image</h4>
                 {referenceData.icon && (
                     <div>
                         <img
@@ -157,8 +154,7 @@ function NodeDetails({
                 />
             </div>
             <div>
-                <label htmlFor="node-detail-image">Banner Image</label>
-                <br />
+                <h4>Banner Image</h4>
                 {referenceData.banner && (
                     <div>
                         <img
@@ -182,8 +178,7 @@ function NodeDetails({
                 />
             </div>
             <div>
-                <label htmlFor="node-type">Type</label>
-                <br />
+                <h4>Type</h4>
                 <select
                     id="node-type"
                     name="node-type"
@@ -205,8 +200,7 @@ function NodeDetails({
                 </select>
             </div>
             <div>
-                <label htmlFor="node-video">Video</label>
-                <br />
+                <h4>Video</h4>
                 <input
                     type="text"
                     placeholder="Video URL"
@@ -218,13 +212,25 @@ function NodeDetails({
                     }
                 ></input>
             </div>
-            {selectedNodes.length === 1 && (
-                <ReferencesEditor
-                    references={referenceData.references ?? []}
-                    onReferencesChange={(references) =>
-                        handleDataChange("references", references)
+            <div>
+                <h4>Visible after date</h4>
+                <DateVisibilityInput
+                    date={referenceData.visibleAfter}
+                    onDateChange={(date) =>
+                        handleDataChange("visibleAfter", date)
                     }
                 />
+            </div>
+            {selectedNodes.length === 1 && (
+                <div>
+                    <h4>References</h4>
+                    <ReferencesEditor
+                        references={referenceData.references ?? []}
+                        onReferencesChange={(references) =>
+                            handleDataChange("references", references)
+                        }
+                    />
+                </div>
             )}
         </div>
     );
diff --git a/src/editor/components/nodetypeentry.css b/src/editor/components/nodetypeentry.css
index 36fbe89..ea3719d 100644
--- a/src/editor/components/nodetypeentry.css
+++ b/src/editor/components/nodetypeentry.css
@@ -17,3 +17,8 @@ div#ks-editor .color-circle {
     margin: 10px;
     border-radius: 50%;
 }
+
+div#ks-editor h4 {
+    margin-bottom: 6px;
+    margin-top: 10px;
+}
diff --git a/src/editor/components/nodetypeentry.tsx b/src/editor/components/nodetypeentry.tsx
index c3f84a9..6550a14 100644
--- a/src/editor/components/nodetypeentry.tsx
+++ b/src/editor/components/nodetypeentry.tsx
@@ -59,8 +59,7 @@ function NodeTypeEntry({
 
     return (
         <div className="node-type" onBlur={handleBlur}>
-            <label htmlFor="node-type-name">Name</label>
-            <br />
+            <h4>Name</h4>
             <input
                 className="node-type-name"
                 type={"text"}
@@ -70,13 +69,14 @@ function NodeTypeEntry({
                 }
             />
             <br />
-            <label>Color</label>
-            <br />
+            <h4>Color</h4>
             <label htmlFor="node-type-color-r">R</label>
             <input
                 id="node-type-color-r"
                 className="node-type-color"
                 type={"number"}
+                min={"0"}
+                max={"255"}
                 value={nodeType.color.r.toString()}
                 onChange={(event) => handleColorChange("r", event.target.value)}
             />
@@ -86,6 +86,8 @@ function NodeTypeEntry({
                 id="node-type-color-g"
                 className="node-type-color"
                 type={"number"}
+                min={"0"}
+                max={"255"}
                 value={nodeType.color.g.toString()}
                 onChange={(event) => handleColorChange("g", event.target.value)}
             />
@@ -95,6 +97,8 @@ function NodeTypeEntry({
                 id="node-type-color-b"
                 className="node-type-color"
                 type={"number"}
+                min={"0"}
+                max={"255"}
                 value={nodeType.color.b.toString()}
                 onChange={(event) => handleColorChange("b", event.target.value)}
             />
diff --git a/src/editor/components/referenceseditor.tsx b/src/editor/components/referenceseditor.tsx
index 00dfedf..f8c6a8a 100644
--- a/src/editor/components/referenceseditor.tsx
+++ b/src/editor/components/referenceseditor.tsx
@@ -35,7 +35,6 @@ function ReferencesEditor({
 
     return (
         <div id="references-editor">
-            <label htmlFor="references-editor">References</label>
             {references.map(
                 (reference: DescriptiveReference, index: number) => {
                     return (
-- 
GitLab