mirror of
https://github.com/Steffo99/todocolors.git
synced 2024-11-22 08:14:18 +00:00
Refactor code
This commit is contained in:
parent
7549d4e1e3
commit
0865ca94e7
22 changed files with 464 additions and 176 deletions
20
todoblue/src/app/board/[board]/BoardBody.tsx
Normal file
20
todoblue/src/app/board/[board]/BoardBody.tsx
Normal file
|
@ -0,0 +1,20 @@
|
|||
import {BoardError} from "@/app/board/[board]/BoardError"
|
||||
import {BoardLoading} from "@/app/board/[board]/BoardLoading"
|
||||
import {useBoardContext} from "@/app/board/[board]/useBoardContext"
|
||||
|
||||
|
||||
export function BoardBody() {
|
||||
const {websocketState} = useBoardContext()
|
||||
|
||||
switch(websocketState) {
|
||||
case undefined:
|
||||
return <BoardLoading text={"Caricamento..."}/>
|
||||
case WebSocket.CONNECTING:
|
||||
return <BoardLoading text={"Connessione..."}/>
|
||||
case WebSocket.OPEN:
|
||||
return <>nothing here</>
|
||||
case WebSocket.CLOSING:
|
||||
case WebSocket.CLOSED:
|
||||
return <BoardError text={"Errore"}/>
|
||||
}
|
||||
}
|
4
todoblue/src/app/board/[board]/BoardContext.tsx
Normal file
4
todoblue/src/app/board/[board]/BoardContext.tsx
Normal file
|
@ -0,0 +1,4 @@
|
|||
import {UseBoardReturns} from "@/app/board/[board]/useBoard"
|
||||
import {createContext, useContext} from "react"
|
||||
|
||||
export const BoardContext = createContext<UseBoardReturns | null>(null);
|
7
todoblue/src/app/board/[board]/BoardError.module.css
Normal file
7
todoblue/src/app/board/[board]/BoardError.module.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.boardError {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
18
todoblue/src/app/board/[board]/BoardError.tsx
Normal file
18
todoblue/src/app/board/[board]/BoardError.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import style from "./BoardError.module.css"
|
||||
import {faExclamationCircle} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export function BoardError({text}: {text: string}) {
|
||||
return (
|
||||
<main className={classNames("red", style.boardError)}>
|
||||
<div>
|
||||
<FontAwesomeIcon size={"4x"} icon={faExclamationCircle}/>
|
||||
</div>
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
|
@ -2,6 +2,8 @@
|
|||
display: grid;
|
||||
align-items: center;
|
||||
padding-top: 4px;
|
||||
grid-row-gap: 4px;
|
||||
grid-column-gap: 10px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 450px) {
|
||||
|
@ -12,7 +14,6 @@
|
|||
;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-rows: auto auto;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +47,19 @@
|
|||
|
||||
.boardTitle {
|
||||
grid-area: title;
|
||||
height: 80px;
|
||||
}
|
||||
|
||||
.boardTitle div {
|
||||
padding-top: 0.125em;
|
||||
padding-right: 0.75ex;
|
||||
padding-left: 0.75ex;
|
||||
padding-bottom: calc(0.125em + 2px);
|
||||
}
|
||||
|
||||
.boardTitle input {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.boardButtons button {
|
40
todoblue/src/app/board/[board]/BoardHeader.tsx
Normal file
40
todoblue/src/app/board/[board]/BoardHeader.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import style from "./BoardHeader.module.css"
|
||||
import {useBoardContext} from "@/app/board/[board]/useBoardContext"
|
||||
import {faArrowDownWideShort, faHouse, faPencil, faTableColumns} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import cn from "classnames"
|
||||
|
||||
|
||||
export function BoardHeader() {
|
||||
const {title, isEditingTitle, editTitle, setEditTitle, toggleEditingTitle, nextGrouper, nextSorter, websocketState} = useBoardContext();
|
||||
|
||||
const isReady = websocketState === WebSocket.OPEN
|
||||
|
||||
return (
|
||||
<header className={style.boardHeader}>
|
||||
<div className={cn(style.boardButtons, style.boardButtonsLeft)}>
|
||||
<button title={"Home"}>
|
||||
<FontAwesomeIcon icon={faHouse}/>
|
||||
</button>
|
||||
<button className={cn({fade: !isReady})} disabled={!isReady} title={"Modifica titolo"} onClick={isReady ? toggleEditingTitle : undefined}>
|
||||
<FontAwesomeIcon icon={faPencil}/>
|
||||
</button>
|
||||
</div>
|
||||
<h1 className={cn({fade: !isReady, [style.boardTitle]: true})}>
|
||||
{isEditingTitle ?
|
||||
<input type={"text"} placeholder={"Titolo"} onChange={(e) => setEditTitle(e.target.value)} value={editTitle}/>
|
||||
:
|
||||
<div>{title}</div>
|
||||
}
|
||||
</h1>
|
||||
<div className={cn(style.boardButtons, style.boardButtonsRight)}>
|
||||
<button title={"Cambia raggruppamento orizzontale"} onClick={nextGrouper}>
|
||||
<FontAwesomeIcon icon={faTableColumns}/>
|
||||
</button>
|
||||
<button title={"Cambia ordinamento verticale"} onClick={nextSorter}>
|
||||
<FontAwesomeIcon icon={faArrowDownWideShort}/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
7
todoblue/src/app/board/[board]/BoardLoading.module.css
Normal file
7
todoblue/src/app/board/[board]/BoardLoading.module.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.boardLoading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
17
todoblue/src/app/board/[board]/BoardLoading.tsx
Normal file
17
todoblue/src/app/board/[board]/BoardLoading.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import style from "./BoardLoading.module.css"
|
||||
import {faSpinner} from "@fortawesome/free-solid-svg-icons"
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
export function BoardLoading({text}: {text: string}) {
|
||||
return (
|
||||
<main className={style.boardLoading}>
|
||||
<div>
|
||||
<FontAwesomeIcon size={"4x"} icon={faSpinner} pulse/>
|
||||
</div>
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
14
todoblue/src/app/board/[board]/BoardManager.tsx
Normal file
14
todoblue/src/app/board/[board]/BoardManager.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {BoardContext} from "@/app/board/[board]/BoardContext"
|
||||
import {useBoard} from "@/app/board/[board]/useBoard"
|
||||
import {ReactNode} from "react"
|
||||
|
||||
|
||||
export function BoardManager({name, children}: {name: string, children: ReactNode}) {
|
||||
const context = useBoard(name);
|
||||
|
||||
return (
|
||||
<BoardContext.Provider value={context}>
|
||||
{children}
|
||||
</BoardContext.Provider>
|
||||
)
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
import {Task} from "@/app/board/[board]/types"
|
||||
|
||||
|
||||
export function groupAndSortTasks(tasks: Task[], grouping: (a: Task) => string, sorting: (a: Task, b: Task) => number) {
|
||||
const groups: {[group: string]: Task[]} = {}
|
||||
|
||||
for(const task of tasks) {
|
||||
const group = grouping(task);
|
||||
if(!groups[group]) {
|
||||
groups[group] = [];
|
||||
}
|
||||
groups[group].push(task);
|
||||
}
|
||||
|
||||
for(const group of Object.keys(groups)) {
|
||||
groups[group].sort(sorting);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
|
@ -1,73 +1,15 @@
|
|||
"use client";
|
||||
|
||||
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
|
||||
import {useEffect} from "react"
|
||||
import {useBoardWebSocket} from "@/app/board/[board]/useBoardWebSocket"
|
||||
import style from "./page.module.css";
|
||||
import classNames from "classnames"
|
||||
import {faHouse, faPencil, faTableColumns, faArrowDownWideShort} from "@fortawesome/free-solid-svg-icons"
|
||||
import {BoardBody} from "@/app/board/[board]/BoardBody"
|
||||
import {BoardManager} from "@/app/board/[board]/BoardManager"
|
||||
import {BoardHeader} from "@/app/board/[board]/BoardHeader"
|
||||
|
||||
|
||||
export default function Page({params: {board}}: {params: {board: string}}) {
|
||||
const {tasks, title, pushEvent, readyState} = useBoardWebSocket(board);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("[Page] Current events: ", tasks)
|
||||
}, [tasks])
|
||||
|
||||
return <>
|
||||
<header className={style.boardHeader}>
|
||||
<div className={classNames(style.boardButtons, style.boardButtonsLeft)}>
|
||||
<button title={"Home"}>
|
||||
<FontAwesomeIcon icon={faHouse}/>
|
||||
</button>
|
||||
<button title={"Modifica titolo"}>
|
||||
<FontAwesomeIcon icon={faPencil}/>
|
||||
</button>
|
||||
</div>
|
||||
<h1 className={style.boardTitle}>
|
||||
{title}
|
||||
</h1>
|
||||
<div className={classNames(style.boardButtons, style.boardButtonsRight)}>
|
||||
<button title={"Cambia raggruppamento orizzontale"}>
|
||||
<FontAwesomeIcon icon={faTableColumns}/>
|
||||
</button>
|
||||
<button title={"Cambia ordinamento verticale"}>
|
||||
<FontAwesomeIcon icon={faArrowDownWideShort}/>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
<main>
|
||||
<div className={"chapter-5"}>
|
||||
<div>
|
||||
<h2>
|
||||
Gruppo A
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
Gruppo B
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
Gruppo C
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
Gruppo D
|
||||
</h2>
|
||||
</div>
|
||||
<div>
|
||||
<h2>
|
||||
Gruppo E
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
sos
|
||||
</footer>
|
||||
</>
|
||||
return (
|
||||
<BoardManager name={board}>
|
||||
<BoardHeader/>
|
||||
<BoardBody/>
|
||||
</BoardManager>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,44 +1,44 @@
|
|||
export type TaskIcon =
|
||||
"User" &
|
||||
"Image" &
|
||||
"Envelope" &
|
||||
"Star" &
|
||||
"Heart" &
|
||||
"Comment" &
|
||||
"FaceSmile" &
|
||||
"File" &
|
||||
"Bell" &
|
||||
"Bookmark" &
|
||||
"Eye" &
|
||||
"Hand" &
|
||||
"PaperPlane" &
|
||||
"Handshake" &
|
||||
"Sun" &
|
||||
"Clock" &
|
||||
"Circle" &
|
||||
"Square" &
|
||||
"Building" &
|
||||
"Flag" &
|
||||
"User" |
|
||||
"Image" |
|
||||
"Envelope" |
|
||||
"Star" |
|
||||
"Heart" |
|
||||
"Comment" |
|
||||
"FaceSmile" |
|
||||
"File" |
|
||||
"Bell" |
|
||||
"Bookmark" |
|
||||
"Eye" |
|
||||
"Hand" |
|
||||
"PaperPlane" |
|
||||
"Handshake" |
|
||||
"Sun" |
|
||||
"Clock" |
|
||||
"Circle" |
|
||||
"Square" |
|
||||
"Building" |
|
||||
"Flag" |
|
||||
"Moon";
|
||||
|
||||
export type TaskImportance =
|
||||
"Highest" &
|
||||
"High" &
|
||||
"Normal" &
|
||||
"Low" &
|
||||
"Highest" |
|
||||
"High" |
|
||||
"Normal" |
|
||||
"Low" |
|
||||
"Lowest";
|
||||
|
||||
export type TaskPriority =
|
||||
"Highest" &
|
||||
"High" &
|
||||
"Normal" &
|
||||
"Low" &
|
||||
"Highest" |
|
||||
"High" |
|
||||
"Normal" |
|
||||
"Low" |
|
||||
"Lowest";
|
||||
|
||||
export type TaskStatus =
|
||||
"Unfinished" &
|
||||
"InProgress" &
|
||||
"Complete"
|
||||
"Unfinished" |
|
||||
"InProgress" |
|
||||
"Complete";
|
||||
|
||||
export type Task = {
|
||||
text: string,
|
||||
|
@ -48,6 +48,10 @@ export type Task = {
|
|||
status: TaskStatus,
|
||||
}
|
||||
|
||||
export type TaskWithId = Task & {
|
||||
id: string,
|
||||
}
|
||||
|
||||
export type TitleBoardAction = {
|
||||
"Title": string,
|
||||
}
|
||||
|
@ -59,4 +63,6 @@ export type TaskBoardAction = {
|
|||
]
|
||||
}
|
||||
|
||||
export type BoardAction = TitleBoardAction & TaskBoardAction;
|
||||
export type BoardAction =
|
||||
TitleBoardAction |
|
||||
TaskBoardAction;
|
||||
|
|
67
todoblue/src/app/board/[board]/useBoard.ts
Normal file
67
todoblue/src/app/board/[board]/useBoard.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
"use client";
|
||||
|
||||
import {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 {useBoardTitleEditor} from "@/app/board/[board]/useBoardTitleEditor"
|
||||
import {useCycleState} from "@/app/useCycleState"
|
||||
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 sortTasksByText(a: TaskWithId, b: TaskWithId) {return a.text.localeCompare(b.text)}
|
||||
|
||||
const TASK_SORTERS: TaskSortingFunction[] = [
|
||||
sortTasksByText,
|
||||
];
|
||||
|
||||
export interface UseBoardReturns {
|
||||
title: string,
|
||||
taskGroups: TaskGroup[],
|
||||
websocketState: number,
|
||||
isEditingTitle: boolean,
|
||||
stopEditingTitle: () => void,
|
||||
startEditingTitle: () => void,
|
||||
toggleEditingTitle: () => void,
|
||||
moveGrouper: (n: number) => void,
|
||||
nextGrouper: () => void,
|
||||
previousGrouper: () => void,
|
||||
moveSorter: (n: number) => void,
|
||||
nextSorter: () => void,
|
||||
previousSorter: () => void,
|
||||
editTitle: string,
|
||||
setEditTitle: Dispatch<SetStateAction<string>>,
|
||||
}
|
||||
|
||||
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: taskSorter, move: moveSorter, next: nextSorter, previous: previousSorter} = useCycleState(TASK_SORTERS);
|
||||
|
||||
const {taskGroups} = useBoardTaskArranger(tasksById, taskGrouper, groupSorter, taskSorter);
|
||||
const {isEditingTitle, stopEditingTitle, startEditingTitle, toggleEditingTitle, editTitle, setEditTitle} = useBoardTitleEditor(title, send);
|
||||
|
||||
return {
|
||||
title,
|
||||
taskGroups,
|
||||
websocketState,
|
||||
isEditingTitle,
|
||||
stopEditingTitle,
|
||||
startEditingTitle,
|
||||
toggleEditingTitle,
|
||||
moveGrouper,
|
||||
nextGrouper,
|
||||
previousGrouper,
|
||||
moveSorter,
|
||||
nextSorter,
|
||||
previousSorter,
|
||||
editTitle,
|
||||
setEditTitle,
|
||||
}
|
||||
}
|
14
todoblue/src/app/board/[board]/useBoardContext.ts
Normal file
14
todoblue/src/app/board/[board]/useBoardContext.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import {BoardContext} from "@/app/board/[board]/BoardContext"
|
||||
import {UseBoardReturns} from "@/app/board/[board]/useBoard"
|
||||
import {useContext} from "react"
|
||||
|
||||
|
||||
export function useBoardContext(): UseBoardReturns {
|
||||
const context = useContext(BoardContext);
|
||||
|
||||
if(context === null) {
|
||||
throw Error()
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
48
todoblue/src/app/board/[board]/useBoardState.ts
Normal file
48
todoblue/src/app/board/[board]/useBoardState.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
"use client";
|
||||
|
||||
import {BoardAction, Task, TaskBoardAction, TitleBoardAction} from "@/app/board/[board]/types"
|
||||
import {Reducer, useReducer} from "react"
|
||||
|
||||
|
||||
type BoardState = {
|
||||
title: string,
|
||||
tasksById: {[key: string]: Task},
|
||||
}
|
||||
|
||||
function boardReducer(state: BoardState, action: BoardAction | null) {
|
||||
if(action === null) {
|
||||
console.debug("[boardReducer] Initializing state...");
|
||||
return {title: "", tasksById: {}}
|
||||
}
|
||||
else if(Object.hasOwn(action, "Title")) {
|
||||
const titleAction = action as TitleBoardAction;
|
||||
const title = titleAction["Title"]
|
||||
console.debug("[boardReducer] Setting board title to:", title)
|
||||
return {...state, title}
|
||||
}
|
||||
else if(Object.hasOwn(action, "Task")) {
|
||||
const taskAction = action as TaskBoardAction;
|
||||
const id = taskAction["Task"][0]
|
||||
const task = taskAction["Task"][1]
|
||||
const tasksById = {...state.tasksById}
|
||||
if(task === null) {
|
||||
console.debug("[boardReducer] Deleting task:", id)
|
||||
delete tasksById[id]
|
||||
}
|
||||
else {
|
||||
console.debug("[boardReducer] Putting task:", id)
|
||||
tasksById[id] = task
|
||||
}
|
||||
return {...state, tasksById}
|
||||
}
|
||||
else {
|
||||
console.warn("[boardReducer] Received unknown action, ignoring:", action)
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
export function useBoardState() {
|
||||
const [state, act] = useReducer<Reducer<BoardState, BoardAction | null>>(boardReducer, {title: "", tasksById: {}})
|
||||
|
||||
return {state, act}
|
||||
}
|
47
todoblue/src/app/board/[board]/useBoardTaskArranger.ts
Normal file
47
todoblue/src/app/board/[board]/useBoardTaskArranger.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
"use client";
|
||||
|
||||
import {Task, TaskWithId} from "@/app/board/[board]/types"
|
||||
import {useMemo} from "react"
|
||||
|
||||
export type TaskGroup = {
|
||||
key: string,
|
||||
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 function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, taskSorter: TaskSortingFunction): TaskGroup[] {
|
||||
const groupsByKey: {[group: string]: TaskWithId[]} = {}
|
||||
|
||||
for(const [id, task] of Object.entries(tasksById)) {
|
||||
const taskWithId = {...task, id};
|
||||
const group = taskGrouper(taskWithId);
|
||||
if(!groupsByKey[group]) {
|
||||
groupsByKey[group] = [];
|
||||
}
|
||||
groupsByKey[group].push(taskWithId);
|
||||
}
|
||||
|
||||
for(const group of Object.keys(groupsByKey)) {
|
||||
groupsByKey[group].sort(taskSorter);
|
||||
}
|
||||
|
||||
const groups: TaskGroup[] = []
|
||||
|
||||
for(const [key, tasks] of Object.entries(groupsByKey)) {
|
||||
groups.push({key, tasks})
|
||||
}
|
||||
|
||||
groups.sort(groupSorter)
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
|
||||
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])
|
||||
|
||||
return {taskGroups};
|
||||
}
|
31
todoblue/src/app/board/[board]/useBoardTitleEditor.ts
Normal file
31
todoblue/src/app/board/[board]/useBoardTitleEditor.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
"use client";
|
||||
|
||||
import {BoardAction} from "@/app/board/[board]/types"
|
||||
import {useCallback, useState} from "react"
|
||||
|
||||
|
||||
export function useBoardTitleEditor(title: string, send: (action: BoardAction) => void) {
|
||||
const [isEditingTitle, setEditingTitle] = useState<boolean>(false)
|
||||
const [editTitle, setEditTitle] = useState<string>(title)
|
||||
|
||||
const startEditingTitle = useCallback(() => {
|
||||
console.debug("[useEditableTitle] Starting title edit...");
|
||||
setEditingTitle(true);
|
||||
setEditTitle(title);
|
||||
}, [title])
|
||||
|
||||
const stopEditingTitle = useCallback(() => {
|
||||
console.debug("[useEditableTitle] Ending title edit...");
|
||||
setEditingTitle(false);
|
||||
if(editTitle) {
|
||||
console.debug("[useEditableTitle] Sending title change event...");
|
||||
send({"Title": editTitle})
|
||||
}
|
||||
}, [send, editTitle])
|
||||
|
||||
const toggleEditingTitle = useCallback(() => {
|
||||
return isEditingTitle ? stopEditingTitle() : startEditingTitle()
|
||||
}, [isEditingTitle, stopEditingTitle, startEditingTitle])
|
||||
|
||||
return {isEditingTitle, startEditingTitle, stopEditingTitle, toggleEditingTitle, editTitle, setEditTitle}
|
||||
}
|
|
@ -1,57 +1,36 @@
|
|||
'use client';
|
||||
|
||||
import {BoardAction, Task} from "@/app/board/[board]/types"
|
||||
import {useMemo, useCallback, useState} from "react"
|
||||
import {useWebSocket} from "@/app/board/[board]/useWebSocket"
|
||||
import {BoardAction} from "@/app/board/[board]/types"
|
||||
import {useBoardState} from "@/app/board/[board]/useBoardState"
|
||||
import {useBoardWebSocketURL} from "@/app/board/[board]/useBoardWebSocketURL"
|
||||
import {useCallback} from "react"
|
||||
import {useWebSocket} from "@/app/useWebSocket"
|
||||
|
||||
|
||||
export function useBoardWebSocket(board: string) {
|
||||
const url = useMemo(() => `ws://127.0.0.1:8080/board/${board}/ws`, [board]);
|
||||
const [title, setTitle] = useState<string>("Nuovo tabellone");
|
||||
const [tasks, setTasks] = useState<{[key: string]: Task}>(() => ({}));
|
||||
export function useBoardWebSocket(name: string) {
|
||||
const {webSocketURL} = useBoardWebSocketURL(name)
|
||||
const {state, act} = useBoardState();
|
||||
|
||||
const onopen = useCallback((sock: WebSocket, event: Event) => {
|
||||
setTasks(() => ({}))
|
||||
console.debug("[useBoardWebSocket] Connected to the websocket of board:", board);
|
||||
}, [])
|
||||
const {websocket, websocketState} = useWebSocket(webSocketURL, {
|
||||
onopen: useCallback((_sock: WebSocket, _event: Event) => {
|
||||
console.debug("[useBoard] Connected to board:", name);
|
||||
act(null);
|
||||
}, []),
|
||||
onmessage: useCallback((_sock: WebSocket, event: MessageEvent) => {
|
||||
const action: BoardAction = JSON.parse(event.data);
|
||||
console.debug("[useBoard] Received:", action);
|
||||
act(action)
|
||||
}, []),
|
||||
});
|
||||
|
||||
const onmessage = useCallback((sock: WebSocket, event: MessageEvent) => {
|
||||
const data: BoardAction = JSON.parse(event.data);
|
||||
console.debug("[useBoardWebSocket] Received:", data);
|
||||
if(data["Title"] !== undefined) {
|
||||
setTitle(data["Title"]);
|
||||
}
|
||||
else if(data["Task"] !== undefined) {
|
||||
const id = data["Task"][0]
|
||||
const task = data["Task"][1]
|
||||
setTasks((prevTasks) => {
|
||||
const tasks = {...prevTasks}
|
||||
if(task === null) {
|
||||
delete tasks[id]
|
||||
}
|
||||
else {
|
||||
tasks[id] = task
|
||||
}
|
||||
return tasks
|
||||
})
|
||||
}
|
||||
}, [])
|
||||
|
||||
const {websocket} = useWebSocket(url, {onopen, onmessage});
|
||||
const readyState = websocket?.readyState;
|
||||
|
||||
const pushEvent = useCallback((data: any) => {
|
||||
if(!websocket) {
|
||||
console.warn("[useBoardWebSocket] Socket does not exist yet, cannot send:", data)
|
||||
return;
|
||||
}
|
||||
if(readyState != 1) {
|
||||
console.warn("[useBoardWebSocket] Socket isn't ready yet, cannot send:", data);
|
||||
const send = useCallback((data: BoardAction) => {
|
||||
if(!websocket || websocketState !== WebSocket.OPEN) {
|
||||
console.warn("[useBoardWebSocket] Webbsocket is not yet ready, cannot send:", data);
|
||||
return;
|
||||
}
|
||||
console.debug("[useBoardWebSocket] Sending:", data);
|
||||
websocket.send(JSON.stringify(data));
|
||||
}, [websocket, readyState])
|
||||
}, [websocket, websocketState])
|
||||
|
||||
return {title, tasks, pushEvent, readyState}
|
||||
return {state, send, websocketState}
|
||||
}
|
||||
|
|
7
todoblue/src/app/board/[board]/useBoardWebSocketURL.ts
Normal file
7
todoblue/src/app/board/[board]/useBoardWebSocketURL.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {useMemo} from "react"
|
||||
|
||||
|
||||
export function useBoardWebSocketURL(name: string) {
|
||||
const webSocketURL = useMemo(() => `ws://127.0.0.1:8080/board/${name}/ws`, [name]);
|
||||
return {webSocketURL}
|
||||
}
|
17
todoblue/src/app/useCycleState.ts
Normal file
17
todoblue/src/app/useCycleState.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import {useCallback, useMemo, useState} from "react"
|
||||
|
||||
|
||||
export function useCycleState(items: any[]) {
|
||||
const [index, setIndex] = useState<number>(0);
|
||||
|
||||
const value = useMemo(() => items[index], [index])
|
||||
|
||||
const move = useCallback((num: number) => {
|
||||
setIndex((prevIndex) => (prevIndex + num) % items.length);
|
||||
}, [items])
|
||||
|
||||
const next = useCallback(() => move(1), [move]);
|
||||
const previous = useCallback(() => move(-1), [move]);
|
||||
|
||||
return {index, value, move, next, previous}
|
||||
}
|
|
@ -11,22 +11,22 @@ export interface WebSocketHandlers {
|
|||
|
||||
export function useWebSocket(url: string, {onclose, onerror, onmessage, onopen}: WebSocketHandlers) {
|
||||
const [websocket, setWebsocket] = useState<WebSocket | null>(null)
|
||||
const [readyState, setReadyState] = useState<number>(0);
|
||||
const [websocketState, setWebsocketState] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
console.debug("[useWebSocket] Creating websocket...");
|
||||
const sock = new WebSocket(url);
|
||||
setWebsocket(sock);
|
||||
sock.onopen = (ev) => {
|
||||
setReadyState(sock.readyState);
|
||||
setWebsocketState(sock.readyState);
|
||||
onopen?.(sock, ev);
|
||||
}
|
||||
sock.onclose = (ev) => {
|
||||
setReadyState(sock.readyState);
|
||||
setWebsocketState(sock.readyState);
|
||||
onclose?.(sock, ev);
|
||||
}
|
||||
sock.onerror = (ev) => {
|
||||
setReadyState(sock.readyState);
|
||||
setWebsocketState(sock.readyState);
|
||||
onerror?.(sock, ev);
|
||||
}
|
||||
sock.onmessage = (ev) => {
|
||||
|
@ -39,5 +39,5 @@ export function useWebSocket(url: string, {onclose, onerror, onmessage, onopen}:
|
|||
}
|
||||
}, [url, onclose, onerror, onmessage, onopen])
|
||||
|
||||
return {websocket, readyState}
|
||||
return {websocket, websocketState}
|
||||
}
|
|
@ -114,7 +114,16 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@18.2.17":
|
||||
"@types/react@*":
|
||||
version "18.2.18"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.18.tgz#c8b233919eef1bdc294f6f34b37f9727ad677516"
|
||||
integrity sha512-da4NTSeBv/P34xoZPhtcLkmZuJ+oYaCxHmyHzwaDQo9RQPBeXV+06gEk2FpqEcsX9XrnNLvRpVh6bdavDSjtiQ==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@18.2.17":
|
||||
version "18.2.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.17.tgz#baa565b17ddb649c2dac85b5eaf9e9a1fe0f3b4e"
|
||||
integrity sha512-u+e7OlgPPh+aryjOm5UJMX32OvB2E3QASOAqVMY6Ahs90djagxwv2ya0IctglNbNTexC12qCSMZG47KPfy1hAA==
|
||||
|
@ -136,9 +145,9 @@ busboy@1.6.0:
|
|||
streamsearch "^1.1.0"
|
||||
|
||||
caniuse-lite@^1.0.30001406:
|
||||
version "1.0.30001517"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz#90fabae294215c3495807eb24fc809e11dc2f0a8"
|
||||
integrity sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==
|
||||
version "1.0.30001518"
|
||||
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001518.tgz#b3ca93904cb4699c01218246c4d77a71dbe97150"
|
||||
integrity sha512-rup09/e3I0BKjncL+FesTayKtPrdwKhUufQFd3riFw1hHg8JmIFoInYfB102cFcY/pPgGmdyl/iy+jgiDi2vdA==
|
||||
|
||||
classnames@^2.3.2:
|
||||
version "2.3.2"
|
||||
|
|
Loading…
Reference in a new issue