Skip to content
This repository has been archived by the owner on Oct 15, 2023. It is now read-only.

Commit

Permalink
Merge pull request #8 from Dungeon-MASSters/editor
Browse files Browse the repository at this point in the history
Editor
  • Loading branch information
NikonP authored Oct 14, 2023
2 parents 89a79a6 + 1ed1acb commit bf02381
Show file tree
Hide file tree
Showing 7 changed files with 655 additions and 89 deletions.
463 changes: 400 additions & 63 deletions frontend/MASSter-frontend/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions frontend/MASSter-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"pocketbase": "^0.18.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-filerobot-image-editor": "^4.5.2",
"react-hook-form": "^7.47.0",
"react-query": "^3.39.3",
"tailwind-merge": "^1.14.0",
Expand Down
17 changes: 15 additions & 2 deletions frontend/MASSter-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import {
IconBrush,
IconLogout,
IconPhoto,
IconPhotoStar,
IconUser
} from "@tabler/icons-react";
import clsx from "clsx";
import { EditorPage } from "./pages/editor";
import { EditedCoversPage } from "./pages/edited";

function App() {
const {
Expand Down Expand Up @@ -65,6 +67,16 @@ function App() {
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem
className={clsx(navigationMenuTriggerStyle())}
>
<Link href="/gallery">
<NavigationMenuLink className="flex gap-1">
<IconPhotoStar />
<span>галерея</span>
</NavigationMenuLink>
</Link>
</NavigationMenuItem>
<NavigationMenuItem
className={clsx(navigationMenuTriggerStyle())}
>
Expand Down Expand Up @@ -115,12 +127,13 @@ function App() {

<Switch>
<Route path="/grid" component={GridPage} />
<Route path="/gallery" component={EditedCoversPage} />
<Route path="/generate">
<AddPromptPage />
</Route>

<Route path="/editor">
<EditorPage />
<Route path="/editor/:file_url">
{(params) => <EditorPage fileUrlB64={params.file_url} />}
</Route>
<Route>
<Redirect to="/grid" />
Expand Down
14 changes: 14 additions & 0 deletions frontend/MASSter-frontend/src/lib/images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function DataURIToBlob(dataURI: string) {
const splitDataURI = dataURI.split(",");
const byteString =
splitDataURI[0].indexOf("base64") >= 0
? atob(splitDataURI[1])
: decodeURI(splitDataURI[1]);
const mimeString = splitDataURI[0].split(":")[1].split(";")[0];

const ia = new Uint8Array(byteString.length);
for (let i = 0; i < byteString.length; i++)
ia[i] = byteString.charCodeAt(i);

return new Blob([ia], { type: mimeString });
}
52 changes: 52 additions & 0 deletions frontend/MASSter-frontend/src/pages/edited.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { pb } from "@/lib/pb-client";
import { IconDownload, IconLoader3 } from "@tabler/icons-react";
import { useQuery } from "react-query";

export function EditedCoversPage() {
const { data, isLoading } = useQuery(["get-gallery"], () =>
pb.collection("edited_covers").getFullList()
);

return (
<div>
<h1 className="text-xl font-bold">Созданные обложки</h1>
{isLoading ? (
<div className="flex gap-1 text-primary">
<IconLoader3 className="animate-spin" />
<span>загрузка...</span>
</div>
) : data && data.length > 0 ? (
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-2">
{data?.map((cover) => (
<ImageCard
key={cover.id}
fileURL={pb.files.getUrl(cover, cover.result)}
/>
))}
</div>
) : (
<div>Вы ещё не сохранили ни одной обложки :(</div>
)}
</div>
);
}

function ImageCard({ fileURL }: { fileURL: string }) {
return (
<div className="h-52 relative">
<img
className=" w-full h-full object-cover rounded-lg"
src={fileURL}
/>

<a
href={fileURL}
className="absolute bottom-0 flex gap-1 p-2 text-primary-foreground bg-primary rounded-bl-lg rounded-tr-lg hover:text-secondary-foreground hover:bg-secondary"
download
>
<IconDownload />
<span>Скачать</span>
</a>
</div>
);
}
127 changes: 125 additions & 2 deletions frontend/MASSter-frontend/src/pages/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,130 @@
export function EditorPage() {
import { FullscreenLoader } from "@/components/loaders";
import { pb } from "@/lib/pb-client";
import { useUser } from "@/lib/use-user";
import { IconBug } from "@tabler/icons-react";
import { useState } from "react";
import FilerobotImageEditor, {
TABS,
TOOLS
} from "react-filerobot-image-editor";
import { useLocation } from "wouter";

export function EditorPage({ fileUrlB64 }: { fileUrlB64: string }) {
const [_, navigate] = useLocation();
const user = useUser(false);
const [isSaving, setIsSaving] = useState(false);
const [isError, setIsError] = useState(false);

const fileUrl = atob(fileUrlB64);

if (user.isLoading || user.data === undefined) {
return <FullscreenLoader title={"Загрузка..."} />;
}

if (isSaving) {
return <FullscreenLoader title="Сохранение..." />;
}

return (
<div>
<div className="mx-auto w-fit h-[500px]">
<h1 className="font-bold text-xl">Создать обложку</h1>

{isError && (
<div className="flex gap-1 text-red-500">
<IconBug />
<span>Не получилось сохранить изображение</span>
</div>
)}

<FilerobotImageEditor
source={fileUrl}
savingPixelRatio={4}
previewPixelRatio={window.devicePixelRatio}
onBeforeSave={() => {
return false;
}}
onSave={(editedImageObject, _) => {
setIsSaving(true);

editedImageObject.imageCanvas?.toBlob((blob) => {
if (blob === null) {
setIsError(true);
return;
}

const formData = new FormData();
formData.append("created_by", user.data.record.id);
formData.append(
"result",
blob,
editedImageObject.fullName ?? "cover.png"
);

pb.collection("edited_covers")
.create(formData)
.then(() => {
console.log("saved");
navigate("/gallery");
})
.catch(() => setIsError(true));
});
}}
onClose={() => {}}
annotationsCommon={{
fill: "#ffffff"
}}
Text={{
text: "Обложкер",
fontSize: 32,
fontStyle: "bold",
align: "center"
}}
Rotate={{ angle: 90, componentType: "slider" }}
Crop={{
presetsItems: [
{
titleKey: "classicTv",
descriptionKey: "4:3",
ratio: 4 / 3
// icon: CropClassicTv, // optional, CropClassicTv is a React Function component. Possible (React Function component, string or HTML Element)
},
{
titleKey: "cinemascope",
descriptionKey: "21:9",
ratio: 21 / 9
// icon: CropCinemaScope, // optional, CropCinemaScope is a React Function component. Possible (React Function component, string or HTML Element)
}
],
presetsFolders: [
{
titleKey: "socialMedia", // will be translated into Social Media as backend contains this translation key
// icon: Social, // optional, Social is a React Function component. Possible (React Function component, string or HTML Element)
groups: [
{
titleKey: "facebook",
items: [
{
titleKey: "profile",
width: 180,
height: 180,
descriptionKey: "fbProfileSize"
},
{
titleKey: "coverPhoto",
width: 820,
height: 312,
descriptionKey: "fbCoverPhotoSize"
}
]
}
]
}
]
}}
tabsIds={[TABS.ADJUST, TABS.ANNOTATE]} // or {['Adjust', 'Annotate', 'Watermark']}
defaultTabId={TABS.ANNOTATE} // or 'Annotate'
defaultToolId={TOOLS.TEXT} // or 'Text'
/>
</div>
);
}
70 changes: 48 additions & 22 deletions frontend/MASSter-frontend/src/pages/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useLocation } from "wouter";

enum ImgType {
banner = 'banner',
video = 'video',
avatar = 'avatar'
banner = "banner",
video = "video",
avatar = "avatar"
}

export function GridPage() {
Expand All @@ -30,23 +30,31 @@ export function GridPage() {
<div>
<div className="py-4 w-full flex justify-center">
<Tabs
defaultValue={ImgType.video} className="w-1/2"
defaultValue={ImgType.video}
className="w-1/2"
onValueChange={(e) => {
setImgType(ImgType[e as keyof typeof ImgType] ?? ImgType.video)
}}>
setImgType(
ImgType[e as keyof typeof ImgType] ?? ImgType.video
);
}}
>
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value={ImgType.video}>Обложки</TabsTrigger>
<TabsTrigger value={ImgType.avatar}>Аватары</TabsTrigger>
<TabsTrigger value={ImgType.banner}>Баннеры</TabsTrigger>
<TabsTrigger value={ImgType.avatar}>
Аватары
</TabsTrigger>
<TabsTrigger value={ImgType.banner}>
Баннеры
</TabsTrigger>
</TabsList>
</Tabs>
</div>
<ImageGrid imgType={imgType}></ImageGrid>
</div>
)
);
}

function ImageGrid({imgType}: {imgType: ImgType}) {
function ImageGrid({ imgType }: { imgType: ImgType }) {
const listQuery = useQuery(
[`get-gen-list-${imgType}`],
() => {
Expand Down Expand Up @@ -175,9 +183,13 @@ function ModalResultWindow({ item, canEdit, openChange }: ModalResWindowProps) {

let elem = <IconLoader3 className="animate-spin"></IconLoader3>;
if (fileQuery.isSuccess && fileQuery.data.length != 0) {
elem = <img className="flex-grow object-contain h-0 w-full"
src={fileQuery.data}></img>
};
elem = (
<img
className="flex-grow object-contain h-0 w-full"
src={fileQuery.data}
></img>
);
}

return (
<div className="flex h-full w-full">
Expand All @@ -187,12 +199,18 @@ function ModalResultWindow({ item, canEdit, openChange }: ModalResWindowProps) {
<div className="flex justify-between mt-6">
<Button
disabled={currentFileIndex == 0}
onClick={() => { setCurrentFileIndex(currentFileIndex - 1) }}>
onClick={() => {
setCurrentFileIndex(currentFileIndex - 1);
}}
>
Previous
</Button>
<Button
disabled={currentFileIndex == item.num_images - 1 }
onClick={() => { setCurrentFileIndex(currentFileIndex + 1) }}>
disabled={currentFileIndex == item.num_images - 1}
onClick={() => {
setCurrentFileIndex(currentFileIndex + 1);
}}
>
Next
</Button>
</div>
Expand All @@ -205,12 +223,20 @@ function ModalResultWindow({ item, canEdit, openChange }: ModalResWindowProps) {
onClick={() => {
pb.collection("text_generation_mvp")
.update(item.id, { status: "open" })
.then(_ => openChange(false));
}}>Перегенерировать</Button>
{canEdit
? <Button onClick={() => {
navigate('/editor')}}>Изменить картинку</Button>
: undefined}
.then((_) => openChange(false));
}}
>
Перегенерировать
</Button>
{canEdit && fileQuery.data ? (
<Button
onClick={() => {
navigate(`/editor/${btoa(fileQuery.data)}`);
}}
>
Редактировать
</Button>
) : undefined}
</div>
</div>
</div>
Expand Down

0 comments on commit bf02381

Please sign in to comment.