Skip to content

Commit 978de14

Browse files
committed
Add initial settings modal
Added a modal for settings. For now just the 18+ rated content can be changed, and it is off by default (previously results were retrieved without this filter). Settings are saved using the web extension storage api which requires new storage permissions.
1 parent 4181710 commit 978de14

File tree

10 files changed

+195
-57
lines changed

10 files changed

+195
-57
lines changed

bun.lockb

2.89 KB
Binary file not shown.

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "Dokodemo AniList (Unofficial)",
44
"description": "Bring AniList's search box anywhere",
55
"version": "0.1.1",
6-
"permissions": ["activeTab", "contextMenus", "scripting"],
6+
"permissions": ["activeTab", "contextMenus", "scripting", "storage"],
77
"background": {
88
"scripts": ["background.js"]
99
},

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,10 @@
3838
"dependencies": {
3939
"@radix-ui/react-dialog": "^1.0.5",
4040
"@radix-ui/react-icons": "^1.3.0",
41+
"@radix-ui/react-switch": "^1.0.3",
4142
"react": "^18.2.0",
4243
"react-dom": "^18.2.0",
43-
"zod": "^3.22.4"
44+
"zod": "^3.22.4",
45+
"zustand": "^4.4.6"
4446
}
4547
}

src/content/components/app.tsx

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import MangaList from "./manga-list";
55
import StaffList from "./staff-list";
66
import StudioList from "./studio-list";
77
import CharacterList from "./character-list";
8+
import SettingsModal from "./settings-modal";
89

910
import { useSearch } from "../hooks/search";
1011
import { useEffect, useState } from "react";
@@ -23,8 +24,9 @@ let runningTabId: number | undefined;
2324
const initialValue = document.getSelection()?.toString() ?? "";
2425

2526
const App = () => {
26-
const [isOpen, setIsOpen] = useState(true);
2727
const [searchBarText, setSearchBarText] = useState(initialValue);
28+
const [dokodemoModalisOpen, setDokodemoModalIsOpen] = useState(true);
29+
const [settingsModalIsOpen, setSettingsModalIsOpen] = useState(false);
2830

2931
const { data, isLoading } = useSearch(searchBarText);
3032

@@ -43,11 +45,11 @@ const App = () => {
4345
}
4446

4547
if (result.data.action === "close") {
46-
setIsOpen(false);
48+
setDokodemoModalIsOpen(false);
4749
return;
4850
}
4951

50-
setIsOpen(true);
52+
setDokodemoModalIsOpen(true);
5153

5254
// set to the selected text if any
5355
setSearchBarText(result.data.text ?? "");
@@ -58,49 +60,57 @@ const App = () => {
5860
}, []);
5961

6062
return (
61-
<Modal
62-
open={isOpen}
63-
onOpenChange={(open) => setIsOpen(open)}
64-
onCloseAnimationEnd={() => setSearchBarText("")}
65-
>
66-
<SearchBar
67-
placeholder="Search AniList"
68-
value={searchBarText}
69-
isLoading={isLoading}
70-
onClose={() => setIsOpen(false)}
71-
onChange={({ target: { value } }) => setSearchBarText(value)}
63+
<>
64+
<Modal
65+
open={dokodemoModalisOpen}
66+
onOpenChange={setDokodemoModalIsOpen}
67+
onCloseAnimationEnd={() => setSearchBarText("")}
68+
>
69+
<SearchBar
70+
placeholder="Search AniList"
71+
value={searchBarText}
72+
isLoading={isLoading}
73+
onClose={() => setDokodemoModalIsOpen(false)}
74+
onOpenSettings={() => setSettingsModalIsOpen(true)}
75+
onChange={({ target: { value } }) => setSearchBarText(value)}
76+
/>
77+
78+
<AnimeList
79+
anime={data.anime.results}
80+
searchText={searchBarText}
81+
thereIsMore={data.anime.pageInfo.hasNextPage}
82+
/>
83+
84+
<MangaList
85+
manga={data.manga.results}
86+
searchText={searchBarText}
87+
thereIsMore={data.manga.pageInfo.hasNextPage}
88+
/>
89+
90+
<CharacterList
91+
characters={data.characters.results}
92+
searchText={searchBarText}
93+
thereIsMore={data.characters.pageInfo.hasNextPage}
94+
/>
95+
96+
<StaffList
97+
staff={data.staff.results}
98+
searchText={searchBarText}
99+
thereIsMore={data.staff.pageInfo.hasNextPage}
100+
/>
101+
102+
<StudioList
103+
studios={data.studios.results}
104+
searchText={searchBarText}
105+
thereIsMore={data.studios.pageInfo.hasNextPage}
106+
/>
107+
</Modal>
108+
109+
<SettingsModal
110+
open={settingsModalIsOpen}
111+
onOpenChange={setSettingsModalIsOpen}
72112
/>
73-
74-
<AnimeList
75-
anime={data.anime.results}
76-
searchText={searchBarText}
77-
thereIsMore={data.anime.pageInfo.hasNextPage}
78-
/>
79-
80-
<MangaList
81-
manga={data.manga.results}
82-
searchText={searchBarText}
83-
thereIsMore={data.manga.pageInfo.hasNextPage}
84-
/>
85-
86-
<CharacterList
87-
characters={data.characters.results}
88-
searchText={searchBarText}
89-
thereIsMore={data.characters.pageInfo.hasNextPage}
90-
/>
91-
92-
<StaffList
93-
staff={data.staff.results}
94-
searchText={searchBarText}
95-
thereIsMore={data.staff.pageInfo.hasNextPage}
96-
/>
97-
98-
<StudioList
99-
studios={data.studios.results}
100-
searchText={searchBarText}
101-
thereIsMore={data.studios.pageInfo.hasNextPage}
102-
/>
103-
</Modal>
113+
</>
104114
);
105115
};
106116

src/content/components/search-bar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
interface Props extends React.ComponentProps<"input"> {
88
isLoading: boolean;
99
onClose: () => void;
10+
onOpenSettings: () => void;
1011
}
1112

1213
const SearchBar = (props: Props) => (
@@ -23,7 +24,7 @@ const SearchBar = (props: Props) => (
2324
/>
2425
<button
2526
aria-label="Settings"
26-
onClick={() => console.log("goooooooooooooooooood morning miyakejimaaaa")}
27+
onClick={props.onOpenSettings}
2728
className="dokodemo-group dokodemo-mx-1 dokodemo-flex dokodemo-cursor-pointer dokodemo-rounded-full dokodemo-border-none dokodemo-bg-transparent"
2829
>
2930
<GearIcon className="dokodemo-h-[20px] dokodemo-w-[20px] dokodemo-text-slate-700 dokodemo-transition-colors dokodemo-duration-300 group-hover:dokodemo-text-sky-600 group-focus-visible:dokodemo-text-sky-600" />
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as Dialog from "@radix-ui/react-dialog";
2+
import * as Switch from "@radix-ui/react-switch";
3+
4+
import { useSettingsStore } from "../store";
5+
6+
const SettingsModal = ({
7+
open,
8+
onOpenChange,
9+
}: {
10+
open: boolean;
11+
onOpenChange: (open: boolean) => void;
12+
}) => {
13+
const { isAdult, toggleIsAdult } = useSettingsStore();
14+
15+
return (
16+
<Dialog.Root open={open} onOpenChange={onOpenChange}>
17+
<Dialog.Portal>
18+
<Dialog.Overlay className="dokodemo-modal dokodemo-fixed dokodemo-inset-0 dokodemo-z-[2147483647] dokodemo-flex dokodemo-overflow-y-auto dokodemo-bg-black/20 dokodemo-font-sans">
19+
<Dialog.Content className="settings-content dokodemo-absolute dokodemo-left-1/2 dokodemo-top-1/2 dokodemo-mx-[24px] dokodemo-flex dokodemo-h-full dokodemo-max-h-[300px] dokodemo-w-full dokodemo-max-w-[555px] -dokodemo-translate-x-1/2 -dokodemo-translate-y-1/2 dokodemo-flex-col dokodemo-rounded-[6px] dokodemo-bg-white dokodemo-p-[20px] dokodemo-leading-[20px]">
20+
<h1 className="dokodemo-mb-[20px] dokodemo-text-[24px] dokodemo-font-bold">
21+
Settings
22+
</h1>
23+
24+
<div className="dokodemo-flex dokodemo-justify-between">
25+
<label
26+
htmlFor="18-content"
27+
className="dokodemo-text-[15px] dokodemo-font-normal"
28+
>
29+
Allow 18+ content in search results
30+
</label>
31+
<Switch.Root
32+
id="18-content"
33+
checked={isAdult}
34+
onCheckedChange={toggleIsAdult}
35+
className="dokodemo-flex dokodemo-h-[25px] dokodemo-w-[42px] dokodemo-cursor-default dokodemo-items-center dokodemo-rounded-full dokodemo-outline-none focus-visible:dokodemo-outline focus-visible:dokodemo-outline-sky-400 data-[state=checked]:dokodemo-bg-sky-500"
36+
>
37+
<Switch.Thumb className="dokodemo-block dokodemo-h-[21px] dokodemo-w-[21px] dokodemo-translate-x-0.5 dokodemo-rounded-full dokodemo-bg-white dokodemo-shadow-[0_2px_2px] dokodemo-shadow-black dokodemo-transition-transform dokodemo-duration-100 dokodemo-will-change-transform data-[state=checked]:dokodemo-translate-x-[19px]" />
38+
</Switch.Root>
39+
</div>
40+
41+
<button
42+
onClick={() => onOpenChange(false)}
43+
className="dokodemo-mb-0 dokodemo-mt-auto dokodemo-cursor-pointer dokodemo-self-end dokodemo-rounded-[4px] dokodemo-bg-sky-500 dokodemo-px-[15px] dokodemo-py-[10px] dokodemo-text-[15px] dokodemo-font-medium dokodemo-text-white dokodemo-transition-shadow dokodemo-duration-200 hover:dokodemo-shadow-[0_1px_10px_0_rgba(49,54,68,.15)]"
44+
>
45+
Close
46+
</button>
47+
</Dialog.Content>
48+
</Dialog.Overlay>
49+
</Dialog.Portal>
50+
</Dialog.Root>
51+
);
52+
};
53+
54+
export default SettingsModal;

src/content/dokodemo-anilist.css

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
line-height: inherit !important;
66
box-shadow: none !important;
77
border-radius: unset !important;
8+
border: none !important;
89
}
910

1011
.dokodemo-modal input:focus,
@@ -14,11 +15,12 @@
1415
}
1516

1617
.dokodemo-modal a {
17-
color: unset !important;
18-
text-decoration: unset !important;
18+
color: unset;
19+
text-decoration: unset;
1920
}
2021

21-
.dokodemo-content {
22+
.dokodemo-content,
23+
.settings-content {
2224
--testing: 1;
2325
}
2426

@@ -30,8 +32,7 @@
3032
animation: dokodemo-fade-in 300ms ease-out;
3133
}
3234

33-
.dokodemo-modal[data-state="closed"],
34-
.dokodemo-content[data-state="closed"] {
35+
.dokodemo-modal[data-state="closed"] {
3536
animation: dokodemo-fade-out 200ms ease-in;
3637
}
3738

@@ -43,6 +44,14 @@
4344
animation: dokodemo-slide-out 150ms ease-in-out;
4445
}
4546

47+
.dokodemo-content[data-state="open"] {
48+
animation: dokodemo-slide-in 150ms ease-in-out;
49+
}
50+
51+
.dokodemo-content[data-state="closed"] {
52+
animation: dokodemo-slide-out 150ms ease-in-out;
53+
}
54+
4655
@keyframes dokodemo-fade-in {
4756
from {
4857
opacity: 0;

src/content/hooks/search.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useSettingsStore } from "../store";
12
import { useEffect, useState } from "react";
23

34
import { FetchMessageSchema } from "../schemas/message";
@@ -18,6 +19,8 @@ export const useSearch = (text: string) => {
1819
const [isLoading, setIsLoading] = useState(false);
1920
const [textToSearch, setTextToSearch] = useState("");
2021

22+
const { isAdult } = useSettingsStore();
23+
2124
useEffect(() => {
2225
if (!text) {
2326
setTextToSearch("");
@@ -49,7 +52,7 @@ export const useSearch = (text: string) => {
4952
pageInfo {
5053
hasNextPage
5154
}
52-
results: media(type: ANIME, search: "${textToSearch}") {
55+
results: media(type: ANIME, search: "${textToSearch}", isAdult: ${isAdult}) {
5356
id
5457
title {
5558
romaji
@@ -67,7 +70,7 @@ export const useSearch = (text: string) => {
6770
pageInfo {
6871
hasNextPage
6972
}
70-
results: media(type: MANGA, search: "${textToSearch}") {
73+
results: media(type: MANGA, search: "${textToSearch}", isAdult: ${isAdult}) {
7174
id
7275
title {
7376
romaji
@@ -120,7 +123,6 @@ export const useSearch = (text: string) => {
120123
}
121124
}
122125
}
123-
124126
`;
125127

126128
const cheerioooooooooo = async () => {
@@ -152,7 +154,7 @@ export const useSearch = (text: string) => {
152154
};
153155

154156
cheerioooooooooo();
155-
}, [textToSearch]);
157+
}, [textToSearch, isAdult]);
156158

157159
return { data, isLoading };
158160
};

src/content/schemas/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,19 @@ export const SearchResponseSchema = z.union([
7474
ErrorQuerySchema,
7575
]);
7676

77+
export const SettingsSchema = z.object({
78+
isAdult: z.boolean(),
79+
});
80+
81+
export const LocalStorageSchema = z.object({
82+
settings: SettingsSchema,
83+
});
84+
7785
export type Anime = z.infer<typeof MediaSchema>;
7886
export type Manga = z.infer<typeof MediaSchema>;
7987
export type Staff = z.infer<typeof StaffSchema>;
8088
export type Studio = z.infer<typeof StudioSchema>;
8189
export type Character = z.infer<typeof CharacterSchema>;
90+
export type SettingsSchema = z.infer<typeof SettingsSchema>;
91+
export type LocalStorageSchema = z.infer<typeof LocalStorageSchema>;
8292
export type SearchResponseSchema = z.infer<typeof SearchResponseSchema>;

src/content/store/index.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { create } from "zustand";
2+
import { LocalStorageSchema, type SettingsSchema } from "../schemas";
3+
4+
interface SettingsStore extends SettingsSchema {
5+
toggleIsAdult: () => void;
6+
}
7+
8+
export const useSettingsStore = create<SettingsStore>()((set, get) => ({
9+
isAdult: false,
10+
toggleIsAdult: async () => {
11+
const isAdult = !get().isAdult;
12+
13+
await browser.storage.local.set({
14+
settings: { isAdult },
15+
} satisfies LocalStorageSchema);
16+
17+
set({ isAdult });
18+
},
19+
}));
20+
21+
// set the initial state saved in the store if it exists
22+
(async () => {
23+
const localStorage = await browser.storage.local.get();
24+
25+
const result = LocalStorageSchema.safeParse(localStorage);
26+
27+
if (!result.success) {
28+
console.log(
29+
"no settings found in the storage, assuming first time running",
30+
);
31+
32+
await browser.storage.local.set({
33+
settings: { isAdult: false },
34+
} satisfies LocalStorageSchema);
35+
36+
return;
37+
}
38+
39+
const {
40+
settings: { isAdult },
41+
} = result.data;
42+
43+
useSettingsStore.setState({ isAdult });
44+
})();
45+
46+
// listen for changes made by other tabs
47+
// currently not working, TODO: fix this
48+
browser.storage.local.onChanged.addListener((changes) =>
49+
console.log("changes", changes),
50+
);

0 commit comments

Comments
 (0)