Implement publishing.

pull/218/head
Atul Varma 2021-08-22 16:43:33 -04:00
rodzic c26d9ef818
commit 5bee62881b
5 zmienionych plików z 136 dodań i 29 usunięć

Wyświetl plik

@ -17,10 +17,15 @@ import {
orderBy,
CollectionReference,
Timestamp,
addDoc,
} from "firebase/firestore";
import React, { useCallback, useContext, useEffect, useState } from "react";
import { AuthContext } from "./auth-context";
import { GalleryComposition, GalleryContext } from "./gallery-context";
import {
GalleryComposition,
GalleryContext,
GallerySubmitStatus,
} from "./gallery-context";
const GALLERY_COLLECTION = "compositions";
@ -123,23 +128,59 @@ function getGalleryCollection(appCtx: FirebaseAppContext) {
) as CollectionReference<FirebaseCompositionDocument>;
}
function docToComp(
doc: FirebaseCompositionDocument,
id: string
): GalleryComposition {
const { createdAt, ...data } = doc;
return {
...data,
id,
createdAt: createdAt.toDate(),
};
}
export const FirebaseGalleryProvider: React.FC<{}> = ({ children }) => {
const appCtx = useContext(FirebaseAppContext);
const [compositions, setCompositions] = useState<GalleryComposition[]>([]);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | undefined>(undefined);
const [lastRefresh, setLastRefresh] = useState(0);
const handleError = (e: Error) => {
setIsLoading(false);
setError(e.message);
};
const [submitStatus, setSubmitStatus] = useState<GallerySubmitStatus>("idle");
const [lastSubmission, setLastSubmission] = useState<
GalleryComposition | undefined
>(undefined);
const context: GalleryContext = {
compositions,
isLoading,
error,
lastRefresh,
lastSubmission,
submitStatus,
submit(props, onSuccess) {
if (!(appCtx && submitStatus === "idle")) return;
const doc: FirebaseCompositionDocument = {
...props,
createdAt: Timestamp.now(),
};
setSubmitStatus("submitting");
setLastSubmission(undefined);
addDoc(getGalleryCollection(appCtx), doc)
.then((docRef) => {
const comp = docToComp(doc, docRef.id);
setSubmitStatus("idle");
setCompositions([comp, ...compositions]);
setLastSubmission(comp);
onSuccess(docRef.id);
})
.catch((e) => {
setSubmitStatus("error");
console.log(e);
});
},
refresh: useCallback(() => {
if (!(appCtx && !isLoading)) return false;
@ -150,17 +191,13 @@ export const FirebaseGalleryProvider: React.FC<{}> = ({ children }) => {
setLastRefresh(Date.now());
setIsLoading(false);
setCompositions(
snapshot.docs.map((doc) => {
const { createdAt, ...data } = doc.data();
return {
...data,
id: doc.id,
createdAt: createdAt.toDate(),
};
})
snapshot.docs.map((doc) => docToComp(doc.data(), doc.id))
);
})
.catch(handleError);
.catch((e) => {
setIsLoading(false);
setError(e.message);
});
return true;
}, [appCtx, isLoading]),
};

Wyświetl plik

@ -2,6 +2,8 @@ import React from "react";
export type GalleryCompositionKind = "creature" | "mandala";
export type GallerySubmitStatus = "idle" | "submitting" | "error";
export type GalleryComposition = {
/** A unique identifier/primary key for the composition. */
id: string;
@ -41,6 +43,15 @@ export interface GalleryContext {
*/
compositions: GalleryComposition[];
submitStatus: GallerySubmitStatus;
submit(
composition: Omit<GalleryComposition, "id" | "createdAt">,
onSuccess: (id: string) => void
): void;
lastSubmission?: GalleryComposition;
/** Whether we're currently loading the gallery from the network. */
isLoading: boolean;
@ -67,5 +78,7 @@ export const GalleryContext = React.createContext<GalleryContext>({
compositions: [],
isLoading: false,
refresh: () => true,
submitStatus: "idle",
submit: () => {},
lastRefresh: 0,
});

Wyświetl plik

@ -1,6 +1,7 @@
import { assertNotNull } from "@justfixnyc/util";
import React, { useContext, useState } from "react";
import { AuthContext } from "./auth-context";
import { GalleryCompositionKind } from "./gallery-context";
import { GalleryCompositionKind, GalleryContext } from "./gallery-context";
export type GalleryWidgetProps = {
kind: GalleryCompositionKind;
@ -44,29 +45,73 @@ const LoginWidget: React.FC<{}> = () => {
const PublishWidget: React.FC<GalleryWidgetProps> = (props) => {
const authCtx = useContext(AuthContext);
const user = assertNotNull(authCtx.loggedInUser, "User must be logged in");
const galleryCtx = useContext(GalleryContext);
const [title, setTitle] = useState("");
const [publishedId, setPublishedId] = useState("");
const handlePublish = () => {
const serializedValue = props.serializeValue();
console.log("TODO: Publish", props.kind, serializedValue);
galleryCtx.submit(
{
title,
kind: props.kind,
serializedValue: props.serializeValue(),
owner: user.id,
ownerName: user.name,
},
setPublishedId
);
};
const isSubmitting = galleryCtx.submitStatus === "submitting";
if (!authCtx.providerName) return null;
if (galleryCtx.lastSubmission?.id === publishedId) {
return (
<>
<p>Your composition "{title}" has been published!</p>
<button
onClick={() => {
setPublishedId("");
setTitle("");
}}
>
I want to publish more!
</button>
</>
);
}
return (
<>
<p>
Here you can publish your composition to our publicly-viewable gallery.
</p>
<div className="flex-widget thingy">
<label htmlFor="gallery-title">Composition title:</label>
<input
id="gallery-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
</div>
<button onClick={handlePublish}>Publish to gallery</button> <AuthWidget />
<form
onSubmit={(e) => {
e.preventDefault();
handlePublish();
}}
>
<div className="flex-widget thingy">
<label htmlFor="gallery-title">Composition title:</label>
<input
id="gallery-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
disabled={isSubmitting}
required
/>
</div>
<button type="submit" disabled={isSubmitting}>
Publish to gallery
</button>{" "}
{!isSubmitting && <AuthWidget />}
{galleryCtx.submitStatus === "error" && (
<p className="error">
Sorry, an error occurred while submitting your composition. Please
try again later.
</p>
)}
</form>
</>
);
};

11
package-lock.json wygenerowano
Wyświetl plik

@ -14,6 +14,7 @@
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@justfixnyc/util": "^0.3.0",
"@types/cheerio": "^0.22.23",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.22",
@ -2904,6 +2905,11 @@
"node": ">=8"
}
},
"node_modules/@justfixnyc/util": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@justfixnyc/util/-/util-0.3.0.tgz",
"integrity": "sha512-Iuj6x5PPhtaHFNLYfZoIOFIwV4ysPGt7EapgfO8hYlQprHRcZfxBXb51ZHmWx3I7Ak5VZLgOLc8/HLFkZjWKlw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -13468,6 +13474,11 @@
}
}
},
"@justfixnyc/util": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@justfixnyc/util/-/util-0.3.0.tgz",
"integrity": "sha512-Iuj6x5PPhtaHFNLYfZoIOFIwV4ysPGt7EapgfO8hYlQprHRcZfxBXb51ZHmWx3I7Ak5VZLgOLc8/HLFkZjWKlw=="
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",

Wyświetl plik

@ -35,6 +35,7 @@
"@babel/preset-react": "^7.13.13",
"@babel/preset-typescript": "^7.12.7",
"@babel/register": "^7.12.10",
"@justfixnyc/util": "^0.3.0",
"@types/cheerio": "^0.22.23",
"@types/jest": "^26.0.20",
"@types/node": "^14.14.22",