diff --git a/packages/frontend/src/layouts/MainLayout.tsx b/packages/frontend/src/layouts/MainLayout.tsx
new file mode 100644
index 0000000..b683e89
--- /dev/null
+++ b/packages/frontend/src/layouts/MainLayout.tsx
@@ -0,0 +1,36 @@
+import { View } from "@adobe/react-spectrum";
+import { PropsWithChildren } from "react";
+
+export default function MainLayout({ children }: PropsWithChildren) {
+ return (
+ <>
+ {children}
+ {/* footer */}
+
+
+ sauce
+ {" "}
+ •{" "}
+
+ code license
+ {" "}
+ •{" "}
+
+ template license
+
+
+ >
+ );
+}
diff --git a/packages/frontend/src/layouts/StickerEditLayout.tsx b/packages/frontend/src/layouts/StickerEditLayout.tsx
new file mode 100644
index 0000000..ef24dea
--- /dev/null
+++ b/packages/frontend/src/layouts/StickerEditLayout.tsx
@@ -0,0 +1,72 @@
+import SpectrumLink from "@/modules/editor/SpectrumLink";
+import { useApiSettings } from "@/modules/stickers/useApiSettings";
+import { useSticker } from "@/modules/stickers/useSticker";
+import { Button, Meter, Text, View } from "@adobe/react-spectrum";
+import ArrowLeft from "@spectrum-icons/workflow/ArrowLeft";
+import ArrowRight from "@spectrum-icons/workflow/ArrowRight";
+import Head from "next/head";
+import Link from "next/link";
+import { useRouter } from "next/router";
+import { PropsWithChildren } from "react";
+
+export default function StickerEditLayout({ children }: PropsWithChildren) {
+ const router = useRouter();
+ const step = Number(router.asPath.match(/step\/(\d)/)?.[1]);
+ const prev = step == 1 ? null : step - 1;
+ const next = step == 4 ? null : step + 1;
+ const stickerId = String(router.query.stickerId);
+ const { sticker } = useSticker(stickerId);
+ const configString = router.query.config
+ ? `?config=${router.query.config}`
+ : "";
+
+ useApiSettings();
+
+ return (
+ <>
+
+
+ {sticker.displayName} Telegram Sticker Template - Avoo's Sticker Stash
+
+
+
+
+
+ {prev && (
+
+ )}
+
+ {children}
+
+
+ {next ? (
+
+ ) : (
+
+ )}
+ >
+ );
+}
diff --git a/packages/frontend/src/modules/editor/SpectrumLink.tsx b/packages/frontend/src/modules/editor/SpectrumLink.tsx
new file mode 100644
index 0000000..300dc13
--- /dev/null
+++ b/packages/frontend/src/modules/editor/SpectrumLink.tsx
@@ -0,0 +1,23 @@
+import Link from "next/link";
+import { AnchorHTMLAttributes, FC, PropsWithChildren } from "react";
+
+interface Props
+ extends PropsWithChildren,
+ AnchorHTMLAttributes {
+ href: string;
+}
+
+/**
+ * Link that is compatible with spectrum buttons
+ */
+const SpectrumLink: FC = ({ href, children, ...rest }) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default SpectrumLink;
diff --git a/packages/frontend/src/modules/editor/StepDescription.tsx b/packages/frontend/src/modules/editor/StepDescription.tsx
new file mode 100644
index 0000000..2537a6b
--- /dev/null
+++ b/packages/frontend/src/modules/editor/StepDescription.tsx
@@ -0,0 +1,10 @@
+import { Well } from "@react-spectrum/well";
+import { FC, PropsWithChildren } from "react";
+
+interface Props extends PropsWithChildren {}
+
+const StepDescription: FC = (props) => {
+ return {props.children};
+};
+
+export default StepDescription;
diff --git a/packages/frontend/src/modules/editor/StepStickerView.tsx b/packages/frontend/src/modules/editor/StepStickerView.tsx
new file mode 100644
index 0000000..d34d83c
--- /dev/null
+++ b/packages/frontend/src/modules/editor/StepStickerView.tsx
@@ -0,0 +1,48 @@
+import StickerRenderer from "@/modules/editor/StickerRenderer";
+import { View } from "@adobe/react-spectrum";
+import { FC } from "react";
+import { InView } from "react-intersection-observer";
+import { Lottie } from "tg-sticker-creator";
+
+interface Props {
+ lottie: Lottie | null;
+ sticky: boolean;
+}
+
+const StepStickerView: FC = (props) => {
+ return (
+
+
+
+ {({ inView, ref, entry }) => (
+
+ {inView ? (
+
+ ) : (
+
+ )}
+
+ )}
+
+
+
+ );
+};
+
+export default StepStickerView;
diff --git a/packages/frontend/src/modules/editor/StepTemplateChanger.tsx b/packages/frontend/src/modules/editor/StepTemplateChanger.tsx
new file mode 100644
index 0000000..6b1f01c
--- /dev/null
+++ b/packages/frontend/src/modules/editor/StepTemplateChanger.tsx
@@ -0,0 +1,37 @@
+import { Item, Menu, MenuTrigger, Text } from "@adobe/react-spectrum";
+import { ActionButton } from "@react-spectrum/button";
+import SwitchIcon from "@spectrum-icons/workflow/Switch";
+import { useRouter } from "next/router";
+import { FC, PropsWithChildren } from "react";
+
+interface Props {
+ step: number;
+ stickers: { id: string; name: string }[];
+}
+
+const StepTemplateChanger: FC = (props) => {
+ const router = useRouter();
+ const configString = router.query.config
+ ? `?config=${router.query.config}`
+ : "";
+
+ return (
+
+
+
+ Change Template
+
+
+
+ );
+};
+
+export default StepTemplateChanger;
diff --git a/packages/frontend/src/modules/editor/StepToolbarContainer.tsx b/packages/frontend/src/modules/editor/StepToolbarContainer.tsx
new file mode 100644
index 0000000..ad21012
--- /dev/null
+++ b/packages/frontend/src/modules/editor/StepToolbarContainer.tsx
@@ -0,0 +1,25 @@
+import { View } from "@adobe/react-spectrum";
+import { FC, PropsWithChildren } from "react";
+
+interface Props extends PropsWithChildren {}
+
+const StepToolbarContainer: FC = (props) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+export default StepToolbarContainer;
diff --git a/packages/frontend/src/modules/export/AddToSetButton.tsx b/packages/frontend/src/modules/export/AddToSetButton.tsx
index 9e9047f..27ac646 100644
--- a/packages/frontend/src/modules/export/AddToSetButton.tsx
+++ b/packages/frontend/src/modules/export/AddToSetButton.tsx
@@ -9,7 +9,7 @@ import {
import Export from "@spectrum-icons/workflow/Export";
import { useAtom } from "jotai";
import { useRouter } from "next/router";
-import { FC, useCallback, useState } from "react";
+import { FC, ReactNode, useCallback, useState } from "react";
import { Lottie, optimizeFilesize } from "tg-sticker-creator";
import { paletteAtom } from "../palette/ColorList";
import { configAtom } from "../stickers/configAtom";
@@ -18,8 +18,12 @@ import { authAtom } from "./auth";
import { gzip } from "./gzip";
import { saveSticker } from "./requests";
+type ActionType = "save" | "add" | "saveAndAdd";
+
interface Props {
lottie: Lottie;
+ action: ActionType;
+ children: ReactNode;
}
const AddToSetButton: FC = (props) => {
@@ -30,55 +34,43 @@ const AddToSetButton: FC = (props) => {
const router = useRouter();
const [loading, setLoading] = useState(false);
- const save = useCallback(
- async (key: string | number) => {
- if (!auth.data || !auth.type) throw new Error("missing auth");
- setLoading(true);
- try {
- const file = await gzip(optimizeFilesize(props.lottie.clone()));
- // TODO: refactor
- const response = await saveSticker({
- settings: JSON.stringify(config),
- palette: JSON.stringify(colors),
- file,
- emojis: sticker.emojis.map((e) => e.emoji).join(""),
- authData: auth.data,
- authType: auth.type,
- sticker: sticker.id,
- action: String(key) as "save" | "add" | "saveAndAdd",
- });
- if (key === "save" || key === "saveAndAdd") {
- router.push("/"); // TODO: close gui if used from within bot?
- }
- console.log("response", response);
- } catch (error: any) {
- alert(error.message || error); // TODO: proper error handling
- console.log(error);
- } finally {
- setLoading(false);
+ const save = useCallback(async () => {
+ if (!auth.data || !auth.type) throw new Error("missing auth");
+ setLoading(true);
+ try {
+ const file = await gzip(optimizeFilesize(props.lottie.clone()));
+ // TODO: refactor
+ const response = await saveSticker({
+ settings: JSON.stringify(config),
+ palette: JSON.stringify(colors),
+ file,
+ emojis: sticker.emojis.map((e) => e.emoji).join(""),
+ authData: auth.data,
+ authType: auth.type,
+ sticker: sticker.id,
+ action: props.action,
+ });
+ if (props.action === "save" || props.action === "saveAndAdd") {
+ router.push(router.asPath.replace(/step\/\d/, "step/4"));
}
- },
- [config, colors, sticker, auth, router, setLoading],
- );
+ console.log("response", response);
+ } catch (error: any) {
+ alert(error.message || error); // TODO: proper error handling
+ console.log(error);
+ } finally {
+ setLoading(false);
+ }
+ }, [config, colors, sticker, auth, router, setLoading, props]);
return (
-
-
-
-
-
-
+
);
};
diff --git a/packages/frontend/src/modules/export/ExportButton.tsx b/packages/frontend/src/modules/export/ExportButton.tsx
new file mode 100644
index 0000000..fb80cf7
--- /dev/null
+++ b/packages/frontend/src/modules/export/ExportButton.tsx
@@ -0,0 +1,19 @@
+import { Button, Text } from "@adobe/react-spectrum";
+import Gears from "@spectrum-icons/workflow/Gears";
+import { FC } from "react";
+import { useExport } from "./useExport";
+
+const ExportButton: FC = () => {
+ const { downloadJson } = useExport();
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default ExportButton;
diff --git a/packages/frontend/src/modules/export/ImportButton.tsx b/packages/frontend/src/modules/export/ImportButton.tsx
new file mode 100644
index 0000000..696bf43
--- /dev/null
+++ b/packages/frontend/src/modules/export/ImportButton.tsx
@@ -0,0 +1,27 @@
+import { Button, DialogContainer, Text } from "@adobe/react-spectrum";
+import Gears from "@spectrum-icons/workflow/Gears";
+import { FC, useCallback, useState } from "react";
+import JsonImportDialog from "./JsonImportDialog";
+
+const ImportButton: FC = () => {
+ const [isOpen, setOpen] = useState(false);
+
+ const handleImport = useCallback(() => {
+ setOpen(true);
+ }, [setOpen]);
+
+ return (
+ <>
+
+
+ setOpen(false)}>
+ {isOpen && }
+
+ >
+ );
+};
+
+export default ImportButton;
diff --git a/packages/frontend/src/modules/export/SettingsMenu.tsx b/packages/frontend/src/modules/export/SettingsMenu.tsx
deleted file mode 100644
index 6078dfc..0000000
--- a/packages/frontend/src/modules/export/SettingsMenu.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import {
- ActionButton,
- DialogContainer,
- Item,
- Menu,
- MenuTrigger,
- Text,
-} from "@adobe/react-spectrum";
-import Gears from "@spectrum-icons/workflow/Gears";
-import { FC, useCallback, useState } from "react";
-import JsonImportDialog from "./JsonImportDialog";
-import { useExport } from "./useExport";
-
-const SettingsMenu: FC = () => {
- const [isOpen, setOpen] = useState(false);
-
- const { downloadJson } = useExport();
-
- const handleExport = useCallback(
- (key: string | number) => {
- switch (key) {
- case "import":
- setOpen(true);
- break;
- case "export":
- downloadJson();
- break;
- }
- },
- [setOpen],
- );
-
- return (
- <>
-
-
-
- Character Export
-
-
-
-
- setOpen(false)}>
- {isOpen && }
-
- >
- );
-};
-
-export default SettingsMenu;
diff --git a/packages/frontend/src/modules/export/generateTgs.ts b/packages/frontend/src/modules/export/generateTgs.ts
index a752704..ec796ce 100644
--- a/packages/frontend/src/modules/export/generateTgs.ts
+++ b/packages/frontend/src/modules/export/generateTgs.ts
@@ -11,5 +11,5 @@ export const generateTgs = async (props: Props) => {
console.log("size in KiB", blob.size / (1 << 10));
- download(blob, `${props.lottie.name || "sticker"}.tgs`, "application/gzip");
+ download(blob, `${props.lottie.name || "sticker"}.tgs`, "application/octet-stream");
};
diff --git a/packages/frontend/src/modules/export/gzip.ts b/packages/frontend/src/modules/export/gzip.ts
index 8dce941..c75716f 100644
--- a/packages/frontend/src/modules/export/gzip.ts
+++ b/packages/frontend/src/modules/export/gzip.ts
@@ -12,7 +12,7 @@ export const gzip = async (obj: Lottie) => {
const zipped = pako.gzip(tgsString, { level: 9 });
const blob = new Blob([zipped], {
- type: "application/gzip",
+ type: "application/octet-stream",
});
// TODO: warning if blob.size > 64 << 10
diff --git a/packages/frontend/src/modules/export/useExport.tsx b/packages/frontend/src/modules/export/useExport.tsx
index 340268d..2c8a6ed 100644
--- a/packages/frontend/src/modules/export/useExport.tsx
+++ b/packages/frontend/src/modules/export/useExport.tsx
@@ -14,7 +14,7 @@ export const useExport = () => {
"sticker-settings.json",
"application/json",
);
- }, [config]);
+ }, [config, palette]);
return {
downloadJson,
diff --git a/packages/frontend/src/modules/overview/VideoCard.tsx b/packages/frontend/src/modules/overview/VideoCard.tsx
index 1dab445..afa5021 100644
--- a/packages/frontend/src/modules/overview/VideoCard.tsx
+++ b/packages/frontend/src/modules/overview/VideoCard.tsx
@@ -8,11 +8,14 @@ import {
} from "@adobe/react-spectrum";
import { Card } from "@react-spectrum/card";
import Edit from "@spectrum-icons/workflow/Edit";
+import Export from "@spectrum-icons/workflow/Export";
import { useRouter } from "next/router";
-import { FC, useCallback } from "react";
+import { FC, PropsWithChildren, useCallback } from "react";
import EmojiList from "../emojis/EmojiList";
import style from "./index.module.css";
import { VideoEntry } from "./VideoEntry";
+import Link from "next/link";
+import SpectrumLink from "../editor/SpectrumLink";
interface Props {
entry: VideoEntry;
@@ -21,13 +24,10 @@ interface Props {
const VideoCard: FC = ({ entry }) => {
const router = useRouter();
- const handleNavigation = useCallback(() => {
- router.push(
- `/edit/${entry.stickerId}?config=${encodeURIComponent(
- entry.settingId || "",
- )}`,
- );
- }, [entry, router]);
+ const url = (step: number) =>
+ `/edit/${entry.stickerId}/step/${step}?config=${encodeURIComponent(
+ entry.settingId || "",
+ )}`;
return (
@@ -46,15 +46,19 @@ const VideoCard: FC = ({ entry }) => {
-
diff --git a/packages/frontend/src/modules/stickers/effects.ts b/packages/frontend/src/modules/stickers/effects.ts
index 541b91d..01e2441 100644
--- a/packages/frontend/src/modules/stickers/effects.ts
+++ b/packages/frontend/src/modules/stickers/effects.ts
@@ -2,6 +2,7 @@ import {
brightnessFilter,
colorMixFilter,
contrastFilter,
+ gay,
greyscaleFilter,
hearts,
hypnoEffect2,
@@ -34,6 +35,8 @@ export const getCommonEffects = () => [
hypnoEffect2("effect.hypno"),
hypnoEffect3("effect.hypno3"),
+ gay("effect.gay"),
+
sepiaFilter("effect.color.sepia"),
greyscaleFilter("effect.color.greyscale"),
saturationFilter("effect.color.saturation"),
diff --git a/packages/frontend/src/modules/stickers/useApiSettings.ts b/packages/frontend/src/modules/stickers/useApiSettings.ts
index 129dc2f..8d39a09 100644
--- a/packages/frontend/src/modules/stickers/useApiSettings.ts
+++ b/packages/frontend/src/modules/stickers/useApiSettings.ts
@@ -33,7 +33,7 @@ export const useApiSettings = () => {
console.log(e);
setUserSettingsLoaded(true);
});
- }, [router.query]);
+ }, [router.query.config]);
return {
userSettings,
diff --git a/packages/frontend/src/modules/stickers/useSticker.ts b/packages/frontend/src/modules/stickers/useSticker.ts
index f048789..4241ef3 100644
--- a/packages/frontend/src/modules/stickers/useSticker.ts
+++ b/packages/frontend/src/modules/stickers/useSticker.ts
@@ -19,12 +19,12 @@ export const stickerAtom = atom({
id: "",
});
-export const useSticker = (id: string) => {
+export const useSticker = (id?: string) => {
const [sticker, setSticker] = useAtom(stickerAtom);
const [config, setConfig] = useAtom(configAtom);
useEffect(() => {
- if (sticker?.id === id) return;
+ if (sticker?.id === id || !id) return;
(async () => {
const sticker = await getSticker(id);
if (!sticker) {
diff --git a/packages/frontend/src/modules/stickers/utilities/hypno.ts b/packages/frontend/src/modules/stickers/utilities/hypno.ts
index 02e1c26..9502427 100644
--- a/packages/frontend/src/modules/stickers/utilities/hypno.ts
+++ b/packages/frontend/src/modules/stickers/utilities/hypno.ts
@@ -147,3 +147,40 @@ export const hypnoEffect3 = (id: string) =>
return sticker;
},
});
+
+export const gay = (id: string) =>
+ createFilter({
+ mandatory: false,
+ niceness: 6,
+ id,
+ displayName: "Gay",
+ inputs: {
+ ringDuration: {
+ type: "number",
+ default: 20,
+ displayName: "Ring Expansion Rate (Frames)",
+ max: 50,
+ min: 5,
+ },
+ easing: {
+ type: "easing",
+ default: "easeInCubic",
+ displayName: "Easing",
+ },
+ },
+ async apply(sticker, inputs) {
+ sticker.addLayerBack(
+ create.shapeLayer().addShapeBack(
+ createHypnoShape({
+ colors: ["red", "orange", "yellow", "green", "blue", "purple"],
+ frames: sticker.finalFrame,
+ center: [256, 256],
+ nrOfFramesARingIsVisibleFor: inputs.ringDuration,
+ easing: inputs.easing,
+ }),
+ ),
+ );
+
+ return sticker;
+ },
+ });
diff --git a/packages/frontend/src/pages/edit/[stickerId]/index.tsx b/packages/frontend/src/pages/edit/[stickerId]/index.tsx
deleted file mode 100644
index 2542a2a..0000000
--- a/packages/frontend/src/pages/edit/[stickerId]/index.tsx
+++ /dev/null
@@ -1,154 +0,0 @@
-import StickerBreadcrumb from "@/modules/breadcrumb/StickerBreadcrumb";
-import { animateAtom } from "@/modules/editor/animate";
-import StepGroups from "@/modules/editor/StepGroups";
-import StickerRenderer from "@/modules/editor/StickerRenderer";
-import AddToSetButton from "@/modules/export/AddToSetButton";
-import { authAtom } from "@/modules/export/auth";
-import ExportMenu from "@/modules/export/ExportMenu";
-import SettingsMenu from "@/modules/export/SettingsMenu";
-import ColorList from "@/modules/palette/ColorList";
-import { getSummary } from "@/modules/stickers";
-import { useApiSettings } from "@/modules/stickers/useApiSettings";
-import { useGeneratedSticker, useSticker } from "@/modules/stickers/useSticker";
-import {
- Switch,
- ActionButton,
- Heading,
- Item,
- Menu,
- MenuTrigger,
- Text,
- View,
-} from "@adobe/react-spectrum";
-import SwitchIcon from "@spectrum-icons/workflow/Switch";
-import { useAtom } from "jotai";
-import { GetStaticPaths, GetStaticProps } from "next";
-import Head from "next/head";
-import { useState } from "react";
-import { InView } from "react-intersection-observer";
-
-interface Props {
- stickerId: string;
- stickers: {
- id: string;
- name: string;
- }[];
-}
-
-export default function Home(props: Props) {
- const [stickerId, setStickerId] = useState(props.stickerId);
- // const { sticker } = useSticker(props.stickerId);
- const { sticker } = useSticker(stickerId);
- const { lottie } = useGeneratedSticker();
- const [auth] = useAtom(authAtom);
-
- const { userSettings } = useApiSettings();
-
- const [animatePreviews, setAnimatePreviews] = useAtom(animateAtom);
-
- return (
-
-
-
- {sticker.displayName} Telegram Sticker Template - Avoo's Sticker Stash
-
-
-
-
- Edit
-
-
- Animate Previews
-
- {auth.type !== "web-app" && }
- {auth.type !== "web-app" && lottie && }
-
-
-
- Change Template
-
-
-
- {lottie && <>{auth.data && }>}
-
-
-
-
-
-
- {({ inView, ref, entry }) => (
-
- {inView ? (
-
- ) : (
-
- )}
-
- )}
-
-
-
-
-
-
- );
-}
-
-export const getStaticProps: GetStaticProps = async (context) => {
- const stickerId = context.params?.stickerId;
- const stickers = await getSummary();
- if (typeof stickerId !== "string") throw new Error("invalid id");
- return {
- props: {
- stickerId,
- stickers: Object.entries(stickers).map(
- ([id, { displayName, emojis }]) => ({
- id,
- name: `${displayName} - ${emojis.map((e) => e.emoji).join("")}`,
- }),
- ),
- },
- };
-};
-
-export const getStaticPaths: GetStaticPaths = async () => {
- const stickers = await getSummary();
- return {
- paths: Object.keys(stickers).map((stickerId) => ({
- params: { stickerId },
- })),
- fallback: false,
- };
-};
diff --git a/packages/frontend/src/pages/edit/[stickerId]/step/1.tsx b/packages/frontend/src/pages/edit/[stickerId]/step/1.tsx
new file mode 100644
index 0000000..d8e882e
--- /dev/null
+++ b/packages/frontend/src/pages/edit/[stickerId]/step/1.tsx
@@ -0,0 +1,90 @@
+import MainLayout from "@/layouts/MainLayout";
+import StickerEditLayout from "@/layouts/StickerEditLayout";
+import StickerBreadcrumb from "@/modules/breadcrumb/StickerBreadcrumb";
+import StepDescription from "@/modules/editor/StepDescription";
+import StepStickerView from "@/modules/editor/StepStickerView";
+import StepTemplateChanger from "@/modules/editor/StepTemplateChanger";
+import StepToolbarContainer from "@/modules/editor/StepToolbarContainer";
+import { authAtom } from "@/modules/export/auth";
+import ImportButton from "@/modules/export/ImportButton";
+import ColorList from "@/modules/palette/ColorList";
+import { getSummary } from "@/modules/stickers";
+import { useGeneratedSticker, useSticker } from "@/modules/stickers/useSticker";
+import { Heading } from "@adobe/react-spectrum";
+import { useAtom } from "jotai";
+import { GetStaticPaths, GetStaticProps } from "next";
+import { useRouter } from "next/router";
+
+interface Props {
+ stickerId: string;
+ stickers: {
+ id: string;
+ name: string;
+ }[];
+}
+
+export default function Home(props: Props) {
+ const router = useRouter();
+ const stickerId = String(router.query.stickerId);
+ const step = Number(router.query.step || 1);
+ const { sticker } = useSticker();
+ const { lottie } = useGeneratedSticker();
+ const [auth] = useAtom(authAtom);
+
+ return (
+
+ Create Palette
+
+ Step 1: Choose the main colors. These are usually all your fur colors
+ and other colors that you want to use more than once.
+
+ When you're done, continue to the next step with the button at the
+ bottom. You can go back to this step later if you need to.
+
+
+ {auth.type !== "web-app" && }
+
+
+
+
+
+
+ );
+}
+
+Home.getLayout = function getLayout(page: any) {
+ return (
+
+ {page}
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const stickerId = context.params?.stickerId;
+ const stickers = await getSummary();
+ if (typeof stickerId !== "string") throw new Error("invalid id");
+ return {
+ props: {
+ stickerId,
+ stickers: Object.entries(stickers).map(
+ ([id, { displayName, emojis }]) => ({
+ id,
+ name: `${displayName} - ${emojis.map((e) => e.emoji).join("")}`,
+ }),
+ ),
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const stickers = await getSummary();
+ return {
+ paths: Object.keys(stickers).flatMap((stickerId) => [
+ {
+ params: { stickerId },
+ },
+ ]),
+ fallback: false,
+ };
+};
diff --git a/packages/frontend/src/pages/edit/[stickerId]/step/2.tsx b/packages/frontend/src/pages/edit/[stickerId]/step/2.tsx
new file mode 100644
index 0000000..7f52e4c
--- /dev/null
+++ b/packages/frontend/src/pages/edit/[stickerId]/step/2.tsx
@@ -0,0 +1,94 @@
+import MainLayout from "@/layouts/MainLayout";
+import StickerEditLayout from "@/layouts/StickerEditLayout";
+import StickerBreadcrumb from "@/modules/breadcrumb/StickerBreadcrumb";
+import { animateAtom } from "@/modules/editor/animate";
+import StepDescription from "@/modules/editor/StepDescription";
+import StepGroups from "@/modules/editor/StepGroups";
+import StepStickerView from "@/modules/editor/StepStickerView";
+import StepTemplateChanger from "@/modules/editor/StepTemplateChanger";
+import StepToolbarContainer from "@/modules/editor/StepToolbarContainer";
+import { authAtom } from "@/modules/export/auth";
+import { getSummary } from "@/modules/stickers";
+import { useGeneratedSticker, useSticker } from "@/modules/stickers/useSticker";
+import { Heading, Switch } from "@adobe/react-spectrum";
+import { useAtom } from "jotai";
+import { GetStaticPaths, GetStaticProps } from "next";
+import { useRouter } from "next/router";
+
+interface Props {
+ stickerId: string;
+ stickers: {
+ id: string;
+ name: string;
+ }[];
+}
+
+export default function Home(props: Props) {
+ const router = useRouter();
+ const step = Number(router.query.step || 2);
+ const { sticker } = useSticker();
+ const { lottie } = useGeneratedSticker();
+ const [auth] = useAtom(authAtom);
+
+ const [animatePreviews, setAnimatePreviews] = useAtom(animateAtom);
+
+ return (
+
+ Edit Sticker
+
+ Step 2: Edit the patterns and colors to match your character. Make sure
+ that you switch through all templates via "Change Template" to ensure
+ everything is colored correctly - different templates reveal different
+ body parts.
+
+ Click the button at the bottom to continue.
+
+
+
+ Animate Previews
+
+
+
+
+
+
+
+ );
+}
+
+Home.getLayout = function getLayout(page: any) {
+ return (
+
+ {page}
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const stickerId = context.params?.stickerId;
+ const stickers = await getSummary();
+ if (typeof stickerId !== "string") throw new Error("invalid id");
+ return {
+ props: {
+ stickerId,
+ stickers: Object.entries(stickers).map(
+ ([id, { displayName, emojis }]) => ({
+ id,
+ name: `${displayName} - ${emojis.map((e) => e.emoji).join("")}`,
+ }),
+ ),
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const stickers = await getSummary();
+ return {
+ paths: Object.keys(stickers).flatMap((stickerId) => [
+ {
+ params: { stickerId },
+ },
+ ]),
+ fallback: false,
+ };
+};
diff --git a/packages/frontend/src/pages/edit/[stickerId]/step/3.tsx b/packages/frontend/src/pages/edit/[stickerId]/step/3.tsx
new file mode 100644
index 0000000..ee5dbbe
--- /dev/null
+++ b/packages/frontend/src/pages/edit/[stickerId]/step/3.tsx
@@ -0,0 +1,94 @@
+import MainLayout from "@/layouts/MainLayout";
+import StickerEditLayout from "@/layouts/StickerEditLayout";
+import StickerBreadcrumb from "@/modules/breadcrumb/StickerBreadcrumb";
+import { animateAtom } from "@/modules/editor/animate";
+import StepDescription from "@/modules/editor/StepDescription";
+import StepStickerView from "@/modules/editor/StepStickerView";
+import StepToolbarContainer from "@/modules/editor/StepToolbarContainer";
+import AddToSetButton from "@/modules/export/AddToSetButton";
+import { authAtom } from "@/modules/export/auth";
+import ExportButton from "@/modules/export/ExportButton";
+import { getSummary } from "@/modules/stickers";
+import { useGeneratedSticker, useSticker } from "@/modules/stickers/useSticker";
+import { Heading } from "@adobe/react-spectrum";
+import { useAtom } from "jotai";
+import { GetStaticPaths, GetStaticProps } from "next";
+import { useRouter } from "next/router";
+
+interface Props {
+ stickerId: string;
+ stickers: {
+ id: string;
+ name: string;
+ }[];
+}
+
+export default function Home(props: Props) {
+ const router = useRouter();
+ const step = Number(router.query.step || 3);
+ const { sticker } = useSticker();
+ const { lottie } = useGeneratedSticker();
+ const [auth] = useAtom(authAtom);
+
+ return (
+
+ Save
+
+ Step 3: Export your character to a file that you can use in step 1 to
+ restore your character, or save your character to your account if you're
+ signed in via the bot.
+
+
+ {auth.type !== "web-app" && }
+ {lottie && (
+ <>
+ {auth.data && (
+
+ Save Configuration to Your Account
+
+ )}
+ >
+ )}
+
+
+
+
+ );
+}
+
+Home.getLayout = function getLayout(page: any) {
+ return (
+
+ {page}
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const stickerId = context.params?.stickerId;
+ const stickers = await getSummary();
+ if (typeof stickerId !== "string") throw new Error("invalid id");
+ return {
+ props: {
+ stickerId,
+ stickers: Object.entries(stickers).map(
+ ([id, { displayName, emojis }]) => ({
+ id,
+ name: `${displayName} - ${emojis.map((e) => e.emoji).join("")}`,
+ }),
+ ),
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const stickers = await getSummary();
+ return {
+ paths: Object.keys(stickers).flatMap((stickerId) => [
+ {
+ params: { stickerId },
+ },
+ ]),
+ fallback: false,
+ };
+};
diff --git a/packages/frontend/src/pages/edit/[stickerId]/step/4.tsx b/packages/frontend/src/pages/edit/[stickerId]/step/4.tsx
new file mode 100644
index 0000000..e9d10e5
--- /dev/null
+++ b/packages/frontend/src/pages/edit/[stickerId]/step/4.tsx
@@ -0,0 +1,107 @@
+import MainLayout from "@/layouts/MainLayout";
+import StickerEditLayout from "@/layouts/StickerEditLayout";
+import StickerBreadcrumb from "@/modules/breadcrumb/StickerBreadcrumb";
+import { animateAtom } from "@/modules/editor/animate";
+import StepDescription from "@/modules/editor/StepDescription";
+import StepGroups from "@/modules/editor/StepGroups";
+import StepStickerView from "@/modules/editor/StepStickerView";
+import StepTemplateChanger from "@/modules/editor/StepTemplateChanger";
+import StepToolbarContainer from "@/modules/editor/StepToolbarContainer";
+import AddToSetButton from "@/modules/export/AddToSetButton";
+import { authAtom } from "@/modules/export/auth";
+import ExportMenu from "@/modules/export/ExportMenu";
+import ColorList from "@/modules/palette/ColorList";
+import { getSummary } from "@/modules/stickers";
+import { useGeneratedSticker, useSticker } from "@/modules/stickers/useSticker";
+import { Heading, Switch } from "@adobe/react-spectrum";
+import { useAtom } from "jotai";
+import { GetStaticPaths, GetStaticProps } from "next";
+import { useRouter } from "next/router";
+
+interface Props {
+ stickerId: string;
+ stickers: {
+ id: string;
+ name: string;
+ }[];
+}
+
+export default function Home(props: Props) {
+ const router = useRouter();
+ const step = Number(router.query.step || 4);
+ const { sticker } = useSticker();
+ const { lottie } = useGeneratedSticker();
+ const [auth] = useAtom(authAtom);
+
+ const [animatePreviews, setAnimatePreviews] = useAtom(animateAtom);
+
+ return (
+
+ Export
+
+ Step 4: Export your stickers! If you're logged in, you can add them to
+ your set directly; otherwise export them to .tgs files that you can send
+ to the @Stickers Telegram bot to create the set by yourself.
+
+ You can also export multiple versions of a sticker, like skirt and
+ non-skirt variants by toggling the relevant options.
+
+
+
+ Animate Previews
+
+ {auth.type !== "web-app" && lottie && }
+
+ {lottie && (
+ <>
+ {auth.data && (
+
+ Add to Your Set
+
+ )}
+ >
+ )}
+
+
+
+
+
+ );
+}
+
+Home.getLayout = function getLayout(page: any) {
+ return (
+
+ {page}
+
+ );
+};
+
+export const getStaticProps: GetStaticProps = async (context) => {
+ const stickerId = context.params?.stickerId;
+ const stickers = await getSummary();
+ if (typeof stickerId !== "string") throw new Error("invalid id");
+ return {
+ props: {
+ stickerId,
+ stickers: Object.entries(stickers).map(
+ ([id, { displayName, emojis }]) => ({
+ id,
+ name: `${displayName} - ${emojis.map((e) => e.emoji).join("")}`,
+ }),
+ ),
+ },
+ };
+};
+
+export const getStaticPaths: GetStaticPaths = async () => {
+ const stickers = await getSummary();
+ return {
+ paths: Object.keys(stickers).flatMap((stickerId) => [
+ {
+ params: { stickerId },
+ },
+ ]),
+ fallback: false,
+ };
+};