1
Fork 0
mirror of https://github.com/Steffo99/todocolors.git synced 2024-11-22 08:14:18 +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>
<arguments value="--port=8081" />
<node-interpreter value="project" />
<envs />
<envs>
<env name="NEXT_PUBLIC_API_BASE_URL" value="ws://192.168.1.135:8080" />
</envs>
<method v="2" />
</configuration>
</component>

View file

@ -10,7 +10,7 @@
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<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="RUST_LOG" value="todored" />
</envs>

View file

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

View file

@ -1,3 +1,4 @@
import {BoardColumns} from "@/app/board/[board]/BoardColumns"
import {BoardError} from "@/app/board/[board]/BoardError"
import {BoardLoading} from "@/app/board/[board]/BoardLoading"
import {useBoardContext} from "@/app/board/[board]/useBoardContext"
@ -12,7 +13,7 @@ export function BoardBody() {
case WebSocket.CONNECTING:
return <BoardLoading text={"Connessione..."}/>
case WebSocket.OPEN:
return <>nothing here</>
return <BoardColumns/>
case WebSocket.CLOSING:
case WebSocket.CLOSED:
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";
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 {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 {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"
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 nameToFontAwesomeIcon(a: string) {
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)}
@ -36,15 +49,16 @@ export interface UseBoardReturns {
previousSorter: () => void,
editTitle: string,
setEditTitle: Dispatch<SetStateAction<string>>,
send: (action: BoardAction) => void,
}
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: [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 {taskGroups} = useBoardTaskArranger(tasksById, taskGrouper, groupSorter, taskSorter);
const {taskGroups} = useBoardTaskArranger(tasksById, taskGrouper, groupSorter, groupNamer, taskSorter);
const {isEditingTitle, stopEditingTitle, startEditingTitle, toggleEditingTitle, editTitle, setEditTitle} = useBoardTitleEditor(title, send);
return {
@ -63,5 +77,6 @@ export function useBoard(name: string): UseBoardReturns {
previousSorter,
editTitle,
setEditTitle,
send,
}
}

View file

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

View file

@ -2,6 +2,6 @@ import {useMemo} from "react"
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}
}

View file

@ -29,7 +29,8 @@ export default function RootLayout({children}: { children: ReactNode }) {
<p>
© <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://github.com/Steffo99/todocolors">GitHub</a>
<a href="https://github.com/Steffo99/todocolors">GitHub</a> -
Using {process.env.NEXT_PUBLIC_API_BASE_URL}
</p>
</footer>
</body>