Spaces:
Sleeping
Sleeping
Commit
·
4af0f6c
1
Parent(s):
cffd4ca
NEW update 1
Browse files- backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc +0 -0
- backend/invoke_worker/chapter_queue.py +2 -2
- frontend/app/index.tsx +1 -1
- frontend/app/read/[source]/[comic_id]/index.tsx +46 -119
- frontend/app/read/components/chapter_image.tsx +162 -101
- frontend/app/read/modules/get_chapter.tsx +16 -44
- frontend/app/view/[source]/[comic_id].tsx +3 -3
- frontend/app/view/componenets/chapter.tsx +8 -5
- frontend/app/view/modules/content.tsx +38 -19
- frontend/components/Image.tsx +1 -1
- frontend/constants/module/file_manager.tsx +10 -4
- frontend/constants/module/storages/chapter_data_storage.tsx +250 -0
- frontend/constants/module/storages/chapter_storage.tsx +5 -5
- frontend/constants/module/storages/comic_storage.tsx +1 -1
backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc
CHANGED
Binary files a/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc and b/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc differ
|
|
backend/invoke_worker/chapter_queue.py
CHANGED
@@ -233,10 +233,10 @@ class UpdateSocketQueue(Thread):
|
|
233 |
MAX_QUEUE = SocketRequestChapterQueueCache.objects.count()
|
234 |
|
235 |
if (MAX_QUEUE):
|
236 |
-
query_result = list(set(SocketRequestChapterQueueCache.objects.order_by("
|
237 |
for socket_id in query_result:
|
238 |
object = {}
|
239 |
-
query_result_2 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).order_by("
|
240 |
|
241 |
for item in query_result_2:
|
242 |
source = item.get("source")
|
|
|
233 |
MAX_QUEUE = SocketRequestChapterQueueCache.objects.count()
|
234 |
|
235 |
if (MAX_QUEUE):
|
236 |
+
query_result = list(set(SocketRequestChapterQueueCache.objects.order_by("datetime").values_list('socket_id', flat=True).distinct()))
|
237 |
for socket_id in query_result:
|
238 |
object = {}
|
239 |
+
query_result_2 = SocketRequestChapterQueueCache.objects.filter(socket_id=socket_id).order_by("datetime").values("source","comic_id","chapter_idx")
|
240 |
|
241 |
for item in query_result_2:
|
242 |
source = item.get("source")
|
frontend/app/index.tsx
CHANGED
@@ -9,7 +9,7 @@ const Index = () => {
|
|
9 |
const pathname = usePathname()
|
10 |
|
11 |
if (pathname === "/" || pathname === "") return (
|
12 |
-
<Redirect href="/read/colamanga/manga-
|
13 |
)
|
14 |
|
15 |
}
|
|
|
9 |
const pathname = usePathname()
|
10 |
|
11 |
if (pathname === "/" || pathname === "") return (
|
12 |
+
<Redirect href="/read/colamanga/manga-wp55334?idx=985" />
|
13 |
)
|
14 |
|
15 |
}
|
frontend/app/read/[source]/[comic_id]/index.tsx
CHANGED
@@ -42,18 +42,13 @@ const Index = ({}:any) => {
|
|
42 |
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
43 |
|
44 |
const [isError,setIsError]:any = useState({state:false,text:""})
|
45 |
-
const [
|
46 |
const [chapterInfo, setChapterInfo]:any = useState({})
|
47 |
const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
|
48 |
-
const [
|
49 |
-
const [
|
50 |
-
const [addChapter, setAddChapter]:any = useState(false)
|
51 |
const [zoom, setZoom]:any = useState(0)
|
52 |
|
53 |
-
const is_adding:any = useRef({state:false,interval:null})
|
54 |
-
const temp_loading:any = useRef(false)
|
55 |
-
const temp_image_keys:any = useRef({})
|
56 |
-
const images:any = useRef({})
|
57 |
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().idx as string));
|
58 |
|
59 |
|
@@ -67,7 +62,7 @@ const Index = ({}:any) => {
|
|
67 |
setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
|
68 |
return
|
69 |
}
|
70 |
-
|
71 |
const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX.current, {exclude_fields:["data"]})
|
72 |
if (stored_chapter?.data_state !== "completed"){
|
73 |
setIsError({state:true,text:"Chapter is not download yet!"})
|
@@ -82,88 +77,51 @@ const Index = ({}:any) => {
|
|
82 |
const stored_chapter_zoom = await Storage.get("CHAPTER_ZOOM") || 0
|
83 |
setZoom(stored_chapter_zoom)
|
84 |
|
85 |
-
|
86 |
-
|
87 |
-
const chapter_current = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current)
|
88 |
-
if (chapter_current){
|
89 |
-
images.current = {...images.current,...chapter_current?.files}
|
90 |
-
setImageKeys(chapter_current?.file_keys)
|
91 |
-
setFirstLoading(false)
|
92 |
-
}
|
93 |
|
94 |
-
|
95 |
-
|
96 |
-
temp_image_keys.current[`${CHAPTER_IDX.current+1}`] = chapter_next?.file_keys
|
97 |
-
images.current = {...images.current,...chapter_next?.files}
|
98 |
-
}
|
99 |
-
|
100 |
-
temp_loading.current = false
|
101 |
})()},[])
|
102 |
|
103 |
|
104 |
useFocusEffect(useCallback(() => {
|
105 |
return () => {
|
106 |
-
|
107 |
-
images.current = {}
|
108 |
-
clearInterval(is_adding.current.interval)
|
109 |
}
|
110 |
},[]))
|
111 |
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
is_adding.current.state = true
|
116 |
-
setIsLoading(true)
|
117 |
-
clearInterval(is_adding.current.interval)
|
118 |
-
is_adding.current.interval = setInterval(()=>{
|
119 |
-
if (!temp_loading.current) {
|
120 |
-
clearInterval(is_adding.current.interval);
|
121 |
-
temp_loading.current = true;
|
122 |
-
(async ()=>{
|
123 |
-
const new_chapter_idx = CHAPTER_IDX.current+1
|
124 |
-
if (imageKeys.slice(-1)[0]?.type !== "no-chapter-banner"){
|
125 |
-
const next_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,new_chapter_idx, {exclude_fields:["data"]})
|
126 |
-
if (next_stored_chapter?.data_state === "completed"){
|
127 |
-
if (temp_image_keys.current.hasOwnProperty(new_chapter_idx)){
|
128 |
-
delete temp_image_keys.current[new_chapter_idx-2]
|
129 |
-
const pre_image_keys = imageKeys.filter((data:any) => {
|
130 |
-
if (data.type === "image"){
|
131 |
-
const idx = Number(data.value.split("-")[0])
|
132 |
-
if (idx < new_chapter_idx-2) return false
|
133 |
-
else return true
|
134 |
-
}else if (data.type === "chapter-info-banner"){
|
135 |
-
if (data.idx < new_chapter_idx-2) return false
|
136 |
-
}else return true
|
137 |
-
})
|
138 |
-
|
139 |
-
setImageKeys([...pre_image_keys,{type:"chapter-info-banner", idx:CHAPTER_IDX.current, value:{last:chapterInfo.title, next:next_stored_chapter?.title}},...temp_image_keys.current[new_chapter_idx]])
|
140 |
-
setIsLoading(false)
|
141 |
-
setAddChapter(false)
|
142 |
-
is_adding.current.state = false
|
143 |
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
is_adding.current.state = false
|
162 |
-
})()
|
163 |
}
|
164 |
-
|
165 |
-
|
|
|
|
|
166 |
|
|
|
|
|
|
|
|
|
|
|
167 |
|
168 |
return (<>
|
169 |
{isError.state
|
@@ -246,7 +204,7 @@ const Index = ({}:any) => {
|
|
246 |
>{isError.text}</Text>
|
247 |
</View>
|
248 |
</View>
|
249 |
-
: <>{!
|
250 |
? <>
|
251 |
<View
|
252 |
|
@@ -257,47 +215,16 @@ const Index = ({}:any) => {
|
|
257 |
zIndex:0
|
258 |
}}
|
259 |
>
|
260 |
-
<
|
261 |
-
data={
|
262 |
-
renderItem={
|
263 |
-
if (item.type === "image"){
|
264 |
-
item.image_data = images.current[item.value].data
|
265 |
-
item.layout = images.current[item.value].layout
|
266 |
-
}
|
267 |
-
return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions}/>
|
268 |
-
}}
|
269 |
-
estimatedItemSize={StaticDimensions.height}
|
270 |
-
extraData={{zoom:zoom,showOptions:showOptions,setShowOptions:setShowOptions}}
|
271 |
onEndReachedThreshold={0.5}
|
272 |
-
|
273 |
-
|
274 |
-
}
|
275 |
-
onViewableItemsChanged={
|
276 |
-
const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
|
277 |
-
const current_count = viewableItems.filter((data:any) => data.item.idx === CHAPTER_IDX.current).length
|
278 |
-
const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.idx)).length
|
279 |
-
|
280 |
-
if (current_count || existed_count){
|
281 |
-
const choose_idx = current_count > existed_count ? CHAPTER_IDX.current : viewableItems.find((data:any) => expect_chapter_idx.includes(data.item.idx))?.item.idx
|
282 |
-
if (choose_idx === CHAPTER_IDX.current) return
|
283 |
-
const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,choose_idx, {exclude_fields:["data"]})
|
284 |
-
setChapterInfo({
|
285 |
-
chapter_id: stored_chapter?.id,
|
286 |
-
chapter_idx: stored_chapter?.id,
|
287 |
-
title: stored_chapter?.title,
|
288 |
-
})
|
289 |
-
const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
|
290 |
-
if (stored_comic.history.idx && choose_idx > stored_comic.history.idx) {
|
291 |
-
await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter?.idx,id:stored_chapter?.id,title:stored_chapter?.title})
|
292 |
-
}
|
293 |
-
router.setParams({idx:choose_idx})
|
294 |
-
CHAPTER_IDX.current = choose_idx
|
295 |
-
}
|
296 |
-
|
297 |
-
|
298 |
-
}}
|
299 |
/>
|
300 |
-
{
|
301 |
<View
|
302 |
style={{
|
303 |
display:"flex",
|
|
|
42 |
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
43 |
|
44 |
const [isError,setIsError]:any = useState({state:false,text:""})
|
45 |
+
const [isLoading, setIsLoading]:any = useState(true)
|
46 |
const [chapterInfo, setChapterInfo]:any = useState({})
|
47 |
const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
|
48 |
+
const [DATA, SET_DATA]:any = useState([])
|
49 |
+
const [isAdding, setIsAdding]:any = useState(false)
|
|
|
50 |
const [zoom, setZoom]:any = useState(0)
|
51 |
|
|
|
|
|
|
|
|
|
52 |
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().idx as string));
|
53 |
|
54 |
|
|
|
62 |
setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
|
63 |
return
|
64 |
}
|
65 |
+
|
66 |
const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX.current, {exclude_fields:["data"]})
|
67 |
if (stored_chapter?.data_state !== "completed"){
|
68 |
setIsError({state:true,text:"Chapter is not download yet!"})
|
|
|
77 |
const stored_chapter_zoom = await Storage.get("CHAPTER_ZOOM") || 0
|
78 |
setZoom(stored_chapter_zoom)
|
79 |
|
80 |
+
const chapter_current_data = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
81 |
|
82 |
+
SET_DATA(chapter_current_data)
|
83 |
+
setIsLoading(false)
|
|
|
|
|
|
|
|
|
|
|
84 |
})()},[])
|
85 |
|
86 |
|
87 |
useFocusEffect(useCallback(() => {
|
88 |
return () => {
|
89 |
+
SET_DATA([])
|
|
|
|
|
90 |
}
|
91 |
},[]))
|
92 |
|
93 |
+
const renderItem = useCallback(({item,index}:any) => {
|
94 |
+
return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions}/>
|
95 |
+
},[zoom,showOptions,setShowOptions])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
|
97 |
+
const onViewableItemsChanged = useCallback(async ({viewableItems, changed}:any) => {
|
98 |
+
const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
|
99 |
+
const current_count = viewableItems.filter((data:any) => data.item.chapter_idx === CHAPTER_IDX.current).length
|
100 |
+
const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.chapter_idx)).length
|
101 |
+
|
102 |
+
if (current_count || existed_count){
|
103 |
+
const choose_idx = current_count > existed_count ? CHAPTER_IDX.current : viewableItems.find((data:any) => expect_chapter_idx.includes(data.item.chapter_idx))?.item.idx
|
104 |
+
if (choose_idx === CHAPTER_IDX.current) return
|
105 |
+
const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,choose_idx, {exclude_fields:["data"]})
|
106 |
+
setChapterInfo({
|
107 |
+
chapter_id: stored_chapter?.id,
|
108 |
+
chapter_idx: stored_chapter?.id,
|
109 |
+
title: stored_chapter?.title,
|
110 |
+
})
|
111 |
+
const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
|
112 |
+
if (stored_comic.history.idx && choose_idx > stored_comic.history.idx) {
|
113 |
+
await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter?.idx,id:stored_chapter?.id,title:stored_chapter?.title})
|
|
|
|
|
114 |
}
|
115 |
+
router.setParams({idx:choose_idx})
|
116 |
+
CHAPTER_IDX.current = choose_idx
|
117 |
+
}
|
118 |
+
},[])
|
119 |
|
120 |
+
const onEndReached = useCallback(async () => {
|
121 |
+
const chapter_current_data = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current+1)
|
122 |
+
|
123 |
+
SET_DATA([...DATA,...chapter_current_data])
|
124 |
+
},[DATA])
|
125 |
|
126 |
return (<>
|
127 |
{isError.state
|
|
|
204 |
>{isError.text}</Text>
|
205 |
</View>
|
206 |
</View>
|
207 |
+
: <>{!isLoading
|
208 |
? <>
|
209 |
<View
|
210 |
|
|
|
215 |
zIndex:0
|
216 |
}}
|
217 |
>
|
218 |
+
<FlatList
|
219 |
+
data={DATA}
|
220 |
+
renderItem={renderItem}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
onEndReachedThreshold={0.5}
|
222 |
+
windowSize={21}
|
223 |
+
ItemSeparatorComponent={undefined}
|
224 |
+
onEndReached={onEndReached}
|
225 |
+
onViewableItemsChanged={onViewableItemsChanged}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
226 |
/>
|
227 |
+
{isAdding && (
|
228 |
<View
|
229 |
style={{
|
230 |
display:"flex",
|
frontend/app/read/components/chapter_image.tsx
CHANGED
@@ -15,6 +15,7 @@ import NetInfo from "@react-native-community/netinfo";
|
|
15 |
import JSZip from 'jszip';
|
16 |
|
17 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
|
18 |
import Image from '@/components/Image';
|
19 |
import {CONTEXT} from '@/constants/module/context';
|
20 |
import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
|
@@ -30,111 +31,171 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions}:any)=>{
|
|
30 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
31 |
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
32 |
|
|
|
|
|
33 |
|
34 |
-
|
35 |
-
|
36 |
-
onPress={()=>{setShowOptions({type:"general",state:!showOptions.state})}}
|
37 |
-
style={{
|
38 |
-
display:"flex",
|
39 |
-
width:"100%",
|
40 |
-
height:"auto",
|
41 |
-
borderWidth:0,
|
42 |
-
alignItems:"center",
|
43 |
-
}}
|
44 |
-
>
|
45 |
-
{item.type === "image" && (
|
46 |
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
aspectRatio: item.layout.width / item.layout.height,
|
54 |
-
}}
|
55 |
-
onLoadEnd={()=>{
|
56 |
-
// console.log("CLEANED")
|
57 |
-
// delete images.current[image_key]
|
58 |
-
}}
|
59 |
-
/>
|
60 |
-
)}
|
61 |
-
{item.type === "chapter-info-banner" && (
|
62 |
-
<View
|
63 |
-
style={{
|
64 |
-
display:"flex",
|
65 |
-
flexDirection:"column",
|
66 |
-
alignItems:"center",
|
67 |
-
gap:12,
|
68 |
-
width:Dimensions.width > 720
|
69 |
-
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
70 |
-
: `${100 - zoom}%`,
|
71 |
-
height:"auto",
|
72 |
-
backgroundColor:"black",
|
73 |
-
padding:16,
|
74 |
-
}}
|
75 |
-
>
|
76 |
-
<Text selectable={false}
|
77 |
-
numberOfLines={1}
|
78 |
-
style={{
|
79 |
-
color:Theme[themeTypeContext].text_color,
|
80 |
-
fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
|
81 |
-
fontFamily:"roboto-bold",
|
82 |
-
}}
|
83 |
-
>
|
84 |
-
End: {item.value.last}
|
85 |
-
</Text>
|
86 |
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
>
|
95 |
-
Next: {item.value.next}
|
96 |
-
</Text>
|
97 |
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
}
|
139 |
|
140 |
export default ChapterImage
|
|
|
15 |
import JSZip from 'jszip';
|
16 |
|
17 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
18 |
+
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
19 |
import Image from '@/components/Image';
|
20 |
import {CONTEXT} from '@/constants/module/context';
|
21 |
import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
|
|
|
31 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
32 |
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
33 |
|
34 |
+
const [isReady, setIsReady] = useState(false);
|
35 |
+
const [isError, setIsError] = useState({state:false,text:""});
|
36 |
|
37 |
+
const image = useRef<any>(null);
|
38 |
+
const image_layout = useRef<any>(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
|
40 |
+
useEffect(()=>{(async () => {
|
41 |
+
if (item.type === "page"){
|
42 |
+
const store_chapter_image_data = await ChapterDataStorage.get(item.id)
|
43 |
+
if (store_chapter_image_data){
|
44 |
+
image.current = {type:"blob",data:store_chapter_image_data.data}
|
45 |
+
image_layout.current = store_chapter_image_data.layout
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
+
setIsReady(true)
|
48 |
+
}else{
|
49 |
+
setIsError({state:true,text:"Image not found!"})
|
50 |
+
}
|
51 |
+
}else setIsReady(true)
|
52 |
+
|
53 |
+
})()},[])
|
|
|
|
|
|
|
54 |
|
55 |
+
|
56 |
+
return ( <Pressable
|
57 |
+
onPress={()=>{setShowOptions({type:"general",state:!showOptions.state})}}
|
58 |
+
style={{
|
59 |
+
display:"flex",
|
60 |
+
width:"100%",
|
61 |
+
height:"auto",
|
62 |
+
borderWidth:0,
|
63 |
+
alignItems:"center",
|
64 |
+
}}
|
65 |
+
>
|
66 |
+
<>{isReady
|
67 |
+
? (<>
|
68 |
+
{item.type === "page" && (
|
69 |
+
<Image source={image.current}
|
70 |
+
contentFit="contain"
|
71 |
+
style={{
|
72 |
+
width:Dimensions.width > 720
|
73 |
+
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
74 |
+
: `${100 - zoom}%`,
|
75 |
+
aspectRatio: image_layout.current.width / image_layout.current.height,
|
76 |
+
}}
|
77 |
+
onLoadEnd={()=>{
|
78 |
+
}}
|
79 |
+
/>
|
80 |
+
)}
|
81 |
+
{item.type === "chapter-info-banner" && (
|
82 |
+
<View
|
83 |
+
style={{
|
84 |
+
display:"flex",
|
85 |
+
flexDirection:"column",
|
86 |
+
alignItems:"center",
|
87 |
+
gap:12,
|
88 |
+
width:Dimensions.width > 720
|
89 |
+
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
90 |
+
: `${100 - zoom}%`,
|
91 |
+
height:"auto",
|
92 |
+
backgroundColor:"black",
|
93 |
+
padding:16,
|
94 |
+
}}
|
95 |
+
>
|
96 |
+
<Text selectable={false}
|
97 |
+
numberOfLines={1}
|
98 |
+
style={{
|
99 |
+
color:Theme[themeTypeContext].text_color,
|
100 |
+
fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
|
101 |
+
fontFamily:"roboto-bold",
|
102 |
+
}}
|
103 |
+
>
|
104 |
+
End: {item.value.last}
|
105 |
+
</Text>
|
106 |
+
|
107 |
+
<Text selectable={false}
|
108 |
+
numberOfLines={1}
|
109 |
+
style={{
|
110 |
+
color:Theme[themeTypeContext].text_color,
|
111 |
+
fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
|
112 |
+
fontFamily:"roboto-bold",
|
113 |
+
}}
|
114 |
+
>
|
115 |
+
Next: {item.value.next}
|
116 |
+
</Text>
|
117 |
+
|
118 |
+
</View>
|
119 |
+
)}
|
120 |
+
{item.type === "no-chapter-banner" && (
|
121 |
+
<View
|
122 |
+
style={{
|
123 |
+
display:"flex",
|
124 |
+
flexDirection:"column",
|
125 |
+
alignItems:"center",
|
126 |
+
gap:12,
|
127 |
+
width:Dimensions.width > 720
|
128 |
+
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
129 |
+
: `${100 - zoom}%`,
|
130 |
+
height:"auto",
|
131 |
+
backgroundColor:"black",
|
132 |
+
padding:16,
|
133 |
+
}}
|
134 |
+
>
|
135 |
+
<Text selectable={false}
|
136 |
+
numberOfLines={1}
|
137 |
+
style={{
|
138 |
+
color:Theme[themeTypeContext].text_color,
|
139 |
+
fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
|
140 |
+
fontFamily:"roboto-bold",
|
141 |
+
}}
|
142 |
+
>
|
143 |
+
No more available chapters on local.
|
144 |
+
</Text>
|
145 |
+
<Text selectable={false}
|
146 |
+
numberOfLines={1}
|
147 |
+
style={{
|
148 |
+
color:Theme[themeTypeContext].text_color,
|
149 |
+
fontSize:(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100),
|
150 |
+
fontFamily:"roboto-bold",
|
151 |
+
}}
|
152 |
+
>
|
153 |
+
You can go back and download more.
|
154 |
+
</Text>
|
155 |
+
</View>
|
156 |
+
)}
|
157 |
+
</>)
|
158 |
+
: (
|
159 |
+
<View
|
160 |
+
style={{
|
161 |
+
display:"flex",
|
162 |
+
justifyContent:"center",
|
163 |
+
alignItems:"center",
|
164 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
165 |
+
width:Dimensions.width > 720
|
166 |
+
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
167 |
+
: `${100 - zoom}%`,
|
168 |
+
height: Dimensions.height * 0.75
|
169 |
+
}}
|
170 |
+
>
|
171 |
+
{isError.state
|
172 |
+
? (
|
173 |
+
<View
|
174 |
+
style={{
|
175 |
+
display:'flex',
|
176 |
+
flexDirection:"column",
|
177 |
+
justifyContent:"center",
|
178 |
+
alignItems:"center",
|
179 |
+
width:"100%",
|
180 |
+
height:"100%",
|
181 |
+
gap:12,
|
182 |
+
}}
|
183 |
+
>
|
184 |
+
|
185 |
+
<Icon source={"alert-circle"} size={25} color={"red"}/>
|
186 |
+
<Text style={{color:"white",fontSize:12,fontFamily:"roboto-bold"}}>{isError.text}</Text>
|
187 |
+
</View>
|
188 |
+
)
|
189 |
+
: (<ActivityIndicator animating={true}/>)
|
190 |
+
}
|
191 |
+
|
192 |
+
|
193 |
+
</View>
|
194 |
+
)
|
195 |
+
}</>
|
196 |
+
|
197 |
+
</Pressable>
|
198 |
+
)
|
199 |
}
|
200 |
|
201 |
export default ChapterImage
|
frontend/app/read/modules/get_chapter.tsx
CHANGED
@@ -9,53 +9,25 @@ export const get_chapter = async (
|
|
9 |
COMIC_ID:string | string[],
|
10 |
CHAPTER_IDX:number,
|
11 |
) => {
|
12 |
-
|
13 |
console.log(SOURCE,COMIC_ID,CHAPTER_IDX)
|
14 |
|
15 |
-
const
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
const files: { [key: string]: any } = {};
|
20 |
-
|
21 |
-
if (stored_chapter?.data.type === "blob"){
|
22 |
-
const zipContent = await zip.loadAsync(stored_chapter?.data.value);
|
23 |
-
for (const fileName in zipContent.files) {
|
24 |
-
if (zipContent.files[fileName].dir) {
|
25 |
-
continue; // Skip directories
|
26 |
-
}
|
27 |
-
const fileData = await zipContent.files[fileName].async('base64');
|
28 |
-
file_keys.push({type:"image", idx:CHAPTER_IDX, id:Number(fileName.split(".")[0]), value: `${CHAPTER_IDX}-${fileName}`})
|
29 |
-
files[`${CHAPTER_IDX}-${fileName}`] = {
|
30 |
-
layout: await getImageLayout("data:image/png;base64," + fileData),
|
31 |
-
data: "data:image/png;base64," + fileData
|
32 |
-
};
|
33 |
-
|
34 |
-
}
|
35 |
-
|
36 |
-
}else if (stored_chapter?.data.type === "file_path"){
|
37 |
-
|
38 |
-
const base64String = await FileSystem.readAsStringAsync(stored_chapter?.data.value, {
|
39 |
-
encoding: FileSystem.EncodingType.Base64,
|
40 |
-
});
|
41 |
-
|
42 |
-
const zipContent = await zip.loadAsync(base64String, {base64: true});
|
43 |
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
}
|
48 |
-
const fileData = await zipContent.files[fileName].async('base64');
|
49 |
-
file_keys.push({type:"image", idx:CHAPTER_IDX, id:Number(fileName.split(".")[0]), value: `${CHAPTER_IDX}-${fileName}`})
|
50 |
-
files[`${CHAPTER_IDX}-${fileName}`] = {
|
51 |
-
layout: await getImageLayout("data:image/png;base64," + fileData),
|
52 |
-
data: "data:image/png;base64," + fileData
|
53 |
-
};
|
54 |
-
}
|
55 |
}
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
|
|
|
|
|
|
|
|
60 |
}
|
61 |
}
|
|
|
9 |
COMIC_ID:string | string[],
|
10 |
CHAPTER_IDX:number,
|
11 |
) => {
|
12 |
+
|
13 |
console.log(SOURCE,COMIC_ID,CHAPTER_IDX)
|
14 |
|
15 |
+
const DATA = []
|
16 |
+
|
17 |
+
const current_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX)
|
18 |
+
const next_stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,CHAPTER_IDX+1)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
|
20 |
+
if (current_stored_chapter?.data_state === "completed" && current_stored_chapter?.max_page) {
|
21 |
+
for (let i = 1; i < current_stored_chapter.max_page; i++) {
|
22 |
+
DATA.push({type:"page",id:`${SOURCE}-${COMIC_ID}-${CHAPTER_IDX}-${i}`, chapter_idx: CHAPTER_IDX})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
}
|
24 |
+
if (next_stored_chapter) {
|
25 |
+
DATA.push({type:"chapter-info-banner", value:{last:current_stored_chapter.title, next:next_stored_chapter.title}})
|
26 |
+
}else{
|
27 |
+
DATA.push({type:"no-chapter-banner"})
|
28 |
+
}
|
29 |
+
return DATA
|
30 |
+
}else{
|
31 |
+
return []
|
32 |
}
|
33 |
}
|
frontend/app/view/[source]/[comic_id].tsx
CHANGED
@@ -190,7 +190,7 @@ const Index = ({}:any) => {
|
|
190 |
for (const [key, value] of Object.entries(stored_comic.info)) {
|
191 |
DATA[key] = value
|
192 |
}
|
193 |
-
DATA["chapters"] = await ChapterStorage.getAll(`${SOURCE}-${ID}`,{exclude_field:["
|
194 |
|
195 |
SET_CONTENT(DATA)
|
196 |
setIsLoading(false)
|
@@ -308,7 +308,7 @@ const Index = ({}:any) => {
|
|
308 |
|
309 |
onPress={()=>{
|
310 |
if (router.canGoBack()) router.back()
|
311 |
-
else router.
|
312 |
}}
|
313 |
>
|
314 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
|
@@ -362,7 +362,7 @@ const Index = ({}:any) => {
|
|
362 |
}}
|
363 |
|
364 |
onPress={(async ()=>{
|
365 |
-
await Clipboard.setStringAsync(
|
366 |
Toast.show({
|
367 |
type: 'info',
|
368 |
text1: '📋 Copied to your clipboard.',
|
|
|
190 |
for (const [key, value] of Object.entries(stored_comic.info)) {
|
191 |
DATA[key] = value
|
192 |
}
|
193 |
+
DATA["chapters"] = await ChapterStorage.getAll(`${SOURCE}-${ID}`,{exclude_field:["item"]})
|
194 |
|
195 |
SET_CONTENT(DATA)
|
196 |
setIsLoading(false)
|
|
|
308 |
|
309 |
onPress={()=>{
|
310 |
if (router.canGoBack()) router.back()
|
311 |
+
else router.replace("/explore")
|
312 |
}}
|
313 |
>
|
314 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
|
|
|
362 |
}}
|
363 |
|
364 |
onPress={(async ()=>{
|
365 |
+
await Clipboard.setStringAsync(`https://comicmtl.netlify.app/view/${SOURCE}/${ID}/`)
|
366 |
Toast.show({
|
367 |
type: 'info',
|
368 |
text1: '📋 Copied to your clipboard.',
|
frontend/app/view/componenets/chapter.tsx
CHANGED
@@ -64,13 +64,13 @@ const ChapterComponent = ({
|
|
64 |
set_is_net_connected(net_info.isConnected)
|
65 |
|
66 |
setStyles(__styles(themeTypeContext,Dimensions))
|
67 |
-
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id
|
68 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
69 |
else set_is_saved(false)
|
70 |
})()}, [])
|
71 |
|
72 |
useEffect(()=>{(async () => {
|
73 |
-
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id
|
74 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
75 |
else set_is_saved(false)
|
76 |
})()},[page,sort])
|
@@ -130,7 +130,7 @@ const ChapterComponent = ({
|
|
130 |
padding:5,
|
131 |
}}
|
132 |
onPress={async () => {
|
133 |
-
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id
|
134 |
if (stored_chapter?.data_state === "completed") {
|
135 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
136 |
if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
@@ -205,8 +205,8 @@ const ChapterComponent = ({
|
|
205 |
<>{chapterRequested[chapter.id]?.state === "ready"
|
206 |
&& <>{chapterToDownload[chapter.id]?.state === "downloading"
|
207 |
? <CircularProgress
|
208 |
-
value={downloadProgress[chapter.id]
|
209 |
-
maxValue={downloadProgress[chapter.id]
|
210 |
radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
|
211 |
inActiveStrokeColor={Theme[themeTypeContext].border_color}
|
212 |
|
@@ -220,6 +220,8 @@ const ChapterComponent = ({
|
|
220 |
textAlign:"center",
|
221 |
}}
|
222 |
onAnimationComplete={async ()=>{
|
|
|
|
|
223 |
const stored_chapter_requested = (await ComicStorage.getByID(SOURCE,ID)).chapter_requested
|
224 |
const new_chapter_requested = stored_chapter_requested.filter((item:any) => item.chapter_id !== chapter.id);
|
225 |
await ComicStorage.updateChapterQueue(SOURCE,ID,new_chapter_requested)
|
@@ -237,6 +239,7 @@ const ChapterComponent = ({
|
|
237 |
|
238 |
set_is_saved(true)
|
239 |
isDownloading.current = false
|
|
|
240 |
}}
|
241 |
/>
|
242 |
: <ActivityIndicator animating={true} color={"green"} />
|
|
|
64 |
set_is_net_connected(net_info.isConnected)
|
65 |
|
66 |
setStyles(__styles(themeTypeContext,Dimensions))
|
67 |
+
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
68 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
69 |
else set_is_saved(false)
|
70 |
})()}, [])
|
71 |
|
72 |
useEffect(()=>{(async () => {
|
73 |
+
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
74 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
75 |
else set_is_saved(false)
|
76 |
})()},[page,sort])
|
|
|
130 |
padding:5,
|
131 |
}}
|
132 |
onPress={async () => {
|
133 |
+
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
134 |
if (stored_chapter?.data_state === "completed") {
|
135 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
136 |
if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
|
|
205 |
<>{chapterRequested[chapter.id]?.state === "ready"
|
206 |
&& <>{chapterToDownload[chapter.id]?.state === "downloading"
|
207 |
? <CircularProgress
|
208 |
+
value={downloadProgress[chapter.id].progress}
|
209 |
+
maxValue={downloadProgress[chapter.id].total}
|
210 |
radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
|
211 |
inActiveStrokeColor={Theme[themeTypeContext].border_color}
|
212 |
|
|
|
220 |
textAlign:"center",
|
221 |
}}
|
222 |
onAnimationComplete={async ()=>{
|
223 |
+
if (downloadProgress[chapter.id].progress !== downloadProgress[chapter.id].total) return
|
224 |
+
|
225 |
const stored_chapter_requested = (await ComicStorage.getByID(SOURCE,ID)).chapter_requested
|
226 |
const new_chapter_requested = stored_chapter_requested.filter((item:any) => item.chapter_id !== chapter.id);
|
227 |
await ComicStorage.updateChapterQueue(SOURCE,ID,new_chapter_requested)
|
|
|
239 |
|
240 |
set_is_saved(true)
|
241 |
isDownloading.current = false
|
242 |
+
console.log("DONE!",downloadProgress[chapter.id])
|
243 |
}}
|
244 |
/>
|
245 |
: <ActivityIndicator animating={true} color={"green"} />
|
frontend/app/view/modules/content.tsx
CHANGED
@@ -3,12 +3,15 @@ import JSZip from 'jszip';
|
|
3 |
import * as FileSystem from 'expo-file-system';
|
4 |
import { Platform } from 'react-native';
|
5 |
|
|
|
6 |
import translator from '@/constants/module/translator';
|
7 |
import Storage from '@/constants/module/storages/storage';
|
8 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
9 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
10 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
|
11 |
import {blobToBase64} from '@/constants/module/file_manager';
|
|
|
12 |
|
13 |
|
14 |
|
@@ -180,9 +183,7 @@ export const download_chapter = async (
|
|
180 |
const [chapter_id, request_info]:any = Object.entries(chapterToDownload)[0];
|
181 |
|
182 |
var progress_lenth:number = 0
|
183 |
-
var total_length:number =
|
184 |
-
setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
|
185 |
-
setChapterToDownload({...chapterToDownload,[chapter_id]:{...chapterToDownload[chapter_id],state:"downloading"}})
|
186 |
|
187 |
await axios({
|
188 |
method: 'post',
|
@@ -201,9 +202,16 @@ export const download_chapter = async (
|
|
201 |
onDownloadProgress: (progressEvent) => {
|
202 |
const _total_length = progressEvent.total
|
203 |
if (total_length !== undefined && progressEvent.loaded !== progress_lenth) {
|
204 |
-
total_length = _total_length as number +
|
205 |
-
|
206 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
207 |
}
|
208 |
},
|
209 |
timeout: 600000,
|
@@ -211,23 +219,34 @@ export const download_chapter = async (
|
|
211 |
}).then(async (response) => {
|
212 |
const DATA = response.data
|
213 |
if (Platform.OS === "web"){
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
}else{
|
216 |
-
const chapter_dir = FileSystem.documentDirectory + "ComicMTL/" + `${source}/` + `${comic_id}/` + `chapters/`;
|
217 |
-
const dirInfo = await FileSystem.getInfoAsync(chapter_dir);
|
218 |
-
if (!dirInfo.exists) await FileSystem.makeDirectoryAsync(chapter_dir, { intermediates: true });
|
219 |
-
await FileSystem.writeAsStringAsync(chapter_dir + `${request_info.chapter_idx}.zip`, (await blobToBase64(DATA)).split(',')[1], {
|
220 |
-
|
221 |
-
});
|
222 |
|
223 |
-
await ChapterStorage.update(`${source}-${comic_id}`,chapter_id,{type:"file_path", value:chapter_dir + `${request_info.chapter_idx}.zip`}, "completed")
|
224 |
}
|
225 |
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth+5, total:total_length}})
|
231 |
|
232 |
|
233 |
|
|
|
3 |
import * as FileSystem from 'expo-file-system';
|
4 |
import { Platform } from 'react-native';
|
5 |
|
6 |
+
|
7 |
import translator from '@/constants/module/translator';
|
8 |
import Storage from '@/constants/module/storages/storage';
|
9 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
10 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
11 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
12 |
+
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
13 |
import {blobToBase64} from '@/constants/module/file_manager';
|
14 |
+
import { getImageLayout } from '@/constants/module/file_manager';
|
15 |
|
16 |
|
17 |
|
|
|
183 |
const [chapter_id, request_info]:any = Object.entries(chapterToDownload)[0];
|
184 |
|
185 |
var progress_lenth:number = 0
|
186 |
+
var total_length:number = 0
|
|
|
|
|
187 |
|
188 |
await axios({
|
189 |
method: 'post',
|
|
|
202 |
onDownloadProgress: (progressEvent) => {
|
203 |
const _total_length = progressEvent.total
|
204 |
if (total_length !== undefined && progressEvent.loaded !== progress_lenth) {
|
205 |
+
total_length = (_total_length as number) + (_total_length as number)*0.25
|
206 |
+
|
207 |
+
if (progress_lenth === 0) {
|
208 |
+
setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
|
209 |
+
setChapterToDownload({...chapterToDownload,[chapter_id]:{...chapterToDownload[chapter_id],state:"downloading"}})
|
210 |
+
progress_lenth = progressEvent.loaded;
|
211 |
+
}else{
|
212 |
+
progress_lenth = progressEvent.loaded;
|
213 |
+
setDownloadProgress({...downloadProgress, [chapter_id]:{progress:progress_lenth, total:total_length}})
|
214 |
+
}
|
215 |
}
|
216 |
},
|
217 |
timeout: 600000,
|
|
|
219 |
}).then(async (response) => {
|
220 |
const DATA = response.data
|
221 |
if (Platform.OS === "web"){
|
222 |
+
|
223 |
+
const zip = new JSZip();
|
224 |
+
const zipContent = await zip.loadAsync(DATA);
|
225 |
+
let page = 0;
|
226 |
+
for (const fileName of Object.keys(zipContent.files).sort((a,b) => parseInt(a, 10) - parseInt(b, 10))) {
|
227 |
+
if (zipContent.files[fileName].dir) {
|
228 |
+
continue; // Skip directories
|
229 |
+
}
|
230 |
+
page += 1
|
231 |
+
const fileData = await zipContent.files[fileName].async('blob');
|
232 |
+
const layout = await getImageLayout(await blobToBase64(fileData, "image/png"));
|
233 |
+
await ChapterDataStorage.store(`${source}-${comic_id}-${request_info.chapter_idx}-${page}`,comic_id,request_info.chapter_idx, fileData, layout)
|
234 |
+
|
235 |
+
}
|
236 |
+
|
237 |
+
await ChapterStorage.update(`${source}-${comic_id}`,chapter_id, "completed", page)
|
238 |
}else{
|
239 |
+
// const chapter_dir = FileSystem.documentDirectory + "ComicMTL/" + `${source}/` + `${comic_id}/` + `chapters/`;
|
240 |
+
// const dirInfo = await FileSystem.getInfoAsync(chapter_dir);
|
241 |
+
// if (!dirInfo.exists) await FileSystem.makeDirectoryAsync(chapter_dir, { intermediates: true });
|
242 |
+
// await FileSystem.writeAsStringAsync(chapter_dir + `${request_info.chapter_idx}.zip`, (await blobToBase64(DATA)).split(',')[1], {
|
243 |
+
// encoding: FileSystem.EncodingType.Base64,
|
244 |
+
// });
|
245 |
|
246 |
+
// await ChapterStorage.update(`${source}-${comic_id}`,chapter_id,{type:"file_path", value:chapter_dir + `${request_info.chapter_idx}.zip`}, "completed")
|
247 |
}
|
248 |
|
249 |
+
setDownloadProgress({...downloadProgress, [chapter_id]:{progress:total_length, total:total_length}})
|
|
|
|
|
|
|
|
|
250 |
|
251 |
|
252 |
|
frontend/components/Image.tsx
CHANGED
@@ -21,7 +21,7 @@ const Image = ({source, style, onError, contentFit, transition, onLoad, onLoadEn
|
|
21 |
(async ()=>{
|
22 |
if(source.hasOwnProperty("type")){
|
23 |
if (source.type === "blob"){
|
24 |
-
imageData.current = {uri:await blobToBase64(source.data)}
|
25 |
setImageLoaded(true)
|
26 |
}else if (source.type === "base64"){
|
27 |
imageData.current = {uri:source.data}
|
|
|
21 |
(async ()=>{
|
22 |
if(source.hasOwnProperty("type")){
|
23 |
if (source.type === "blob"){
|
24 |
+
imageData.current = {uri:await blobToBase64(source.data,"image/png")}
|
25 |
setImageLoaded(true)
|
26 |
}else if (source.type === "base64"){
|
27 |
imageData.current = {uri:source.data}
|
frontend/constants/module/file_manager.tsx
CHANGED
@@ -1,12 +1,18 @@
|
|
1 |
import { Image as RNImage } from 'react-native';
|
2 |
|
3 |
-
export const blobToBase64 = (blob: Blob): Promise<string> => {
|
4 |
-
|
5 |
const reader = new FileReader();
|
6 |
-
reader.onloadend = () =>
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
reader.onerror = reject;
|
8 |
reader.readAsDataURL(blob);
|
9 |
-
|
10 |
};
|
11 |
|
12 |
export const base64ToBlob = (base64: string, mimetype: string = 'image/png'): Blob => {
|
|
|
1 |
import { Image as RNImage } from 'react-native';
|
2 |
|
3 |
+
export const blobToBase64 = (blob: Blob, mimetype: string = ""): Promise<string> => {
|
4 |
+
return new Promise((resolve, reject) => {
|
5 |
const reader = new FileReader();
|
6 |
+
reader.onloadend = () => {
|
7 |
+
const base64String = reader.result as string;
|
8 |
+
if (mimetype){
|
9 |
+
const base64WithMimeType = `data:${mimetype};base64,${base64String.split(',')[1]}`;
|
10 |
+
resolve(base64WithMimeType);
|
11 |
+
}else resolve(base64String);
|
12 |
+
};
|
13 |
reader.onerror = reject;
|
14 |
reader.readAsDataURL(blob);
|
15 |
+
});
|
16 |
};
|
17 |
|
18 |
export const base64ToBlob = (base64: string, mimetype: string = 'image/png'): Blob => {
|
frontend/constants/module/storages/chapter_data_storage.tsx
ADDED
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Platform } from "react-native";
|
2 |
+
import * as SQLite from 'expo-sqlite';
|
3 |
+
|
4 |
+
const DATABASE_NAME = 'ChapterDataStorageDB'
|
5 |
+
|
6 |
+
class Chapter_Data_Storage_Web {
|
7 |
+
private static dbPromise: Promise<IDBDatabase>;
|
8 |
+
|
9 |
+
private static getDB(): Promise<IDBDatabase> {
|
10 |
+
if (!this.dbPromise) {
|
11 |
+
this.dbPromise = new Promise((resolve, reject) => {
|
12 |
+
const request = indexedDB.open(DATABASE_NAME, 1);
|
13 |
+
|
14 |
+
request.onupgradeneeded = (event) => {
|
15 |
+
const db = (event.target as IDBOpenDBRequest).result;
|
16 |
+
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
17 |
+
store.createIndex('comic_id', 'comic_id', { unique: false });
|
18 |
+
store.createIndex('chapter_idx', 'chapter_idx', { unique: false });
|
19 |
+
};
|
20 |
+
|
21 |
+
request.onsuccess = () => {
|
22 |
+
resolve(request.result);
|
23 |
+
};
|
24 |
+
|
25 |
+
request.onerror = () => {
|
26 |
+
reject(request.error);
|
27 |
+
};
|
28 |
+
});
|
29 |
+
}
|
30 |
+
return this.dbPromise;
|
31 |
+
}
|
32 |
+
|
33 |
+
static async store(id: string, comic_id:string, chapter_idx: number, data:any, layout:any ): Promise<void> {
|
34 |
+
const db = await this.getDB();
|
35 |
+
return new Promise((resolve, reject) => {
|
36 |
+
const transaction = db.transaction('dataStore', 'readwrite');
|
37 |
+
const store = transaction.objectStore('dataStore');
|
38 |
+
const request = store.put({ id, comic_id, chapter_idx, data, layout });
|
39 |
+
|
40 |
+
request.onsuccess = () => {
|
41 |
+
resolve();
|
42 |
+
};
|
43 |
+
|
44 |
+
request.onerror = () => {
|
45 |
+
reject(request.error);
|
46 |
+
};
|
47 |
+
});
|
48 |
+
}
|
49 |
+
|
50 |
+
static async get(id: string): Promise<{id: string, comic_id: string, chapter_idx: number, data: any } | null> {
|
51 |
+
const db = await this.getDB();
|
52 |
+
return new Promise((resolve, reject) => {
|
53 |
+
const transaction = db.transaction('dataStore', 'readonly');
|
54 |
+
const store = transaction.objectStore('dataStore');
|
55 |
+
const request = store.get(id); // Query by id
|
56 |
+
|
57 |
+
request.onsuccess = () => {
|
58 |
+
const data = request.result;
|
59 |
+
if (data) {
|
60 |
+
resolve(data);
|
61 |
+
} else {
|
62 |
+
resolve(null);
|
63 |
+
}
|
64 |
+
};
|
65 |
+
|
66 |
+
request.onerror = () => {
|
67 |
+
console.error('Error retrieving item:', request.error);
|
68 |
+
reject(request.error);
|
69 |
+
};
|
70 |
+
});
|
71 |
+
}
|
72 |
+
static async removeByID(id: string): Promise<void> {
|
73 |
+
const db = await this.getDB();
|
74 |
+
return new Promise((resolve, reject) => {
|
75 |
+
const transaction = db.transaction('dataStore', 'readwrite');
|
76 |
+
const store = transaction.objectStore('dataStore');
|
77 |
+
const request = store.delete(id); // Delete the item
|
78 |
+
request.onsuccess = () => {
|
79 |
+
resolve();
|
80 |
+
};
|
81 |
+
request.onerror = () => {
|
82 |
+
reject(request.error);
|
83 |
+
};
|
84 |
+
request.onerror = () => {
|
85 |
+
reject(request.error);
|
86 |
+
};
|
87 |
+
});
|
88 |
+
}
|
89 |
+
|
90 |
+
static async removeByComicID(comic_id: string): Promise<void> {
|
91 |
+
const db = await this.getDB();
|
92 |
+
return new Promise((resolve, reject) => {
|
93 |
+
const transaction = db.transaction('dataStore', 'readwrite');
|
94 |
+
const store = transaction.objectStore('dataStore');
|
95 |
+
const index = store.index('comic_id');
|
96 |
+
const request = index.openCursor(comic_id);
|
97 |
+
|
98 |
+
request.onsuccess = (event) => {
|
99 |
+
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
100 |
+
if (cursor) {
|
101 |
+
cursor.delete();
|
102 |
+
cursor.continue();
|
103 |
+
} else {
|
104 |
+
resolve();
|
105 |
+
}
|
106 |
+
};
|
107 |
+
|
108 |
+
request.onerror = () => {
|
109 |
+
reject(request.error);
|
110 |
+
};
|
111 |
+
});
|
112 |
+
}
|
113 |
+
}
|
114 |
+
|
115 |
+
|
116 |
+
class Chapter_Data_Storage_Native {
|
117 |
+
private DATABASE: any;
|
118 |
+
|
119 |
+
constructor() {
|
120 |
+
this.DATABASE = new Promise(async (resolve, reject) => {
|
121 |
+
resolve(await SQLite.openDatabaseAsync(DATABASE_NAME));
|
122 |
+
});
|
123 |
+
|
124 |
+
}
|
125 |
+
|
126 |
+
public async store(source: string, id: string, tag: string, info: any) {
|
127 |
+
try {
|
128 |
+
const db = await this.DATABASE;
|
129 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
130 |
+
await db.runAsync(
|
131 |
+
'INSERT OR REPLACE INTO ComicStorage (source, id, tag, info, chapter_requested, history) VALUES (?, ?, ?, ?, ?, ?);', source, id, tag, JSON.stringify(info), JSON.stringify([]), JSON.stringify({})
|
132 |
+
);
|
133 |
+
} catch (error) {
|
134 |
+
console.log(error);
|
135 |
+
}
|
136 |
+
}
|
137 |
+
|
138 |
+
public async getByID(source: string, id: string) {
|
139 |
+
try {
|
140 |
+
const db = await this.DATABASE;
|
141 |
+
// await db.runAsync("DROP TABLE IF EXISTS ComicStorage;");
|
142 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
143 |
+
const DATA: any = await db.getFirstAsync(
|
144 |
+
'SELECT * FROM ComicStorage WHERE source = ? AND id = ?;', source, id
|
145 |
+
);
|
146 |
+
if (DATA) return { id: DATA.id, source: DATA.source, tag: DATA.tag, info: JSON.parse(DATA.info), chapter_requested: JSON.parse(DATA.chapter_requested), history: JSON.parse(DATA.history) };
|
147 |
+
else return DATA;
|
148 |
+
} catch (error) {
|
149 |
+
console.log("[Comic Storage - getByID] error:", error);
|
150 |
+
}
|
151 |
+
}
|
152 |
+
|
153 |
+
public async getByTag(tag: string) {
|
154 |
+
try {
|
155 |
+
const db = await this.DATABASE;
|
156 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
157 |
+
const DATA: any = await db.allAsync(
|
158 |
+
'SELECT * FROM ComicStorage WHERE tag = ?;', tag
|
159 |
+
);
|
160 |
+
|
161 |
+
return DATA.map((row: any) => ({ id: row.id, source: row.source, tag: row.tag, info: JSON.parse(row.info), chapter_requested: JSON.parse(row.chapter_requested), history: JSON.parse(row.history) }));
|
162 |
+
} catch (error) {
|
163 |
+
console.log(error);
|
164 |
+
}
|
165 |
+
}
|
166 |
+
|
167 |
+
public async updateChapterQueue(source: string, id: string, chapter_requested: any) {
|
168 |
+
try {
|
169 |
+
const db = await this.DATABASE;
|
170 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
171 |
+
await db.runAsync(
|
172 |
+
'UPDATE ComicStorage SET chapter_requested = ? WHERE source = ? AND id = ?;', JSON.stringify(chapter_requested), source, id
|
173 |
+
);
|
174 |
+
} catch (error) {
|
175 |
+
console.log(error);
|
176 |
+
}
|
177 |
+
}
|
178 |
+
|
179 |
+
public async updateInfo(source: string, id: string, info: any) {
|
180 |
+
try {
|
181 |
+
const db = await this.DATABASE;
|
182 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
183 |
+
await db.runAsync(
|
184 |
+
'UPDATE ComicStorage SET info = ? WHERE source = ? AND id = ?;', JSON.stringify(info), source, id
|
185 |
+
);
|
186 |
+
} catch (error) {
|
187 |
+
console.log(error);
|
188 |
+
}
|
189 |
+
}
|
190 |
+
|
191 |
+
public async updateHistory(source:string, id: string, history: any): Promise<void> {
|
192 |
+
try {
|
193 |
+
const db = await this.DATABASE;
|
194 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
195 |
+
await db.runAsync(
|
196 |
+
'UPDATE ComicStorage SET history = ? WHERE source = ? AND id = ?;', JSON.stringify(history), source, id
|
197 |
+
);
|
198 |
+
} catch (error) {
|
199 |
+
console.log(error);
|
200 |
+
}
|
201 |
+
}
|
202 |
+
|
203 |
+
public async replaceTag(source: string, id: string, new_tag: string) {
|
204 |
+
try {
|
205 |
+
const db = await this.DATABASE;
|
206 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
207 |
+
await db.runAsync(
|
208 |
+
'UPDATE ComicStorage SET tag = ? WHERE source = ? AND id = ?;', new_tag, source, id
|
209 |
+
);
|
210 |
+
} catch (error) {
|
211 |
+
console.log(error);
|
212 |
+
}
|
213 |
+
}
|
214 |
+
|
215 |
+
public async removeByID(source: string, id: string) {
|
216 |
+
try {
|
217 |
+
const db = await this.DATABASE;
|
218 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
219 |
+
await db.runAsync(
|
220 |
+
'DELETE FROM ComicStorage WHERE source = ? AND id = ?;', source, id
|
221 |
+
);
|
222 |
+
} catch (error) {
|
223 |
+
console.log(error);
|
224 |
+
}
|
225 |
+
}
|
226 |
+
|
227 |
+
public async removeByTag(tag: string) {
|
228 |
+
try {
|
229 |
+
const db = await this.DATABASE;
|
230 |
+
await db.runAsync('CREATE TABLE IF NOT EXISTS ComicStorage (id TEXT PRIMARY KEY NOT NULL, source TEXT, tag TEXT, info TEXT, chapter_requested TEXT, history TEXT);');
|
231 |
+
await db.runAsync(
|
232 |
+
'DELETE FROM ComicStorage WHERE tag = ?;', tag
|
233 |
+
);
|
234 |
+
} catch (error) {
|
235 |
+
console.log(error);
|
236 |
+
}
|
237 |
+
}
|
238 |
+
}
|
239 |
+
|
240 |
+
|
241 |
+
|
242 |
+
var ChapterDataStorage:any
|
243 |
+
if (Platform.OS === "web") {
|
244 |
+
ChapterDataStorage = Chapter_Data_Storage_Web;
|
245 |
+
}else{
|
246 |
+
ChapterDataStorage = new Chapter_Data_Storage_Native();
|
247 |
+
}
|
248 |
+
|
249 |
+
export default ChapterDataStorage;
|
250 |
+
|
frontend/constants/module/storages/chapter_storage.tsx
CHANGED
@@ -8,7 +8,7 @@ import { ensure_safe_table_name } from "../ensure_safe_table_name";
|
|
8 |
const DATABASE_NAME = 'ChapterDB';
|
9 |
|
10 |
class Chapter_Storage_Web {
|
11 |
-
private static DATABASE_VERSION: number =
|
12 |
|
13 |
private static async openDB(): Promise<IDBDatabase> {
|
14 |
return new Promise((resolve, reject) => {
|
@@ -137,13 +137,13 @@ class Chapter_Storage_Web {
|
|
137 |
}
|
138 |
|
139 |
|
140 |
-
public static async add(item: string, idx: number, id: string, title: string
|
141 |
const db = await this.openDB();
|
142 |
return new Promise((resolve, reject) => {
|
143 |
const transaction = db.transaction('dataStore', 'readwrite');
|
144 |
const store = transaction.objectStore('dataStore');
|
145 |
|
146 |
-
const request = store.add({ id, item, idx, title,
|
147 |
|
148 |
request.onsuccess = () => {
|
149 |
resolve();
|
@@ -154,7 +154,7 @@ class Chapter_Storage_Web {
|
|
154 |
};
|
155 |
});
|
156 |
}
|
157 |
-
public static async update(item: string, id: string,
|
158 |
const db = await this.openDB();
|
159 |
return new Promise((resolve, reject) => {
|
160 |
const transaction = db.transaction('dataStore', 'readwrite');
|
@@ -166,8 +166,8 @@ class Chapter_Storage_Web {
|
|
166 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
167 |
if (cursor) {
|
168 |
if (cursor.value.id === id) {
|
169 |
-
cursor.value.data = newData;
|
170 |
cursor.value.data_state = data_state;
|
|
|
171 |
const updateRequest = cursor.update(cursor.value);
|
172 |
|
173 |
updateRequest.onsuccess = () => {
|
|
|
8 |
const DATABASE_NAME = 'ChapterDB';
|
9 |
|
10 |
class Chapter_Storage_Web {
|
11 |
+
private static DATABASE_VERSION: number = 2;
|
12 |
|
13 |
private static async openDB(): Promise<IDBDatabase> {
|
14 |
return new Promise((resolve, reject) => {
|
|
|
137 |
}
|
138 |
|
139 |
|
140 |
+
public static async add(item: string, idx: number, id: string, title: string): Promise<void> {
|
141 |
const db = await this.openDB();
|
142 |
return new Promise((resolve, reject) => {
|
143 |
const transaction = db.transaction('dataStore', 'readwrite');
|
144 |
const store = transaction.objectStore('dataStore');
|
145 |
|
146 |
+
const request = store.add({ id, item, idx, title, data_state:"", max_page:0 });
|
147 |
|
148 |
request.onsuccess = () => {
|
149 |
resolve();
|
|
|
154 |
};
|
155 |
});
|
156 |
}
|
157 |
+
public static async update(item: string, id: string, data_state: string, max_page:number): Promise<void> {
|
158 |
const db = await this.openDB();
|
159 |
return new Promise((resolve, reject) => {
|
160 |
const transaction = db.transaction('dataStore', 'readwrite');
|
|
|
166 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
167 |
if (cursor) {
|
168 |
if (cursor.value.id === id) {
|
|
|
169 |
cursor.value.data_state = data_state;
|
170 |
+
cursor.value.max_page = max_page;
|
171 |
const updateRequest = cursor.update(cursor.value);
|
172 |
|
173 |
updateRequest.onsuccess = () => {
|
frontend/constants/module/storages/comic_storage.tsx
CHANGED
@@ -9,7 +9,7 @@ class Comic_Storage_Web {
|
|
9 |
private static getDB(): Promise<IDBDatabase> {
|
10 |
if (!this.dbPromise) {
|
11 |
this.dbPromise = new Promise((resolve, reject) => {
|
12 |
-
const request = indexedDB.open(DATABASE_NAME,
|
13 |
|
14 |
request.onupgradeneeded = (event) => {
|
15 |
const db = (event.target as IDBOpenDBRequest).result;
|
|
|
9 |
private static getDB(): Promise<IDBDatabase> {
|
10 |
if (!this.dbPromise) {
|
11 |
this.dbPromise = new Promise((resolve, reject) => {
|
12 |
+
const request = indexedDB.open(DATABASE_NAME, 2);
|
13 |
|
14 |
request.onupgradeneeded = (event) => {
|
15 |
const db = (event.target as IDBOpenDBRequest).result;
|