diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/BoardSignal.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/BoardSignal.ts index 5252c37..591aa2a 100644 --- a/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/BoardSignal.ts +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/BoardSignal.ts @@ -1,5 +1,7 @@ +import {ConnectBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/ConnectBoardSignal" +import {DisconnectBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/DisconnectBoardSignal" import {TaskBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/TaskBoardSignal" import {TitleBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/TitleBoardSignal" -export type BoardSignal = TaskBoardSignal | TitleBoardSignal; +export type BoardSignal = TaskBoardSignal | TitleBoardSignal | ConnectBoardSignal | DisconnectBoardSignal; diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/ConnectBoardSignal.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/ConnectBoardSignal.ts new file mode 100644 index 0000000..df81e8f --- /dev/null +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/ConnectBoardSignal.ts @@ -0,0 +1,6 @@ +/** + * **Object** signaling the connection of a new client to the board. + */ +export type ConnectBoardSignal = { + "Connect": string, +} diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/DisconnectBoardSignal.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/DisconnectBoardSignal.ts new file mode 100644 index 0000000..1cd2227 --- /dev/null +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(signal)/DisconnectBoardSignal.ts @@ -0,0 +1,6 @@ +/** + * **Object** signaling the disconnection of a client from the board. + */ +export type DisconnectBoardSignal = { + "Disconnect": string, +} diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/BoardState.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/BoardState.ts index 65c67c0..6a53c14 100644 --- a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/BoardState.ts +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/BoardState.ts @@ -7,4 +7,5 @@ import {Task} from "@/app/[lang]/board/[board]/(api)/(task)" export type BoardState = { title: string, tasksById: {[key: string]: Task}, + connectedClients: string[], } diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/boardReducer.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/boardReducer.ts index 9995059..4bef106 100644 --- a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/boardReducer.ts +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/boardReducer.ts @@ -1,4 +1,6 @@ import {BoardSignal, DeleteTaskBoardSignal, TaskBoardSignal, TitleBoardSignal, UpdateTaskBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)" +import {ConnectBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/ConnectBoardSignal" +import {DisconnectBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/DisconnectBoardSignal" import {BoardState} from "@/app/[lang]/board/[board]/(api)/(state)/BoardState" import {DEFAULT_BOARD_STATE} from "@/app/[lang]/board/[board]/(api)/(state)/defaultBoardState" @@ -41,6 +43,28 @@ export function boardReducer(state: BoardState, action: BoardSignal | null) { } return {...state, tasksById} } + else if("Connect" in action) { + const connectAction = action as ConnectBoardSignal; + const id = connectAction["Connect"]; + const connectedClients = [...state.connectedClients, id] + console.debug("[boardReducer] Adding new client:", id) + return {...state, connectedClients} + } + else if("Disconnect" in action) { + const disconnectAction = action as DisconnectBoardSignal; + const id = disconnectAction["Disconnect"]; + const connectedClients = [...state.connectedClients] + const clientIndex = connectedClients.indexOf(id) + if(clientIndex !== -1) { + connectedClients.splice(clientIndex, 1); + console.debug("[boardReducer] Removing client:", id) + return {...state, connectedClients} + } + else { + console.warn("[boardReducer] Received DisconnectBoardSignal without the client being connected in first place.") + return state + } + } else { console.warn("[boardReducer] Received unknown signal, ignoring:", action) return state diff --git a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/defaultBoardState.ts b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/defaultBoardState.ts index b09e46b..632fdc4 100644 --- a/todoblue/src/app/[lang]/board/[board]/(api)/(state)/defaultBoardState.ts +++ b/todoblue/src/app/[lang]/board/[board]/(api)/(state)/defaultBoardState.ts @@ -4,4 +4,4 @@ import {BoardState} from "@/app/[lang]/board/[board]/(api)/(state)/BoardState" /** * **Object** denoting the {@link BoardState} of a board where no {@link BoardAction}s have been performed. */ -export const DEFAULT_BOARD_STATE: BoardState = {title: "", tasksById: {}} +export const DEFAULT_BOARD_STATE: BoardState = {title: "", tasksById: {}, connectedClients: []} diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.module.css b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.module.css index eba2815..f9315cf 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.module.css +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.module.css @@ -58,9 +58,6 @@ var(--bhsl-background-lightness), 100% ); - - width: 36px; - height: 36px; } .leftButtonsArea { diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.tsx index 87bccf2..4f533b3 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeader.tsx @@ -3,6 +3,7 @@ import {CycleColumningButton} from "@/app/[lang]/board/[board]/(page)/(header)/C import {CycleGroupingButton} from "@/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton" import {CycleSortingButton} from "@/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton" import {NavigateHomeButton} from "@/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton" +import {ConnectedClientsButton} from "@/app/[lang]/board/[board]/(page)/(header)/ConnectedClientsButton" import {ToggleEditingButton} from "@/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton" import {ToggleStarredButton} from "@/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton" import {useBoardLayoutEditor} from "@/app/[lang]/board/[board]/(page)/useBoardLayoutEditor" @@ -25,7 +26,7 @@ export function BoardHeader({lang, className, metadataHook, layoutHook: {columni
- +
+ diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css new file mode 100644 index 0000000..ac6946b --- /dev/null +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css @@ -0,0 +1,16 @@ +.block { + display: inline-flex; + justify-content: center; + align-items: center; + + height: 36px; + padding: 0.125em 0.75ex; +} + +.singleBlock { + width: 36px; +} + +.doubleBlock { + width: 76px; +} diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ConnectedClientsButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ConnectedClientsButton.tsx new file mode 100644 index 0000000..0ee3aeb --- /dev/null +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ConnectedClientsButton.tsx @@ -0,0 +1,25 @@ +import {useClientTranslation} from "@/app/(i18n)/client" +import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" +import {faUsers} from "@fortawesome/free-solid-svg-icons" +import cn from "classnames" + + +export function ConnectedClientsButton({lang}: {lang: string}) { + const {isReady, boardState: {connectedClients}} = useBoardConsumer() + const {t} = useClientTranslation(lang, "board") + + if(!isReady) return null; + + return ( +
+ +   + {connectedClients.length} +
+ ) +} diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleColumningButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleColumningButton.tsx index f6efe30..b333340 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleColumningButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleColumningButton.tsx @@ -1,5 +1,7 @@ import {useClientTranslation} from "@/app/(i18n)/client" import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" +import cn from "classnames" import {COLUMNING_MODE_TO_ICON, ColumningMode} from "../(view)/(columning)" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" @@ -14,6 +16,7 @@ export function CycleColumningButton({lang, value, next}: {lang: string, value: diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton.tsx index 8090076..f3942d7 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton.tsx @@ -1,7 +1,9 @@ import {useClientTranslation} from "@/app/(i18n)/client" import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {faObjectGroup} from "@fortawesome/free-solid-svg-icons" +import cn from "classnames" export function CycleGroupingButton({lang, next}: {lang: string, next: () => void}) { @@ -14,6 +16,7 @@ export function CycleGroupingButton({lang, next}: {lang: string, next: () => voi diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton.tsx index 270213d..f3395cb 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton.tsx @@ -1,7 +1,9 @@ import {useClientTranslation} from "@/app/(i18n)/client" import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {faArrowDownWideShort} from "@fortawesome/free-solid-svg-icons" +import cn from "classnames" export function CycleSortingButton({lang, next}: {lang: string, next: () => void}) { @@ -14,6 +16,7 @@ export function CycleSortingButton({lang, next}: {lang: string, next: () => void diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton.tsx index 43cea5d..75c30e0 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton.tsx @@ -1,6 +1,8 @@ import {useClientTranslation} from "@/app/(i18n)/client" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" import {faHouse} from "@fortawesome/free-solid-svg-icons" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" +import cn from "classnames" import {useRouter} from "next/navigation" import {useCallback} from "react" @@ -11,7 +13,11 @@ export function NavigateHomeButton({lang}: {lang: string}) { const goHome = useCallback(() => router.push("/"), [router]) return ( - ) diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton.tsx index 92a5d04..76fd1ba 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton.tsx @@ -1,8 +1,10 @@ import {useClientTranslation} from "@/app/(i18n)/client" import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" import {useBoardMetadataEditor} from "@/app/[lang]/board/[board]/(page)/useBoardMetadataEditor" import {faFloppyDisk, faPencil} from "@fortawesome/free-solid-svg-icons" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" +import cn from "classnames" export function ToggleEditingButton({lang, metadataHook}: {lang: string, metadataHook: ReturnType}) { @@ -15,6 +17,7 @@ export function ToggleEditingButton({lang, metadataHook}: {lang: string, metadat diff --git a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton.tsx b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton.tsx index d18d27f..be2a4b3 100644 --- a/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton.tsx +++ b/todoblue/src/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton.tsx @@ -1,9 +1,11 @@ import {useClientTranslation} from "@/app/(i18n)/client" import {useStarredConsumer} from "@/app/[lang]/(layout)/(contextStarred)" import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" +import style from "@/app/[lang]/board/[board]/(page)/(header)/BoardHeaderButtons.module.css" import {faStar as faStarRegular} from "@fortawesome/free-regular-svg-icons" import {faStar as faStarSolid} from "@fortawesome/free-solid-svg-icons" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" +import cn from "classnames" export function ToggleStarredButton({lang}: {lang: string}) { @@ -16,6 +18,7 @@ export function ToggleStarredButton({lang}: {lang: string}) {