Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement gallery drafts hook #315

Merged
merged 28 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7f59dc7
wip
shahin-hq Oct 27, 2023
beba28b
wip
shahin-hq Oct 30, 2023
2b2b96a
wip
shahin-hq Oct 30, 2023
694e033
wip
shahin-hq Oct 30, 2023
1f92817
wip
shahin-hq Oct 30, 2023
8cc7418
wip
shahin-hq Oct 30, 2023
b03b234
wip
shahin-hq Oct 30, 2023
2d110aa
Merge branch 'develop' into feat/persist-gallery-drafts
shahin-hq Oct 30, 2023
3bcd7fc
wip
shahin-hq Oct 31, 2023
bdc3a87
Merge remote-tracking branch 'origin/feat/persist-gallery-drafts' int…
shahin-hq Oct 31, 2023
4a49115
wip
shahin-hq Oct 31, 2023
ea516a6
Merge branch 'develop' into feat/persist-gallery-drafts
shahin-hq Oct 31, 2023
b0aef6f
wip
shahin-hq Oct 31, 2023
07cd85c
Merge remote-tracking branch 'origin/feat/persist-gallery-drafts' int…
shahin-hq Oct 31, 2023
6a0d962
Merge branch 'develop' into feat/persist-gallery-drafts
alfonsobries Oct 31, 2023
48f6b47
Merge branch 'develop' into feat/persist-gallery-drafts
goga-m Nov 1, 2023
69e3f55
wip
shahin-hq Nov 1, 2023
08f07f4
Merge branch 'develop' into feat/persist-gallery-drafts
shahin-hq Nov 1, 2023
26d5e80
Merge remote-tracking branch 'origin/feat/persist-gallery-drafts' int…
shahin-hq Nov 1, 2023
228ea59
wip
shahin-hq Nov 1, 2023
79f5d9b
wip
shahin-hq Nov 1, 2023
19b5d54
wip
shahin-hq Nov 1, 2023
0c373ce
wip
shahin-hq Nov 1, 2023
e7e0778
Merge branch 'feat/gallery-drafts' into feat/persist-gallery-drafts
shahin-hq Nov 2, 2023
00561a1
Merge branch 'feat/gallery-drafts' into feat/persist-gallery-drafts
shahin-hq Nov 2, 2023
d013354
fix tests
shahin-hq Nov 2, 2023
cc12a80
fix tests
shahin-hq Nov 2, 2023
39491b1
fix tests
shahin-hq Nov 2, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
"react-chartjs-2": "^5.2.0",
"react-i18next": "^12.3.1",
"react-in-viewport": "1.0.0-alpha.30",
"react-indexed-db-hook": "^1.0.14",
"react-loading-skeleton": "^3.3.1",
"react-popper": "^2.3.0",
"react-resize-detector": "^8.1.0",
Expand Down
21 changes: 17 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

142 changes: 142 additions & 0 deletions resources/js/Pages/Galleries/hooks/useGalleryDrafts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { useEffect, useState } from "react";
import { useIndexedDB } from "react-indexed-db-hook";
import { useAuth } from "@/Contexts/AuthContext";
import { useDebounce } from "@/Hooks/useDebounce";
import { useIsFirstRender } from "@/Hooks/useIsFirstRender";

interface DraftNft {
nftId: number;
image: string;
collectionSlug: string;
}

interface GalleryDraft {
title: string;
cover: ArrayBuffer | null;
coverType: string | null;
nfts: DraftNft[];
walletAddress?: string;
id: number | null;
}

const initialGalleryDraft: GalleryDraft = {
title: "",
cover: null,
coverType: null,
nfts: [],
id: null,
};

const MAX_DRAFT_LIMIT_PER_WALLET = 6;
shahin-hq marked this conversation as resolved.
Show resolved Hide resolved

interface GalleryDraftsState {
reachedLimit: boolean;
draft: GalleryDraft;
setDraftCover: (image: ArrayBuffer | null, type: string | null) => void;
setDraftNfts: (nfts: App.Data.Gallery.GalleryNftData[]) => void;
setDraftTitle: (title: string) => void;
}

export const useGalleryDrafts = (givenDraftId?: number): GalleryDraftsState => {
const { wallet } = useAuth();

const database = useIndexedDB("gallery-drafts");

const [draft, setDraft] = useState<GalleryDraft>({
...initialGalleryDraft,
walletAddress: wallet?.address,
});

const [save, setSave] = useState(false);
const [isSaving, setIsSaving] = useState(false);

const [title, setTitle] = useState<string>("");
const [debouncedValue] = useDebounce(title, 400);

const isFirstRender = useIsFirstRender();

const [reachedLimit, setReachedLimit] = useState(false);

// populate `draft` state if `givenDraftId` is present
useEffect(() => {
if (givenDraftId === undefined) return;
const getDraft = async (): Promise<void> => {
const draft: GalleryDraft = await database.getByID(givenDraftId);

if (draft.walletAddress === wallet?.address) {
setDraft(draft);
}
};

void getDraft();
}, []);
shahin-hq marked this conversation as resolved.
Show resolved Hide resolved

// handle debounced title
useEffect(() => {
if (isFirstRender) return;

setDraft({ ...draft, title: debouncedValue });
setSave(true);
}, [debouncedValue]);

useEffect(() => {
if (!save || isSaving || reachedLimit) return;

void saveDraft();
}, [save]);

const saveDraft = async (): Promise<void> => {
setIsSaving(true);

if (draft.id === null) {
const walletDrafts = await getWalletDrafts();

if (walletDrafts.length >= MAX_DRAFT_LIMIT_PER_WALLET) {
setReachedLimit(true);
return;
}

const draftToCreate: Partial<GalleryDraft> = { ...draft };
delete draftToCreate.id;

const id = await database.add(draftToCreate);
setDraft({ ...draft, id });
} else {
await database.update(draft);
}

setSave(false);
setIsSaving(false);
};

const getWalletDrafts = async (): Promise<GalleryDraft[]> => {
const allDrafts: GalleryDraft[] = await database.getAll();

return allDrafts.filter((draft) => draft.walletAddress === wallet?.address);
};

const setDraftCover = (image: ArrayBuffer | null, type: string | null): void => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to use ArrayBuffer to store images as Safari cannot store Blobs

If you are storing large, user-generated files such as images or videos, then you may try to store them as File or Blob objects. This will work on some platforms but fail on others. Safari on iOS, in particular, cannot store Blobs in IndexedDB.

Luckily it is not too difficult to convert a Blob into an ArrayBuffer, and vice versa. Storing ArrayBuffers in IndexedDB is very well supported.

Remember, however, that a Blob has a MIME type while an ArrayBuffer does not. You will need to store the type alongside the buffer in order to do the conversion correctly.

https://web.dev/articles/indexeddb-best-practices

setDraft({ ...draft, cover: image, coverType: type });
setSave(true);
};

const setDraftNfts = (nfts: App.Data.Gallery.GalleryNftData[]): void => {
setDraft({
...draft,
nfts: nfts.map((nft) => ({
nftId: nft.id,
image: nft.images.large ?? "",
collectionSlug: nft.collectionSlug,
})),
});
setSave(true);
};

return {
reachedLimit,
draft,
setDraftCover,
setDraftNfts,
setDraftTitle: setTitle,
};
};
4 changes: 4 additions & 0 deletions resources/js/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ import {
import { resolvePageComponent } from "laravel-vite-plugin/inertia-helpers";
import { createRoot } from "react-dom/client";
import { I18nextProvider } from "react-i18next";
import { initDB } from "react-indexed-db-hook";
import { AuthContextProvider } from "./Contexts/AuthContext";
import DarkModeContextProvider from "./Contexts/DarkModeContex";
import EnvironmentContextProvider from "./Contexts/EnvironmentContext";
import { CookieConsent } from "./cookieConsent";
import MetaMaskContextProvider from "@/Contexts/MetaMaskContext";
import { TransactionSliderProvider } from "@/Contexts/TransactionSliderContext";
import { databaseConfig } from "@/databaseConfig";
import { i18n } from "@/I18n";

// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
Expand Down Expand Up @@ -55,6 +57,8 @@ const appName = window.document.querySelector("title")?.innerText ?? "Dashbrd";

const queryClient = new QueryClient();

initDB(databaseConfig);

void createInertiaApp({
title: (title) => (title !== "" ? title : appName),

Expand Down
17 changes: 17 additions & 0 deletions resources/js/databaseConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const databaseConfig = {
name: "dashbrd",
version: 1,
objectStoresMeta: [
{
store: "gallery-drafts",
storeConfig: { keyPath: "id", autoIncrement: true },
storeSchema: [
{ name: "walletAddress", keypath: "walletAddress", options: { unique: false } },
{ name: "title", keypath: "title", options: { unique: false } },
{ name: "cover", keypath: "cover", options: { unique: false } },
{ name: "coverType", keypath: "coverType", options: { unique: false } },
{ name: "nfts", keypath: "nfts", options: { unique: false } },
],
},
],
};
Loading