mirror of
https://github.com/Steffo99/todocolors.git
synced 2024-11-25 01:34:18 +00:00
Implement Connect
and Disconnect
BoardChange
s on Todoblue
This commit is contained in:
parent
5ca640be39
commit
33cb947da3
16 changed files with 107 additions and 7 deletions
|
@ -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;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* **Object** signaling the connection of a new client to the board.
|
||||||
|
*/
|
||||||
|
export type ConnectBoardSignal = {
|
||||||
|
"Connect": string,
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
/**
|
||||||
|
* **Object** signaling the disconnection of a client from the board.
|
||||||
|
*/
|
||||||
|
export type DisconnectBoardSignal = {
|
||||||
|
"Disconnect": string,
|
||||||
|
}
|
|
@ -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[],
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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: []}
|
||||||
|
|
|
@ -58,9 +58,6 @@
|
||||||
var(--bhsl-background-lightness),
|
var(--bhsl-background-lightness),
|
||||||
100%
|
100%
|
||||||
);
|
);
|
||||||
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftButtonsArea {
|
.leftButtonsArea {
|
||||||
|
|
|
@ -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}/>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
|
@ -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}/>
|
||||||
|
|
||||||
|
{connectedClients.length}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue