1
Fork 0
mirror of https://github.com/Steffo99/todocolors.git synced 2024-11-24 17:24:18 +00:00

Implement Connect and Disconnect BoardChanges on Todoblue

This commit is contained in:
Steffo 2023-08-22 05:44:41 +02:00
parent 5ca640be39
commit 33cb947da3
Signed by: steffo
GPG key ID: 2A24051445686895
16 changed files with 107 additions and 7 deletions

View file

@ -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 {TaskBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/TaskBoardSignal"
import {TitleBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/TitleBoardSignal" import {TitleBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)/TitleBoardSignal"
export type BoardSignal = TaskBoardSignal | TitleBoardSignal; export type BoardSignal = TaskBoardSignal | TitleBoardSignal | ConnectBoardSignal | DisconnectBoardSignal;

View file

@ -0,0 +1,6 @@
/**
* **Object** signaling the connection of a new client to the board.
*/
export type ConnectBoardSignal = {
"Connect": string,
}

View file

@ -0,0 +1,6 @@
/**
* **Object** signaling the disconnection of a client from the board.
*/
export type DisconnectBoardSignal = {
"Disconnect": string,
}

View file

@ -7,4 +7,5 @@ import {Task} from "@/app/[lang]/board/[board]/(api)/(task)"
export type BoardState = { export type BoardState = {
title: string, title: string,
tasksById: {[key: string]: Task}, tasksById: {[key: string]: Task},
connectedClients: string[],
} }

View file

@ -1,4 +1,6 @@
import {BoardSignal, DeleteTaskBoardSignal, TaskBoardSignal, TitleBoardSignal, UpdateTaskBoardSignal} from "@/app/[lang]/board/[board]/(api)/(signal)" 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 {BoardState} from "@/app/[lang]/board/[board]/(api)/(state)/BoardState"
import {DEFAULT_BOARD_STATE} from "@/app/[lang]/board/[board]/(api)/(state)/defaultBoardState" 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} 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 { else {
console.warn("[boardReducer] Received unknown signal, ignoring:", action) console.warn("[boardReducer] Received unknown signal, ignoring:", action)
return state return state

View file

@ -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. * **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: []}

View file

@ -58,9 +58,6 @@
var(--bhsl-background-lightness), var(--bhsl-background-lightness),
100% 100%
); );
width: 36px;
height: 36px;
} }
.leftButtonsArea { .leftButtonsArea {

View file

@ -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 {CycleGroupingButton} from "@/app/[lang]/board/[board]/(page)/(header)/CycleGroupingButton"
import {CycleSortingButton} from "@/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton" import {CycleSortingButton} from "@/app/[lang]/board/[board]/(page)/(header)/CycleSortingButton"
import {NavigateHomeButton} from "@/app/[lang]/board/[board]/(page)/(header)/NavigateHomeButton" 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 {ToggleEditingButton} from "@/app/[lang]/board/[board]/(page)/(header)/ToggleEditingButton"
import {ToggleStarredButton} from "@/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton" import {ToggleStarredButton} from "@/app/[lang]/board/[board]/(page)/(header)/ToggleStarredButton"
import {useBoardLayoutEditor} from "@/app/[lang]/board/[board]/(page)/useBoardLayoutEditor" import {useBoardLayoutEditor} from "@/app/[lang]/board/[board]/(page)/useBoardLayoutEditor"
@ -25,7 +26,7 @@ export function BoardHeader({lang, className, metadataHook, layoutHook: {columni
<div className={cn(style.buttonsArea, style.leftButtonsArea)}> <div className={cn(style.buttonsArea, style.leftButtonsArea)}>
<NavigateHomeButton lang={lang}/> <NavigateHomeButton lang={lang}/>
<ToggleStarredButton lang={lang}/> <ToggleStarredButton lang={lang}/>
<ToggleEditingButton lang={lang} metadataHook={metadataHook}/> <ConnectedClientsButton lang={lang}/>
</div> </div>
<BoardHeaderTitle <BoardHeaderTitle
lang={lang} lang={lang}
@ -33,6 +34,7 @@ export function BoardHeader({lang, className, metadataHook, layoutHook: {columni
editorHook={metadataHook} editorHook={metadataHook}
/> />
<div className={cn(style.buttonsArea, style.rightButtonsArea)}> <div className={cn(style.buttonsArea, style.rightButtonsArea)}>
<ToggleEditingButton lang={lang} metadataHook={metadataHook}/>
<CycleColumningButton lang={lang} value={columningHook.value} next={columningHook.next}/> <CycleColumningButton lang={lang} value={columningHook.value} next={columningHook.next}/>
<CycleGroupingButton lang={lang} next={groupingHook.next}/> <CycleGroupingButton lang={lang} next={groupingHook.next}/>
<CycleSortingButton lang={lang} next={sortingHook.next}/> <CycleSortingButton lang={lang} next={sortingHook.next}/>

View file

@ -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;
}

View file

@ -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 (
<div
title={t("privacyButtonTitle")}
className={cn(style.block, style.doubleBlock)}
>
<FontAwesomeIcon icon={faUsers}/>
&nbsp;
{connectedClients.length}
</div>
)
}

View file

@ -1,5 +1,7 @@
import {useClientTranslation} from "@/app/(i18n)/client" import {useClientTranslation} from "@/app/(i18n)/client"
import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" 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 {COLUMNING_MODE_TO_ICON, ColumningMode} from "../(view)/(columning)"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
@ -14,6 +16,7 @@ export function CycleColumningButton({lang, value, next}: {lang: string, value:
<button <button
title={t("cycleColumningButtonTitle")} title={t("cycleColumningButtonTitle")}
onClick={next} onClick={next}
className={cn(style.block, style.singleBlock)}
> >
<FontAwesomeIcon icon={COLUMNING_MODE_TO_ICON[value]}/> <FontAwesomeIcon icon={COLUMNING_MODE_TO_ICON[value]}/>
</button> </button>

View file

@ -1,7 +1,9 @@
import {useClientTranslation} from "@/app/(i18n)/client" import {useClientTranslation} from "@/app/(i18n)/client"
import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" 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 {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {faObjectGroup} from "@fortawesome/free-solid-svg-icons" import {faObjectGroup} from "@fortawesome/free-solid-svg-icons"
import cn from "classnames"
export function CycleGroupingButton({lang, next}: {lang: string, next: () => void}) { export function CycleGroupingButton({lang, next}: {lang: string, next: () => void}) {
@ -14,6 +16,7 @@ export function CycleGroupingButton({lang, next}: {lang: string, next: () => voi
<button <button
title={t("cycleGroupingButtonTitle")} title={t("cycleGroupingButtonTitle")}
onClick={next} onClick={next}
className={cn(style.block, style.singleBlock)}
> >
<FontAwesomeIcon icon={faObjectGroup}/> <FontAwesomeIcon icon={faObjectGroup}/>
</button> </button>

View file

@ -1,7 +1,9 @@
import {useClientTranslation} from "@/app/(i18n)/client" import {useClientTranslation} from "@/app/(i18n)/client"
import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" 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 {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {faArrowDownWideShort} from "@fortawesome/free-solid-svg-icons" import {faArrowDownWideShort} from "@fortawesome/free-solid-svg-icons"
import cn from "classnames"
export function CycleSortingButton({lang, next}: {lang: string, next: () => void}) { export function CycleSortingButton({lang, next}: {lang: string, next: () => void}) {
@ -14,6 +16,7 @@ export function CycleSortingButton({lang, next}: {lang: string, next: () => void
<button <button
title={t("cycleSortingButtonTitle")} title={t("cycleSortingButtonTitle")}
onClick={next} onClick={next}
className={cn(style.block, style.singleBlock)}
> >
<FontAwesomeIcon icon={faArrowDownWideShort}/> <FontAwesomeIcon icon={faArrowDownWideShort}/>
</button> </button>

View file

@ -1,6 +1,8 @@
import {useClientTranslation} from "@/app/(i18n)/client" 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 {faHouse} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import cn from "classnames"
import {useRouter} from "next/navigation" import {useRouter} from "next/navigation"
import {useCallback} from "react" import {useCallback} from "react"
@ -11,7 +13,11 @@ export function NavigateHomeButton({lang}: {lang: string}) {
const goHome = useCallback(() => router.push("/"), [router]) const goHome = useCallback(() => router.push("/"), [router])
return ( return (
<button title={t("navigateHomeButtonTitle")} onClick={goHome}> <button
title={t("navigateHomeButtonTitle")}
onClick={goHome}
className={cn(style.block, style.singleBlock)}
>
<FontAwesomeIcon icon={faHouse}/> <FontAwesomeIcon icon={faHouse}/>
</button> </button>
) )

View file

@ -1,8 +1,10 @@
import {useClientTranslation} from "@/app/(i18n)/client" import {useClientTranslation} from "@/app/(i18n)/client"
import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" 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 {useBoardMetadataEditor} from "@/app/[lang]/board/[board]/(page)/useBoardMetadataEditor"
import {faFloppyDisk, faPencil} from "@fortawesome/free-solid-svg-icons" import {faFloppyDisk, faPencil} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import cn from "classnames"
export function ToggleEditingButton({lang, metadataHook}: {lang: string, metadataHook: ReturnType<typeof useBoardMetadataEditor>}) { export function ToggleEditingButton({lang, metadataHook}: {lang: string, metadataHook: ReturnType<typeof useBoardMetadataEditor>}) {
@ -15,6 +17,7 @@ export function ToggleEditingButton({lang, metadataHook}: {lang: string, metadat
<button <button
title={metadataHook.isEditingMetadata ? t("stopEditingButtonTitle") : t("startEditingButtonTitle")} title={metadataHook.isEditingMetadata ? t("stopEditingButtonTitle") : t("startEditingButtonTitle")}
onClick={metadataHook.toggleEditingMetadata} onClick={metadataHook.toggleEditingMetadata}
className={cn(style.block, style.singleBlock)}
> >
<FontAwesomeIcon icon={metadataHook.isEditingMetadata ? faFloppyDisk : faPencil}/> <FontAwesomeIcon icon={metadataHook.isEditingMetadata ? faFloppyDisk : faPencil}/>
</button> </button>

View file

@ -1,9 +1,11 @@
import {useClientTranslation} from "@/app/(i18n)/client" import {useClientTranslation} from "@/app/(i18n)/client"
import {useStarredConsumer} from "@/app/[lang]/(layout)/(contextStarred)" import {useStarredConsumer} from "@/app/[lang]/(layout)/(contextStarred)"
import {useBoardConsumer} from "@/app/[lang]/board/[board]/(layout)/(contextBoard)" 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 faStarRegular} from "@fortawesome/free-regular-svg-icons"
import {faStar as faStarSolid} from "@fortawesome/free-solid-svg-icons" import {faStar as faStarSolid} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import cn from "classnames"
export function ToggleStarredButton({lang}: {lang: string}) { export function ToggleStarredButton({lang}: {lang: string}) {
@ -16,6 +18,7 @@ export function ToggleStarredButton({lang}: {lang: string}) {
<button <button
title={thisIsStarred ? t("removeStarredButtonTitle") : t("addStarredButtonTitle")} title={thisIsStarred ? t("removeStarredButtonTitle") : t("addStarredButtonTitle")}
onClick={() => toggleStarred(boardName)} onClick={() => toggleStarred(boardName)}
className={cn(style.block, style.singleBlock)}
> >
<FontAwesomeIcon icon={thisIsStarred ? faStarSolid : faStarRegular}/> <FontAwesomeIcon icon={thisIsStarred ? faStarSolid : faStarRegular}/>
</button> </button>