1
Fork 0
mirror of https://github.com/Steffo99/festa.git synced 2024-12-22 14:44:21 +00:00

WIP: file state things

This commit is contained in:
Steffo 2022-06-08 19:14:00 +02:00
parent 63efa5b896
commit dafd64b4c9
Signed by: steffo
GPG key ID: 6965406171929D01
11 changed files with 164 additions and 164 deletions

View file

@ -11,5 +11,6 @@
"node_modules": true, "node_modules": true,
".next": true, ".next": true,
"yarn.lock": true, "yarn.lock": true,
} },
"editor.formatOnSave": true
} }

View file

@ -0,0 +1,15 @@
import { ReactNode } from "react";
import { useDefinedContext } from "../../utils/definedContext";
import { EditingContext } from "./EditingContext";
type EditableProps = {
editing: JSX.Element,
preview: JSX.Element,
}
export function Editable({editing, preview}: EditableProps) {
const [isEditing,] = useDefinedContext(EditingContext)
return isEditing ? editing : preview
}

View file

@ -1,72 +0,0 @@
import { faChevronRight, faClock } from "@fortawesome/free-solid-svg-icons"
import { HTMLProps } from "react"
import { EditingContext } from "./EditingContext"
import { useDefinedContext } from "../../utils/definedContext"
import { FestaIcon } from "../extensions/FestaIcon"
import { FormDateRange } from "../form/FormDateRange"
import { HumanDate } from "../HumanDate"
type EditableDateRangeProps = {
startProps: HTMLProps<HTMLInputElement> & {value?: string},
endProps: HTMLProps<HTMLInputElement> & {value?: string},
}
export function EditableDateRange(props: EditableDateRangeProps) {
const [editing,] = useDefinedContext(EditingContext)
if(editing) {
return (
<FormDateRange
preview={false}
icon={
<FestaIcon icon={faClock}/>
}
start={
<input
type="datetime-local"
{...props.startProps}
/>
}
connector={
<FestaIcon icon={faChevronRight}/>
}
end={
<input
type="datetime-local"
{...props.endProps}
/>
}
/>
)
}
const startTime = Date.parse(props.startProps.value!)
const endTime = Date.parse(props.endProps.value!)
if(Number.isNaN(startTime) && Number.isNaN(endTime)) {
return null
}
const startDate = new Date(startTime)
const endDate = new Date(endTime)
return (
<FormDateRange
preview={true}
icon={
<FestaIcon icon={faClock}/>
}
start={
<HumanDate date={startDate}/>
}
connector={
<FestaIcon icon={faChevronRight}/>
}
end={
<HumanDate date={endDate}/>
}
/>
)
}

View file

@ -0,0 +1,16 @@
import { HTMLProps } from "react";
import { HumanDate } from "../HumanDate";
import { Editable } from "./Editable";
export function EditableDateTimeLocal(props: HTMLProps<HTMLInputElement> & { value: Date }) {
return (
<Editable
editing={
<input type="datetime-local" {...props} value={props.value.toISOString()} />
}
preview={
<HumanDate date={props.value} />
}
/>
)
}

View file

@ -0,0 +1,19 @@
import { HTMLProps } from "react";
import { Editable } from "./Editable";
/**
* Controlled input component which displays an `input[type="file"]` in editing mode, and is invisible in preview mode.
*
* Value is the file's [fakepath](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file#value), as a string.
*/
export function EditableFile(props: HTMLProps<HTMLInputElement> & { value: string }) {
return (
<Editable
editing={
<input type="file" {...props} />
}
preview={<></>}
/>
)
}

View file

@ -1,17 +1,19 @@
import { HTMLProps } from "react"; import { HTMLProps } from "react";
import { EditingContext } from "./EditingContext";
import { useDefinedContext } from "../../utils/definedContext";
import { FestaMarkdown } from "../extensions/FestaMarkdown"; import { FestaMarkdown } from "../extensions/FestaMarkdown";
import { Editable } from "./Editable";
/** /**
* Controlled input component which displays a `textarea` in editing mode, and renders the input in Markdown using {@link FestaMarkdown} in preview mode. * Controlled input component which displays a `textarea` in editing mode, and renders the input in Markdown using {@link FestaMarkdown} in preview mode.
*/ */
export function EditableMarkdown({value, ...props}: HTMLProps<HTMLTextAreaElement>) { export function EditableMarkdown(props: HTMLProps<HTMLTextAreaElement> & { value: string }) {
const [editing,] = useDefinedContext(EditingContext) return (
<Editable
return editing ? ( editing={
<textarea value={value} {...props}/> <textarea {...props} />
) : ( }
<FestaMarkdown markdown={value as string}/> preview={
<FestaMarkdown markdown={props.value} />
}
/>
) )
} }

View file

@ -1,21 +0,0 @@
import { HTMLProps } from "react";
import { EditingContext } from "./EditingContext";
import { useDefinedContext } from "../../utils/definedContext";
import { Postcard } from "../postcard/Postcard";
/**
* Controlled input component which displays an `input[type="file"]` in editing mode, and is invisible in preview mode.
*
* As a side effect, it changes the {@link Postcard} to the input file when it's rendered.
*/
export function EditablePostcard({value, ...props}: HTMLProps<HTMLInputElement> & {value: string | undefined}) {
const [editing,] = useDefinedContext(EditingContext)
return <>
{editing ?
<input type="file" value={undefined} {...props}/>
: null}
<Postcard src={value}/>
</>
}

View file

@ -1,17 +1,19 @@
import { HTMLProps } from "react"; import { HTMLProps } from "react";
import { EditingContext } from "./EditingContext"; import { Editable } from "./Editable";
import { useDefinedContext } from "../../utils/definedContext";
/** /**
* Controlled input component which displays an `input[type="text"]` in editing mode, and a `span` displaying the input in preview mode. * Controlled input component which displays an `input[type="text"]` in editing mode, and a `span` displaying the input in preview mode.
*/ */
export function EditableText(props: HTMLProps<HTMLInputElement> & {value: string}) { export function EditableText(props: HTMLProps<HTMLInputElement> & { value: string }) {
const [editing,] = useDefinedContext(EditingContext) return (
<Editable
return editing ? ( editing={
<input type="text" {...props}/> <input type="text" {...props} />
) : ( }
<span>{props.value}</span> preview={
<span>{props.value}</span>
}
/>
) )
} }

View file

@ -7,13 +7,16 @@ type PostcardProps = {
src?: string | StaticImageData src?: string | StaticImageData
} }
export function Postcard({src}: PostcardProps) { export function Postcard({ src }: PostcardProps) {
const {setPostcard} = useDefinedContext(PostcardContext) const { setPostcard } = useDefinedContext(PostcardContext)
useEffect( useEffect(
() => { () => {
if(src) { if (src) {
if(typeof src === "object") { if (src === undefined) {
return
}
if (typeof src === "object") {
setPostcard(src.src) setPostcard(src.src)
} }
else { else {

58
hooks/useFileState.ts Normal file
View file

@ -0,0 +1,58 @@
import { useReducer } from "react";
type FileState = {
value: string,
file: File,
url: URL,
}
type FileActionClear = { type: "clear" }
type FileActionSet = { type: "set", value: string, file: File }
type FileAction = FileActionClear | FileActionSet
function fileReducer(state, action) {
switch (action.type) {
case "clear":
case "set":
}
}
export function useFileState() {
const [value, dispatch] = useReducer(
)
/*
const [value, setValue] = useState<string>("")
const [file, setFile] = useState<File | null>(null)
const onChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
setValue(e.target.value)
const file = e.target.files![0]
setFile(file ?? null)
},
[]
)
const doClear = useCallback(
() => {
setValue("")
setFile(null)
},
[]
)
return {
value,
file,
onChange,
doClear,
}
*/
}

View file

@ -10,25 +10,28 @@ import { EditableText } from "../../components/editable/EditableText";
import { ToolToggleEditing } from "../../components/tools/ToolToggleEditing"; import { ToolToggleEditing } from "../../components/tools/ToolToggleEditing";
import { EditingContext } from "../../components/editable/EditingContext"; import { EditingContext } from "../../components/editable/EditingContext";
import { database } from "../../utils/prismaClient"; import { database } from "../../utils/prismaClient";
import { EditablePostcard } from "../../components/editable/EditablePostcard"; import { EditableFile } from "../../components/editable/EditableFile";
import { ViewEvent } from "../../components/view/ViewEvent"; import { ViewEvent } from "../../components/view/ViewEvent";
import { ToolToggleVisible } from "../../components/tools/ToolToggleVisible"; import { ToolToggleVisible } from "../../components/tools/ToolToggleVisible";
import { EditableDateRange } from "../../components/editable/EditableDateRange"; import { EditableDateRange } from "../../components/editable/EditableDateRange";
import { WorkInProgress } from "../../components/WorkInProgress"; import { WorkInProgress } from "../../components/WorkInProgress";
import { FormDateRange } from "../../components/form/FormDateRange";
import { Postcard } from "../../components/postcard/Postcard";
import { useFileState } from "../../hooks/useFileState";
export async function getServerSideProps(context: NextPageContext) { export async function getServerSideProps(context: NextPageContext) {
const slug = context.query.slug as string const slug = context.query.slug as string
if(typeof slug === "object") { if (typeof slug === "object") {
return {notFound: true} return { notFound: true }
} }
const event = await database.event.findUnique({ const event = await database.event.findUnique({
where: {slug}, where: { slug },
include: {creator: true} include: { creator: true }
}) })
if(!event) { if (!event) {
return {notFound: true} return { notFound: true }
} }
return { return {
@ -41,79 +44,53 @@ export async function getServerSideProps(context: NextPageContext) {
type PageEventDetailProps = { type PageEventDetailProps = {
event: Event & {creator: User} event: Event & { creator: User }
} }
export default function PageEventDetail({event}: PageEventDetailProps) { export default function PageEventDetail({ event }: PageEventDetailProps) {
const {t} = useTranslation() const { t } = useTranslation()
const editState = useState<boolean>(false) const editState = useState<boolean>(false)
const [title, setTitle] = useState<string>(event.name) const [title, setTitle] = useState<string>(event.name)
const [description, setDescription] = useState<string>(event.description) const [description, setDescription] = useState<string>(event.description)
const [postcard, setPostcard] = useState<string | null>(event.postcard) const postcard = useFileState()
const [startingAt, setStartingAt] = useState<string>(event.startingAt?.toISOString() ?? "") const [startingAt, setStartingAt] = useState<string>(event.startingAt?.toISOString() ?? "")
const [endingAt, setEndingAt] = useState<string>(event.endingAt?.toISOString() ?? "") const [endingAt, setEndingAt] = useState<string>(event.endingAt?.toISOString() ?? "")
const setPostcardBlob = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files![0]
if(!file) {
setPostcard(null)
return
}
const blobUrl = URL.createObjectURL(file)
setPostcard(blobUrl)
},
[]
)
return <> return <>
<Head> <Head>
<title key="title">{event.name} - {t("siteTitle")}</title> <title key="title">{event.name} - {t("siteTitle")}</title>
</Head> </Head>
<WorkInProgress/> <WorkInProgress />
<Postcard src={postcard.file ? URL.createObjectURL(postcard.file) : event.postcard ?? undefined} />
<EditingContext.Provider value={editState}> <EditingContext.Provider value={editState}>
<ToolBar vertical="vadapt" horizontal="right"> <ToolBar vertical="vadapt" horizontal="right">
<ToolToggleEditing/> <ToolToggleEditing />
<ToolToggleVisible/> <ToolToggleVisible />
</ToolBar> </ToolBar>
<ViewEvent <ViewEvent
title={ title={
<EditableText <EditableText
value={title} value={title}
onChange={(e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)} onChange={(e: ChangeEvent<HTMLInputElement>) => setTitle(e.target.value)}
placeholder={t("eventDetailsTitlePlaceholder")} placeholder={t("eventDetailsTitlePlaceholder")}
/> />
} }
postcard={ postcard={
<EditablePostcard <EditableFile
value={postcard ?? undefined} value={postcard.value}
onChange={setPostcardBlob} onChange={postcard.onChange}
placeholder={t("eventDetailsPostcardPlaceholder")} placeholder={t("eventDetailsPostcardPlaceholder")}
/> />
} }
description={<> description={<>
<EditableMarkdown <EditableMarkdown
value={description} value={description}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)} onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
rows={3}
placeholder={t("eventDetailsDescriptionPlaceholder")} placeholder={t("eventDetailsDescriptionPlaceholder")}
/> />
</>} </>}
daterange={ daterange={<></>}
<EditableDateRange
startProps={{
value: startingAt ?? "",
onChange: (e: ChangeEvent<HTMLInputElement>) => setStartingAt(e.target.value)
}}
endProps={{
value: endingAt ?? "",
onChange: (e: ChangeEvent<HTMLInputElement>) => setEndingAt(e.target.value)
}}
/>
}
/> />
</EditingContext.Provider> </EditingContext.Provider>
</> </>