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:
parent
63efa5b896
commit
dafd64b4c9
11 changed files with 164 additions and 164 deletions
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -11,5 +11,6 @@
|
|||
"node_modules": true,
|
||||
".next": true,
|
||||
"yarn.lock": true,
|
||||
}
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
}
|
15
components/editable/Editable.tsx
Normal file
15
components/editable/Editable.tsx
Normal 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
|
||||
}
|
|
@ -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}/>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
16
components/editable/EditableDateTimeLocal.tsx
Normal file
16
components/editable/EditableDateTimeLocal.tsx
Normal 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} />
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
19
components/editable/EditableFile.tsx
Normal file
19
components/editable/EditableFile.tsx
Normal 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={<></>}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
import { HTMLProps } from "react";
|
||||
import { EditingContext } from "./EditingContext";
|
||||
import { useDefinedContext } from "../../utils/definedContext";
|
||||
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.
|
||||
*/
|
||||
export function EditableMarkdown({value, ...props}: HTMLProps<HTMLTextAreaElement>) {
|
||||
const [editing,] = useDefinedContext(EditingContext)
|
||||
|
||||
return editing ? (
|
||||
<textarea value={value} {...props}/>
|
||||
) : (
|
||||
<FestaMarkdown markdown={value as string}/>
|
||||
export function EditableMarkdown(props: HTMLProps<HTMLTextAreaElement> & { value: string }) {
|
||||
return (
|
||||
<Editable
|
||||
editing={
|
||||
<textarea {...props} />
|
||||
}
|
||||
preview={
|
||||
<FestaMarkdown markdown={props.value} />
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -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}/>
|
||||
</>
|
||||
}
|
|
@ -1,17 +1,19 @@
|
|||
import { HTMLProps } from "react";
|
||||
import { EditingContext } from "./EditingContext";
|
||||
import { useDefinedContext } from "../../utils/definedContext";
|
||||
import { Editable } from "./Editable";
|
||||
|
||||
|
||||
/**
|
||||
* 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}) {
|
||||
const [editing,] = useDefinedContext(EditingContext)
|
||||
|
||||
return editing ? (
|
||||
<input type="text" {...props}/>
|
||||
) : (
|
||||
<span>{props.value}</span>
|
||||
export function EditableText(props: HTMLProps<HTMLInputElement> & { value: string }) {
|
||||
return (
|
||||
<Editable
|
||||
editing={
|
||||
<input type="text" {...props} />
|
||||
}
|
||||
preview={
|
||||
<span>{props.value}</span>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -7,13 +7,16 @@ type PostcardProps = {
|
|||
src?: string | StaticImageData
|
||||
}
|
||||
|
||||
export function Postcard({src}: PostcardProps) {
|
||||
const {setPostcard} = useDefinedContext(PostcardContext)
|
||||
export function Postcard({ src }: PostcardProps) {
|
||||
const { setPostcard } = useDefinedContext(PostcardContext)
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
if(src) {
|
||||
if(typeof src === "object") {
|
||||
if (src) {
|
||||
if (src === undefined) {
|
||||
return
|
||||
}
|
||||
if (typeof src === "object") {
|
||||
setPostcard(src.src)
|
||||
}
|
||||
else {
|
||||
|
|
58
hooks/useFileState.ts
Normal file
58
hooks/useFileState.ts
Normal 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,
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -10,25 +10,28 @@ import { EditableText } from "../../components/editable/EditableText";
|
|||
import { ToolToggleEditing } from "../../components/tools/ToolToggleEditing";
|
||||
import { EditingContext } from "../../components/editable/EditingContext";
|
||||
import { database } from "../../utils/prismaClient";
|
||||
import { EditablePostcard } from "../../components/editable/EditablePostcard";
|
||||
import { EditableFile } from "../../components/editable/EditableFile";
|
||||
import { ViewEvent } from "../../components/view/ViewEvent";
|
||||
import { ToolToggleVisible } from "../../components/tools/ToolToggleVisible";
|
||||
import { EditableDateRange } from "../../components/editable/EditableDateRange";
|
||||
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) {
|
||||
const slug = context.query.slug as string
|
||||
if(typeof slug === "object") {
|
||||
return {notFound: true}
|
||||
if (typeof slug === "object") {
|
||||
return { notFound: true }
|
||||
}
|
||||
|
||||
const event = await database.event.findUnique({
|
||||
where: {slug},
|
||||
include: {creator: true}
|
||||
where: { slug },
|
||||
include: { creator: true }
|
||||
})
|
||||
if(!event) {
|
||||
return {notFound: true}
|
||||
if (!event) {
|
||||
return { notFound: true }
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -41,43 +44,29 @@ export async function getServerSideProps(context: NextPageContext) {
|
|||
|
||||
|
||||
type PageEventDetailProps = {
|
||||
event: Event & {creator: User}
|
||||
event: Event & { creator: User }
|
||||
}
|
||||
|
||||
|
||||
export default function PageEventDetail({event}: PageEventDetailProps) {
|
||||
const {t} = useTranslation()
|
||||
export default function PageEventDetail({ event }: PageEventDetailProps) {
|
||||
const { t } = useTranslation()
|
||||
const editState = useState<boolean>(false)
|
||||
const [title, setTitle] = useState<string>(event.name)
|
||||
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 [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 <>
|
||||
<Head>
|
||||
<title key="title">{event.name} - {t("siteTitle")}</title>
|
||||
</Head>
|
||||
<WorkInProgress/>
|
||||
<WorkInProgress />
|
||||
<Postcard src={postcard.file ? URL.createObjectURL(postcard.file) : event.postcard ?? undefined} />
|
||||
<EditingContext.Provider value={editState}>
|
||||
<ToolBar vertical="vadapt" horizontal="right">
|
||||
<ToolToggleEditing/>
|
||||
<ToolToggleVisible/>
|
||||
<ToolToggleEditing />
|
||||
<ToolToggleVisible />
|
||||
</ToolBar>
|
||||
<ViewEvent
|
||||
title={
|
||||
|
@ -88,9 +77,9 @@ export default function PageEventDetail({event}: PageEventDetailProps) {
|
|||
/>
|
||||
}
|
||||
postcard={
|
||||
<EditablePostcard
|
||||
value={postcard ?? undefined}
|
||||
onChange={setPostcardBlob}
|
||||
<EditableFile
|
||||
value={postcard.value}
|
||||
onChange={postcard.onChange}
|
||||
placeholder={t("eventDetailsPostcardPlaceholder")}
|
||||
/>
|
||||
}
|
||||
|
@ -98,22 +87,10 @@ export default function PageEventDetail({event}: PageEventDetailProps) {
|
|||
<EditableMarkdown
|
||||
value={description}
|
||||
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setDescription(e.target.value)}
|
||||
rows={3}
|
||||
placeholder={t("eventDetailsDescriptionPlaceholder")}
|
||||
/>
|
||||
</>}
|
||||
daterange={
|
||||
<EditableDateRange
|
||||
startProps={{
|
||||
value: startingAt ?? "",
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => setStartingAt(e.target.value)
|
||||
}}
|
||||
endProps={{
|
||||
value: endingAt ?? "",
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => setEndingAt(e.target.value)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
daterange={<></>}
|
||||
/>
|
||||
</EditingContext.Provider>
|
||||
</>
|
||||
|
|
Loading…
Reference in a new issue