diff --git a/.idea/runConfigurations/Run_client.xml b/.idea/runConfigurations/Run_client.xml
index 4ad3839..aef77a2 100644
--- a/.idea/runConfigurations/Run_client.xml
+++ b/.idea/runConfigurations/Run_client.xml
@@ -7,7 +7,9 @@
-
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Run_server.xml b/.idea/runConfigurations/Run_server.xml
index 6d631f7..8faeddd 100644
--- a/.idea/runConfigurations/Run_server.xml
+++ b/.idea/runConfigurations/Run_server.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/todoblue/src/app/CreatePrivateBoard.tsx b/todoblue/src/app/CreatePrivateBoard.tsx
index 9884aa6..14d96de 100644
--- a/todoblue/src/app/CreatePrivateBoard.tsx
+++ b/todoblue/src/app/CreatePrivateBoard.tsx
@@ -3,14 +3,22 @@
import {useBoardCreator} from "@/app/useBoardCreator"
import {faKey} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
+import classNames from "classnames"
import {default as React} from "react"
export function CreatePrivateBoard() {
const {createBoard} = useBoardCreator();
+ const isSecure = typeof window !== "undefined" && window.isSecureContext;
+
return (
)
}
diff --git a/todoblue/src/app/board/[board]/BoardBody.tsx b/todoblue/src/app/board/[board]/BoardBody.tsx
index 0e634c9..8b6ee0a 100644
--- a/todoblue/src/app/board/[board]/BoardBody.tsx
+++ b/todoblue/src/app/board/[board]/BoardBody.tsx
@@ -1,3 +1,4 @@
+import {BoardColumns} from "@/app/board/[board]/BoardColumns"
import {BoardError} from "@/app/board/[board]/BoardError"
import {BoardLoading} from "@/app/board/[board]/BoardLoading"
import {useBoardContext} from "@/app/board/[board]/useBoardContext"
@@ -12,7 +13,7 @@ export function BoardBody() {
case WebSocket.CONNECTING:
return
case WebSocket.OPEN:
- return <>nothing here>
+ return
case WebSocket.CLOSING:
case WebSocket.CLOSED:
return
diff --git a/todoblue/src/app/board/[board]/BoardColumns.tsx b/todoblue/src/app/board/[board]/BoardColumns.tsx
new file mode 100644
index 0000000..4ba7bf7
--- /dev/null
+++ b/todoblue/src/app/board/[board]/BoardColumns.tsx
@@ -0,0 +1,13 @@
+import {TaskGroupColumn} from "@/app/board/[board]/TaskGroupColumn"
+import {useBoardContext} from "@/app/board/[board]/useBoardContext"
+
+
+export function BoardColumns() {
+ const {taskGroups} = useBoardContext()
+
+ return (
+
+ {taskGroups.map((tg) => )}
+
+ )
+}
diff --git a/todoblue/src/app/board/[board]/TaskDiv.module.css b/todoblue/src/app/board/[board]/TaskDiv.module.css
new file mode 100644
index 0000000..ed628ae
--- /dev/null
+++ b/todoblue/src/app/board/[board]/TaskDiv.module.css
@@ -0,0 +1,94 @@
+.taskDiv {
+ display: grid;
+ grid-template-areas:
+ "icon description"
+ ;
+ grid-template-columns: auto 1fr;
+ align-items: center;
+ min-width: unset;
+}
+
+.taskIcon {
+ grid-area: icon;
+ justify-self: start;
+
+ cursor: pointer;
+}
+
+.taskDescription {
+ grid-area: description;
+ justify-self: start;
+}
+
+.taskDescriptionComplete {
+ text-decoration: 2px currentColor solid line-through;
+}
+
+.taskPriorityHighest {
+ border: 4px solid hsl(var(--bhsl-current-hue) var(--bhsl-current-saturation) var(--bhsl-current-lightness) / 0.15);
+ padding: 4px;
+}
+
+.taskPriorityHigh {
+ border: 3px solid hsl(var(--bhsl-current-hue) var(--bhsl-current-saturation) var(--bhsl-current-lightness) / 0.15);
+ padding: 5px;
+}
+
+.taskPriorityNormal {
+ border: 2px solid hsl(var(--bhsl-current-hue) var(--bhsl-current-saturation) var(--bhsl-current-lightness) / 0.15);
+ padding: 6px;
+}
+
+.taskPriorityLow {
+ border: 1px solid hsl(var(--bhsl-current-hue) var(--bhsl-current-saturation) var(--bhsl-current-lightness) / 0.15);
+ padding: 7px;
+}
+
+.taskPriorityLowest {
+ border: 0;
+ padding: 8px;
+}
+
+.taskImportanceHighest {
+ --bhsl-current-hue: 100deg;
+ --bhsl-current-saturation: 37%;
+ --bhsl-current-lightness: 68.2%;
+}
+
+.taskImportanceHigh {
+ --bhsl-current-hue: 159deg;
+ --bhsl-current-saturation: 30%;
+ --bhsl-current-lightness: 52%;
+}
+
+.taskImportanceNormal {
+ --bhsl-current-hue: 186deg;
+ --bhsl-current-saturation: 47%;
+ --bhsl-current-lightness: 38%;
+}
+
+.taskImportanceLow {
+ --bhsl-current-hue: 203deg;
+ --bhsl-current-saturation: 64%;
+ --bhsl-current-lightness: 32%;
+}
+
+.taskImportanceLowest {
+ --bhsl-current-hue: 237deg;
+ --bhsl-current-saturation: 44%;
+ --bhsl-current-lightness: 31%;
+}
+
+@keyframes inProgress {
+ 0% {
+ filter: brightness(100%);
+ }
+
+ 100% {
+ filter: brightness(150%);
+ }
+}
+
+.taskStatusInProgress {
+ animation: 1.7s inProgress ease-out infinite alternate-reverse;
+}
diff --git a/todoblue/src/app/board/[board]/TaskDiv.tsx b/todoblue/src/app/board/[board]/TaskDiv.tsx
new file mode 100644
index 0000000..c088da5
--- /dev/null
+++ b/todoblue/src/app/board/[board]/TaskDiv.tsx
@@ -0,0 +1,49 @@
+import {TaskIconEl} from "@/app/board/[board]/TaskIconEl"
+import {Task, TaskWithId} from "@/app/board/[board]/Types"
+import {useBoardContext} from "@/app/board/[board]/useBoardContext"
+import {useCallback} from "react"
+import style from "./TaskDiv.module.css"
+import cn from "classnames"
+
+export function TaskDiv({task}: {task: TaskWithId}) {
+ const {send} = useBoardContext()
+
+ const toggleStatus = useCallback(() => {
+ if(task.status === "Unfinished") {
+ send({"Task": [task.id, {...task, status: "Complete"}]})
+ }
+ else if(task.status === "Complete") {
+ send({"Task": [task.id, {...task, status: "Unfinished"}]})
+ }
+ }, [send, task])
+
+ return (
+
+
+
+
+
+ {task.text}
+
+
+ )
+}
diff --git a/todoblue/src/app/board/[board]/TaskGroupColumn.module.css b/todoblue/src/app/board/[board]/TaskGroupColumn.module.css
new file mode 100644
index 0000000..51c34df
--- /dev/null
+++ b/todoblue/src/app/board/[board]/TaskGroupColumn.module.css
@@ -0,0 +1,5 @@
+.taskGroupColumn {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+}
diff --git a/todoblue/src/app/board/[board]/TaskGroupColumn.tsx b/todoblue/src/app/board/[board]/TaskGroupColumn.tsx
new file mode 100644
index 0000000..15d1d84
--- /dev/null
+++ b/todoblue/src/app/board/[board]/TaskGroupColumn.tsx
@@ -0,0 +1,15 @@
+import {TaskDiv} from "@/app/board/[board]/TaskDiv"
+import {TaskGroup} from "@/app/board/[board]/useBoardTaskArranger"
+import style from "./TaskGroupColumn.module.css"
+
+
+export function TaskGroupColumn({taskGroup}: {taskGroup: TaskGroup}) {
+ return (
+
+
+ {taskGroup.name}
+
+ {taskGroup.tasks.map(task => )}
+
+ )
+}
diff --git a/todoblue/src/app/board/[board]/TaskIconEl.tsx b/todoblue/src/app/board/[board]/TaskIconEl.tsx
new file mode 100644
index 0000000..1c9a453
--- /dev/null
+++ b/todoblue/src/app/board/[board]/TaskIconEl.tsx
@@ -0,0 +1,106 @@
+import {TaskIcon} from "@/app/board/[board]/Types"
+import {SizeProp} from "@fortawesome/fontawesome-svg-core"
+import {
+ faUser as faUserSolid,
+ faImage as faImageSolid,
+ faEnvelope as faEnvelopeSolid,
+ faStar as faStarSolid,
+ faHeart as faHeartSolid,
+ faComment as faCommentSolid,
+ faFaceSmile as faFaceSmileSolid,
+ faFile as faFileSolid,
+ faBell as faBellSolid,
+ faBookmark as faBookmarkSolid,
+ faEye as faEyeSolid,
+ faHand as faHandSolid,
+ faPaperPlane as faPaperPlaneSolid,
+ faHandshake as faHandshakeSolid,
+ faSun as faSunSolid,
+ faClock as faClockSolid,
+ faCircle as faCircleSolid,
+ faSquare as faSquareSolid,
+ faBuilding as faBuildingSolid,
+ faFlag as faFlagSolid,
+ faMoon as faMoonSolid,
+} from "@fortawesome/free-solid-svg-icons"
+import {
+ faUser as faUserRegular,
+ faImage as faImageRegular,
+ faEnvelope as faEnvelopeRegular,
+ faStar as faStarRegular,
+ faHeart as faHeartRegular,
+ faComment as faCommentRegular,
+ faFaceSmile as faFaceSmileRegular,
+ faFile as faFileRegular,
+ faBell as faBellRegular,
+ faBookmark as faBookmarkRegular,
+ faEye as faEyeRegular,
+ faHand as faHandRegular,
+ faPaperPlane as faPaperPlaneRegular,
+ faHandshake as faHandshakeRegular,
+ faSun as faSunRegular,
+ faClock as faClockRegular,
+ faCircle as faCircleRegular,
+ faSquare as faSquareRegular,
+ faBuilding as faBuildingRegular,
+ faFlag as faFlagRegular,
+ faMoon as faMoonRegular,
+} from "@fortawesome/free-regular-svg-icons"
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
+
+
+export const ICONS = {
+ solid: {
+ "User": faUserSolid,
+ "Image": faImageSolid,
+ "Envelope": faEnvelopeSolid,
+ "Star": faStarSolid,
+ "Heart": faHeartSolid,
+ "Comment": faCommentSolid,
+ "FaceSmile": faFaceSmileSolid,
+ "File": faFileSolid,
+ "Bell": faBellSolid,
+ "Bookmark": faBookmarkSolid,
+ "Eye": faEyeSolid,
+ "Hand": faHandSolid,
+ "PaperPlane": faPaperPlaneSolid,
+ "Handshake": faHandshakeSolid,
+ "Sun": faSunSolid,
+ "Clock": faClockSolid,
+ "Circle": faCircleSolid,
+ "Square": faSquareSolid,
+ "Building": faBuildingSolid,
+ "Flag": faFlagSolid,
+ "Moon": faMoonSolid,
+ },
+ regular: {
+ "User": faUserRegular,
+ "Image": faImageRegular,
+ "Envelope": faEnvelopeRegular,
+ "Star": faStarRegular,
+ "Heart": faHeartRegular,
+ "Comment": faCommentRegular,
+ "FaceSmile": faFaceSmileRegular,
+ "File": faFileRegular,
+ "Bell": faBellRegular,
+ "Bookmark": faBookmarkRegular,
+ "Eye": faEyeRegular,
+ "Hand": faHandRegular,
+ "PaperPlane": faPaperPlaneRegular,
+ "Handshake": faHandshakeRegular,
+ "Sun": faSunRegular,
+ "Clock": faClockRegular,
+ "Circle": faCircleRegular,
+ "Square": faSquareRegular,
+ "Building": faBuildingRegular,
+ "Flag": faFlagRegular,
+ "Moon": faMoonRegular,
+ }
+}
+
+
+export function TaskIconEl({icon, style, size}: {icon: TaskIcon, style: "solid" | "regular", size?: SizeProp}) {
+ return (
+
+ )
+}
diff --git a/todoblue/src/app/board/[board]/types.ts b/todoblue/src/app/board/[board]/Types.ts
similarity index 100%
rename from todoblue/src/app/board/[board]/types.ts
rename to todoblue/src/app/board/[board]/Types.ts
diff --git a/todoblue/src/app/board/[board]/useBoard.ts b/todoblue/src/app/board/[board]/useBoard.tsx
similarity index 61%
rename from todoblue/src/app/board/[board]/useBoard.ts
rename to todoblue/src/app/board/[board]/useBoard.tsx
index cef9159..cff021a 100644
--- a/todoblue/src/app/board/[board]/useBoard.ts
+++ b/todoblue/src/app/board/[board]/useBoard.tsx
@@ -1,17 +1,30 @@
"use client";
-import {TaskWithId} from "@/app/board/[board]/types"
+import {TaskIconEl} from "@/app/board/[board]/TaskIconEl"
+import {BoardAction} from "@/app/board/[board]/Types"
+import {TaskIcon, TaskWithId} from "@/app/board/[board]/types"
import {useBoardWebSocket} from "@/app/board/[board]/useBoardWebSocket"
-import {GroupSortingFunction, TaskGroup, TaskGroupingFunction, TaskSortingFunction, useBoardTaskArranger} from "@/app/board/[board]/useBoardTaskArranger"
+import {GroupNamingFunction, GroupSortingFunction, TaskGroup, TaskGroupingFunction, TaskSortingFunction, useBoardTaskArranger} from "@/app/board/[board]/useBoardTaskArranger"
import {useBoardTitleEditor} from "@/app/board/[board]/useBoardTitleEditor"
import {useCycleState} from "@/app/useCycleState"
+import {faBell, faBookmark, faBuilding, faCircle, faClock, faComment, faEnvelope, faEye, faFaceSmile, faFile, faFlag, faHand, faHandshake, faHeart, faImage, faMoon, faPaperPlane, faSquare, faStar, faSun, faUser, IconDefinition} from "@fortawesome/free-solid-svg-icons"
+import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {Dispatch, SetStateAction} from "react"
function groupTasksByIcon(a: TaskWithId) {return a.icon}
function sortGroupsByKey(a: TaskGroup, b: TaskGroup) {return a.key.localeCompare(b.key)}
-const TASK_GROUPERS: [TaskGroupingFunction, GroupSortingFunction][] = [
- [groupTasksByIcon, sortGroupsByKey],
+function nameToFontAwesomeIcon(a: string) {
+ let icon = a as TaskIcon;
+ return <>
+
+
+ {a}
+ >
+}
+
+const TASK_GROUPERS: [TaskGroupingFunction, GroupSortingFunction, GroupNamingFunction][] = [
+ [groupTasksByIcon, sortGroupsByKey, nameToFontAwesomeIcon],
]
function sortTasksByText(a: TaskWithId, b: TaskWithId) {return a.text.localeCompare(b.text)}
@@ -36,15 +49,16 @@ export interface UseBoardReturns {
previousSorter: () => void,
editTitle: string,
setEditTitle: Dispatch>,
+ send: (action: BoardAction) => void,
}
export function useBoard(name: string): UseBoardReturns {
const {state: {title, tasksById}, send, websocketState} = useBoardWebSocket(name);
- const {value: [taskGrouper, groupSorter], move: moveGrouper, next: nextGrouper, previous: previousGrouper} = useCycleState(TASK_GROUPERS);
+ const {value: [taskGrouper, groupSorter, groupNamer], move: moveGrouper, next: nextGrouper, previous: previousGrouper} = useCycleState(TASK_GROUPERS);
const {value: taskSorter, move: moveSorter, next: nextSorter, previous: previousSorter} = useCycleState(TASK_SORTERS);
- const {taskGroups} = useBoardTaskArranger(tasksById, taskGrouper, groupSorter, taskSorter);
+ const {taskGroups} = useBoardTaskArranger(tasksById, taskGrouper, groupSorter, groupNamer, taskSorter);
const {isEditingTitle, stopEditingTitle, startEditingTitle, toggleEditingTitle, editTitle, setEditTitle} = useBoardTitleEditor(title, send);
return {
@@ -63,5 +77,6 @@ export function useBoard(name: string): UseBoardReturns {
previousSorter,
editTitle,
setEditTitle,
+ send,
}
}
diff --git a/todoblue/src/app/board/[board]/useBoardTaskArranger.ts b/todoblue/src/app/board/[board]/useBoardTaskArranger.ts
index 8e9c8c4..f94107b 100644
--- a/todoblue/src/app/board/[board]/useBoardTaskArranger.ts
+++ b/todoblue/src/app/board/[board]/useBoardTaskArranger.ts
@@ -1,18 +1,20 @@
"use client";
import {Task, TaskWithId} from "@/app/board/[board]/types"
-import {useMemo} from "react"
+import {ReactNode, useMemo} from "react"
export type TaskGroup = {
key: string,
+ name: ReactNode,
tasks: TaskWithId[],
}
export type TaskGroupingFunction = (a: TaskWithId) => string
-export type TaskSortingFunction = (a: TaskWithId, b: TaskWithId) => number;
export type GroupSortingFunction = (a: TaskGroup, b: TaskGroup) => number;
+export type GroupNamingFunction = (a: string) => ReactNode;
+export type TaskSortingFunction = (a: TaskWithId, b: TaskWithId) => number;
-export function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, taskSorter: TaskSortingFunction): TaskGroup[] {
+export function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, groupNamer: GroupNamingFunction, taskSorter: TaskSortingFunction): TaskGroup[] {
const groupsByKey: {[group: string]: TaskWithId[]} = {}
for(const [id, task] of Object.entries(tasksById)) {
@@ -31,7 +33,8 @@ export function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper:
const groups: TaskGroup[] = []
for(const [key, tasks] of Object.entries(groupsByKey)) {
- groups.push({key, tasks})
+ const name = groupNamer(key);
+ groups.push({key, name, tasks})
}
groups.sort(groupSorter)
@@ -40,8 +43,8 @@ export function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper:
}
-export function useBoardTaskArranger(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, taskSorter: TaskSortingFunction) {
- const taskGroups = useMemo(() => arrangeBoardTasks(tasksById, taskGrouper, groupSorter, taskSorter), [tasksById, taskGrouper, taskSorter, groupSorter])
+export function useBoardTaskArranger(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, groupNamer: GroupNamingFunction, taskSorter: TaskSortingFunction) {
+ const taskGroups = useMemo(() => arrangeBoardTasks(tasksById, taskGrouper, groupSorter, groupNamer, taskSorter), [tasksById, taskGrouper, taskSorter, groupSorter])
return {taskGroups};
}
diff --git a/todoblue/src/app/board/[board]/useBoardWebSocketURL.ts b/todoblue/src/app/board/[board]/useBoardWebSocketURL.ts
index 1d77a88..fd3bb94 100644
--- a/todoblue/src/app/board/[board]/useBoardWebSocketURL.ts
+++ b/todoblue/src/app/board/[board]/useBoardWebSocketURL.ts
@@ -2,6 +2,6 @@ import {useMemo} from "react"
export function useBoardWebSocketURL(name: string) {
- const webSocketURL = useMemo(() => `ws://127.0.0.1:8080/board/${name}/ws`, [name]);
+ const webSocketURL = useMemo(() => `${process.env.NEXT_PUBLIC_API_BASE_URL}/board/${name}/ws`, [name]);
return {webSocketURL}
}
diff --git a/todoblue/src/app/layout.tsx b/todoblue/src/app/layout.tsx
index 439d942..fe4b9a7 100644
--- a/todoblue/src/app/layout.tsx
+++ b/todoblue/src/app/layout.tsx
@@ -29,7 +29,8 @@ export default function RootLayout({children}: { children: ReactNode }) {
© Stefano Pigozzi -
AGPL 3.0 -
- GitHub
+ GitHub -
+ Using {process.env.NEXT_PUBLIC_API_BASE_URL}