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

Final progress for today

This commit is contained in:
Steffo 2023-08-03 06:16:45 +02:00
parent 0865ca94e7
commit 7a17afc267
Signed by: steffo
GPG key ID: 2A24051445686895
15 changed files with 352 additions and 30 deletions

View file

@ -7,7 +7,9 @@
</scripts> </scripts>
<arguments value="--port=8081" /> <arguments value="--port=8081" />
<node-interpreter value="project" /> <node-interpreter value="project" />
<envs /> <envs>
<env name="NEXT_PUBLIC_API_BASE_URL" value="ws://192.168.1.135:8080" />
</envs>
<method v="2" /> <method v="2" />
</configuration> </configuration>
</component> </component>

View file

@ -10,7 +10,7 @@
<option name="buildTarget" value="REMOTE" /> <option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" /> <option name="backtrace" value="SHORT" />
<envs> <envs>
<env name="AXUM_HOST" value="127.0.0.1:8080" /> <env name="AXUM_HOST" value="0.0.0.0:8080" />
<env name="REDIS_CONN" value="redis://127.0.0.1:6379/" /> <env name="REDIS_CONN" value="redis://127.0.0.1:6379/" />
<env name="RUST_LOG" value="todored" /> <env name="RUST_LOG" value="todored" />
</envs> </envs>

View file

@ -3,14 +3,22 @@
import {useBoardCreator} from "@/app/useBoardCreator" import {useBoardCreator} from "@/app/useBoardCreator"
import {faKey} from "@fortawesome/free-solid-svg-icons" import {faKey} from "@fortawesome/free-solid-svg-icons"
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome" import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import classNames from "classnames"
import {default as React} from "react" import {default as React} from "react"
export function CreatePrivateBoard() { export function CreatePrivateBoard() {
const {createBoard} = useBoardCreator(); const {createBoard} = useBoardCreator();
const isSecure = typeof window !== "undefined" && window.isSecureContext;
return ( return (
<form <form
className={"panel box form-flex"} className={classNames({
"panel": true,
"box": true,
"form-flex": true,
"red": !isSecure,
})}
onSubmit={e => { onSubmit={e => {
e.preventDefault(); e.preventDefault();
createBoard(crypto.randomUUID().toString()); createBoard(crypto.randomUUID().toString());
@ -21,18 +29,28 @@ export function CreatePrivateBoard() {
{" "} {" "}
Privato Privato
</h3> </h3>
<p> {isSecure ?
Crea un nuovo tabellone privato utilizzando un codice segreto autogenerato! <>
<br/> <p>
<small>Esso sarà accessibile solo da chi ne conosce il link.</small> Crea un nuovo tabellone privato utilizzando un codice segreto autogenerato!
</p> <br/>
<label className={"float-bottom"}> <small>Esso sarà accessibile solo da chi ne conosce il link.</small>
<span/> </p>
<button> <label className={"float-bottom"}>
Crea <span/>
</button> <button>
<span/> Crea
</label> </button>
<span/>
</label>
</> : <>
<p>
Questa funzionalità non è disponibile al di fuori di contesti sicuri.
<br/>
<small>Assicurati di stare usando HTTPS!</small>
</p>
</>
}
</form> </form>
) )
} }

View file

@ -1,3 +1,4 @@
import {BoardColumns} from "@/app/board/[board]/BoardColumns"
import {BoardError} from "@/app/board/[board]/BoardError" import {BoardError} from "@/app/board/[board]/BoardError"
import {BoardLoading} from "@/app/board/[board]/BoardLoading" import {BoardLoading} from "@/app/board/[board]/BoardLoading"
import {useBoardContext} from "@/app/board/[board]/useBoardContext" import {useBoardContext} from "@/app/board/[board]/useBoardContext"
@ -12,7 +13,7 @@ export function BoardBody() {
case WebSocket.CONNECTING: case WebSocket.CONNECTING:
return <BoardLoading text={"Connessione..."}/> return <BoardLoading text={"Connessione..."}/>
case WebSocket.OPEN: case WebSocket.OPEN:
return <>nothing here</> return <BoardColumns/>
case WebSocket.CLOSING: case WebSocket.CLOSING:
case WebSocket.CLOSED: case WebSocket.CLOSED:
return <BoardError text={"Errore"}/> return <BoardError text={"Errore"}/>

View file

@ -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 (
<div className={"chapter-5"}>
{taskGroups.map((tg) => <TaskGroupColumn taskGroup={tg}/>)}
</div>
)
}

View file

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

View file

@ -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 (
<div onClick={toggleStatus} tabIndex={0} className={cn({
"panel": true,
[style.taskDiv]: true,
[style.taskPriorityHighest]: task.priority === "Highest",
[style.taskPriorityHigh]: task.priority === "High",
[style.taskPriorityNormal]: task.priority === "Normal",
[style.taskPriorityLow]: task.priority === "Low",
[style.taskPriorityLowest]: task.priority === "Lowest",
[style.taskImportanceHighest]: task.importance === "Highest",
[style.taskImportanceHigh]: task.importance === "High",
[style.taskImportanceNormal]: task.importance === "Normal",
[style.taskImportanceLow]: task.importance === "Low",
[style.taskImportanceLowest]: task.importance === "Lowest",
[style.taskStatusUnfinished]: task.status === "Unfinished",
[style.taskStatusInProgress]: task.status === "InProgress",
[style.taskStatusComplete]: task.status === "Complete",
})}>
<div className={style.taskIcon}>
<TaskIconEl icon={task.icon} style={task.status === "Complete" ? "solid" : "regular"}/>
</div>
<div className={cn({
[style.taskDescription]: true,
[style.taskDescriptionComplete]: task.status === "Complete",
})}>
{task.text}
</div>
</div>
)
}

View file

@ -0,0 +1,5 @@
.taskGroupColumn {
display: flex;
flex-direction: column;
gap: 8px;
}

View file

@ -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 (
<div className={style.taskGroupColumn}>
<h3>
{taskGroup.name}
</h3>
{taskGroup.tasks.map(task => <TaskDiv task={task}/>)}
</div>
)
}

View file

@ -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 (
<FontAwesomeIcon icon={ICONS[style][icon]} size={size}/>
)
}

View file

@ -1,17 +1,30 @@
"use client"; "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 {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 {useBoardTitleEditor} from "@/app/board/[board]/useBoardTitleEditor"
import {useCycleState} from "@/app/useCycleState" 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" import {Dispatch, SetStateAction} from "react"
function groupTasksByIcon(a: TaskWithId) {return a.icon} function groupTasksByIcon(a: TaskWithId) {return a.icon}
function sortGroupsByKey(a: TaskGroup, b: TaskGroup) {return a.key.localeCompare(b.key)} function sortGroupsByKey(a: TaskGroup, b: TaskGroup) {return a.key.localeCompare(b.key)}
const TASK_GROUPERS: [TaskGroupingFunction, GroupSortingFunction][] = [ function nameToFontAwesomeIcon(a: string) {
[groupTasksByIcon, sortGroupsByKey], let icon = a as TaskIcon;
return <>
<TaskIconEl icon={icon} style={"solid"}/>
&nbsp;
{a}
</>
}
const TASK_GROUPERS: [TaskGroupingFunction, GroupSortingFunction, GroupNamingFunction][] = [
[groupTasksByIcon, sortGroupsByKey, nameToFontAwesomeIcon],
] ]
function sortTasksByText(a: TaskWithId, b: TaskWithId) {return a.text.localeCompare(b.text)} function sortTasksByText(a: TaskWithId, b: TaskWithId) {return a.text.localeCompare(b.text)}
@ -36,15 +49,16 @@ export interface UseBoardReturns {
previousSorter: () => void, previousSorter: () => void,
editTitle: string, editTitle: string,
setEditTitle: Dispatch<SetStateAction<string>>, setEditTitle: Dispatch<SetStateAction<string>>,
send: (action: BoardAction) => void,
} }
export function useBoard(name: string): UseBoardReturns { export function useBoard(name: string): UseBoardReturns {
const {state: {title, tasksById}, send, websocketState} = useBoardWebSocket(name); 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 {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); const {isEditingTitle, stopEditingTitle, startEditingTitle, toggleEditingTitle, editTitle, setEditTitle} = useBoardTitleEditor(title, send);
return { return {
@ -63,5 +77,6 @@ export function useBoard(name: string): UseBoardReturns {
previousSorter, previousSorter,
editTitle, editTitle,
setEditTitle, setEditTitle,
send,
} }
} }

View file

@ -1,18 +1,20 @@
"use client"; "use client";
import {Task, TaskWithId} from "@/app/board/[board]/types" import {Task, TaskWithId} from "@/app/board/[board]/types"
import {useMemo} from "react" import {ReactNode, useMemo} from "react"
export type TaskGroup = { export type TaskGroup = {
key: string, key: string,
name: ReactNode,
tasks: TaskWithId[], tasks: TaskWithId[],
} }
export type TaskGroupingFunction = (a: TaskWithId) => string export type TaskGroupingFunction = (a: TaskWithId) => string
export type TaskSortingFunction = (a: TaskWithId, b: TaskWithId) => number;
export type GroupSortingFunction = (a: TaskGroup, b: TaskGroup) => 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[]} = {} const groupsByKey: {[group: string]: TaskWithId[]} = {}
for(const [id, task] of Object.entries(tasksById)) { for(const [id, task] of Object.entries(tasksById)) {
@ -31,7 +33,8 @@ export function arrangeBoardTasks(tasksById: { [p: string]: Task }, taskGrouper:
const groups: TaskGroup[] = [] const groups: TaskGroup[] = []
for(const [key, tasks] of Object.entries(groupsByKey)) { for(const [key, tasks] of Object.entries(groupsByKey)) {
groups.push({key, tasks}) const name = groupNamer(key);
groups.push({key, name, tasks})
} }
groups.sort(groupSorter) 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) { export function useBoardTaskArranger(tasksById: { [p: string]: Task }, taskGrouper: TaskGroupingFunction, groupSorter: GroupSortingFunction, groupNamer: GroupNamingFunction, taskSorter: TaskSortingFunction) {
const taskGroups = useMemo(() => arrangeBoardTasks(tasksById, taskGrouper, groupSorter, taskSorter), [tasksById, taskGrouper, taskSorter, groupSorter]) const taskGroups = useMemo(() => arrangeBoardTasks(tasksById, taskGrouper, groupSorter, groupNamer, taskSorter), [tasksById, taskGrouper, taskSorter, groupSorter])
return {taskGroups}; return {taskGroups};
} }

View file

@ -2,6 +2,6 @@ import {useMemo} from "react"
export function useBoardWebSocketURL(name: string) { 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} return {webSocketURL}
} }

View file

@ -29,7 +29,8 @@ export default function RootLayout({children}: { children: ReactNode }) {
<p> <p>
© <a href="https://steffo.eu">Stefano Pigozzi</a> - © <a href="https://steffo.eu">Stefano Pigozzi</a> -
<a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL 3.0</a> - <a href="https://www.gnu.org/licenses/agpl-3.0.en.html">AGPL 3.0</a> -
<a href="https://github.com/Steffo99/todocolors">GitHub</a> <a href="https://github.com/Steffo99/todocolors">GitHub</a> -
Using {process.env.NEXT_PUBLIC_API_BASE_URL}
</p> </p>
</footer> </footer>
</body> </body>