Skip to content

Commit

Permalink
feat: 타입 재정의
Browse files Browse the repository at this point in the history
  • Loading branch information
kangju2000 committed Sep 8, 2024
1 parent 0f36c5b commit 1be2762
Show file tree
Hide file tree
Showing 20 changed files with 292 additions and 207 deletions.
9 changes: 8 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ module.exports = {
'no-unused-vars': 'off',
'no-empty-pattern': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/no-unused-vars': [
'warn',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
caughtErrorsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/naming-convention': [
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
"preview": "vite preview",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preinstall": "npx only-allow pnpm",
"prepare": "husky install"
"prepare": "husky"
},
"dependencies": {
"cheerio": "^1.0.0-rc.12",
"cheerio": "^1.0.0",
"clsx": "^2.1.1",
"date-fns": "^2.30.0",
"framer-motion": "^10.16.4",
Expand Down
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

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

13 changes: 13 additions & 0 deletions src/content/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Trigger } from './components/Trigger'
import { ContentThemeProvider } from '@/components/ContentThemeProvider'
import { StorageProvider } from '@/context/storageContext'

export function App() {
return (
<ContentThemeProvider>
<StorageProvider>
<Trigger />
</StorageProvider>
</ContentThemeProvider>
)
}
17 changes: 11 additions & 6 deletions src/content/components/Trigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,24 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { SettingsContent } from './setting'
import { Navigation } from '@/content/components/Navigation'
import { TaskContent } from '@/content/components/task'
import { useSettingsStore } from '@/hooks/useSettingsStore'
import { useStorage } from '@/context/storageContext'

export function Trigger() {
const [isOpen, setIsOpen] = useState(false)
const [activeTab, setActiveTab] = useState<'tasks' | 'settings'>('tasks')
const [settings] = useSettingsStore()
const {
data: { settings },
isLoading,
} = useStorage()

useHotkeys('ctrl+/, meta+/', () => {
setIsOpen(prev => !prev)
})

if (isLoading) {
return null
}

return (
<>
<motion.div
Expand All @@ -24,11 +31,9 @@ export function Trigger() {
exit={{ opacity: 0 }}
onClick={() => setIsOpen(prev => !prev)}
className={
'd-mask d-mask-squircle fixed bottom-25px right-25px z-[9999] h-56px w-56px cursor-pointer bg-cover bg-repeat'
'd-mask d-mask-squircle fixed bottom-25px right-25px h-56px w-56px cursor-pointer bg-cover bg-repeat'
}
style={{
backgroundImage: `url(${settings.triggerImage})`,
}}
style={{ backgroundImage: `url(${settings.triggerImage})` }}
/>

<AnimatePresence>
Expand Down
2 changes: 1 addition & 1 deletion src/content/components/setting/ImageCropModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function ImageCropModal({ image, onComplete, onClose }: ImageCropModalPro
}, [croppedAreaPixels, image, onComplete])

return (
<div className="z-100 fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
<div className="flex h-400px w-300px flex-col gap-12px rounded-12px bg-white p-12px">
<div className="relative h-full w-full overflow-hidden rounded-12px">
<div className="absolute inset-0 bg-white" />
Expand Down
27 changes: 17 additions & 10 deletions src/content/components/setting/SettingsContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { useDropzone } from 'react-dropzone'
import { ImageCropModal } from './ImageCropModal'
import { SettingItem } from './SettingItem'
import packageJson from '../../../../package.json'
import { useSettingsStore } from '@/hooks/useSettingsStore'
import { setStorageData } from '@/lib/chromeStorage'
import { useStorage } from '@/context/storageContext'

const { version } = packageJson

Expand All @@ -19,8 +18,17 @@ const refreshIntervalOptions = [
]

export function SettingsContent() {
const [settings] = useSettingsStore()
// const {
// storage: { settings },
// setSettings,
// isInitialStateResolved,
// } = useStorageStore()

const {
data: { settings },
updateData,
isLoading,
} = useStorage()
const [image, setImage] = useState<string | null>(null)
const [isCropModalOpen, setIsCropModalOpen] = useState(false)
const [isHovering, setIsHovering] = useState(false)
Expand All @@ -44,22 +52,19 @@ export function SettingsContent() {

const handleCropComplete = useCallback(
async (croppedImage: string) => {
await setStorageData('settings', {
...settings,
triggerImage: croppedImage,
})
updateData({ settings: { ...settings, triggerImage: croppedImage } })
setIsCropModalOpen(false)
},
[settings],
)

const handleRefreshIntervalChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newRefreshInterval = Number(event.target.value)
setStorageData('settings', { ...settings, refreshInterval: newRefreshInterval })
updateData({ settings: { ...settings, refreshInterval: newRefreshInterval } })
}

return (
<div {...getRootProps()} className="relative flex-1 space-y-12px overflow-y-auto px-12px py-20px">
<div {...getRootProps()} className="relative flex flex-1 flex-col gap-12px overflow-y-auto px-12px py-20px">
<input {...getInputProps()} ref={inputRef} />
{isDragActive && (
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white bg-opacity-80 backdrop-blur-sm">
Expand Down Expand Up @@ -104,7 +109,9 @@ export function SettingsContent() {
onMouseEnter={() => setIsHovering(true)}
onMouseLeave={() => setIsHovering(false)}
>
<img src={settings.triggerImage} alt="버튼 이미지 미리보기" className="h-full w-full object-cover" />
{!isLoading && (
<img src={settings.triggerImage} alt="버튼 이미지 미리보기" className="h-full w-full object-cover" />
)}
{isHovering && (
<div
className="absolute inset-0 flex cursor-pointer items-center justify-center bg-black bg-opacity-50 transition-opacity duration-200"
Expand Down
15 changes: 10 additions & 5 deletions src/content/components/task/TaskContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ import { AnimatedRefreshButton } from './AnimatedRefreshButton'
import { LoadingSkeleton } from './LoadingSkeleton'
import { TabNavigation } from './TabNavigation'
import { TaskList } from './TaskList'
import { useStorage } from '@/context/storageContext'
import { contentsData } from '@/data/dummyData'
import { useContents } from '@/hooks/useContents'
import useFilteredActivityList from '@/hooks/useFilteredActivityList'
import useGetContents from '@/hooks/useGetContents'

const isDevelopment = process.env.NODE_ENV === 'development'

export function TaskContent() {
const [taskTab, setTaskTab] = useState<'ongoing' | 'all'>('ongoing')
const scrollRef = useRef<HTMLDivElement>(null)
const { data, pos, isLoading, refetch } = useGetContents({ enabled: !isDevelopment })

const contentData = isDevelopment ? contentsData : data
const { contents, progress, isLoading, refetch } = useContents()

const {
data: { meta },
} = useStorage()
const contentData = isDevelopment ? contentsData : contents

const filteredTasks = useFilteredActivityList(contentData.activityList, '-1', taskTab === 'ongoing' ? 0 : 1, false)

const formattedUpdateTime = formatDistanceToNowStrict(new Date(contentData.updateAt), { addSuffix: true, locale: ko })
const formattedUpdateTime = formatDistanceToNowStrict(new Date(meta.updateAt), { addSuffix: true, locale: ko })

useEffect(() => {
scrollRef.current.scrollTo({ top: 0, behavior: 'smooth' })
Expand All @@ -45,7 +50,7 @@ export function TaskContent() {
<div className="absolute inset-x-0 top-0 z-10 h-16px bg-gradient-to-b from-slate-100 to-transparent"></div>
<div className="absolute inset-x-0 bottom-0 z-10 h-16px bg-gradient-to-t from-slate-100 to-transparent"></div>
<div ref={scrollRef} className="h-full overflow-y-auto px-16px py-20px">
{isLoading ? <LoadingSkeleton progress={pos} /> : <TaskList tasks={filteredTasks} />}
{isLoading ? <LoadingSkeleton progress={progress} /> : <TaskList tasks={filteredTasks} />}
</div>
</div>
</>
Expand Down
9 changes: 2 additions & 7 deletions src/content/index.dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,12 @@ import cropperStyles from 'react-easy-crop/react-easy-crop.css?inline'

import '@/styles/index.css'

import { Trigger } from './components/Trigger'
import { ContentThemeProvider } from '@/components/ContentThemeProvider'
import { App } from './App'
import createShadowRoot from '@/utils/createShadowRoot'

// remove scroll to top button
document.getElementById('back-top')?.remove()

const root = createShadowRoot([cropperStyles])

root.render(
<ContentThemeProvider>
<Trigger />
</ContentThemeProvider>,
)
root.render(<App />)
9 changes: 2 additions & 7 deletions src/content/index.prod.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import cropperStyles from 'react-easy-crop/react-easy-crop.css?inline'

import { Trigger } from './components/Trigger'
import { ContentThemeProvider } from '@/components/ContentThemeProvider'
import { App } from './App'
import styles from '@/styles/index.css?inline'
import createShadowRoot from '@/utils/createShadowRoot'

Expand All @@ -10,8 +9,4 @@ document.getElementById('back-top')?.remove()

const root = createShadowRoot([styles, cropperStyles])

root.render(
<ContentThemeProvider>
<Trigger />
</ContentThemeProvider>,
)
root.render(<App />)
72 changes: 72 additions & 0 deletions src/context/storageContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React, { createContext, useContext, useState, useCallback } from 'react'
import { createChromeStorageStateHookLocal } from 'use-chrome-storage'

import packageJson from '../../package.json'
import type { StorageData } from '@/types'

const STORAGE_KEY = 'data'

const DEFAULT_STORAGE_DATA: StorageData['data'] = {
meta: {
version: packageJson.version,
updateAt: new Date().toISOString(),
},
contents: {
courseList: [{ id: '-1', title: '전체' }],
activityList: [],
},
settings: {
refreshInterval: 1000 * 60 * 20, // 20 minutes
triggerImage: chrome.runtime.getURL('/assets/Lee-Gil-ya.webp'),
},
}

interface StorageContextType {
data: StorageData['data'] | null
isLoading: boolean
updateData: (newData: Partial<StorageData['data']>) => void
}

const StorageContext = createContext<StorageContextType | undefined>(undefined)

export function StorageProvider({ children }: { children: React.ReactNode }) {
const [storageHook] = useState(() => createChromeStorageStateHookLocal(STORAGE_KEY, DEFAULT_STORAGE_DATA))
const [storage, setStorage, , , isInitialStateResolved] = storageHook()

const updateData = useCallback(
(newData: Partial<StorageData['data']>) => {
setStorage(prevData => ({
...prevData,
settings: {
...prevData.settings,
...newData.settings,
},
contents: {
...prevData.contents,
...newData.contents,
},
meta: {
...prevData.meta,
...newData.meta,
},
}))
},
[setStorage],
)

const value = {
data: storage,
isLoading: !isInitialStateResolved,
updateData,
}

return <StorageContext.Provider value={value}>{children}</StorageContext.Provider>
}

export function useStorage() {
const context = useContext(StorageContext)
if (context === undefined) {
throw new Error('useStorage must be used within a StorageProvider')
}
return context
}
1 change: 0 additions & 1 deletion src/data/dummyData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -622,5 +622,4 @@ export const contentsData: Contents = {
title: '고급웹프로그래밍',
},
],
updateAt: '2023-10-24T11:24:04.212Z',
}
26 changes: 26 additions & 0 deletions src/hooks/useContents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect } from 'react'

import { useContentsFetcher } from './useContentsFetcher'
import { useRefreshCheck } from './useRefreshCheck'
import { useStorage } from '@/context/storageContext'

export const useContents = () => {
const {
data: { contents },
} = useStorage()
const { shouldRefresh } = useRefreshCheck()
const { fetchContents, isLoading, progress } = useContentsFetcher()

useEffect(() => {
if (shouldRefresh || !contents.courseList.length) {
fetchContents()
}
}, [shouldRefresh, contents.courseList.length])

return {
contents,
isLoading,
progress,
refetch: fetchContents,
}
}
42 changes: 42 additions & 0 deletions src/hooks/useContentsFetcher.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useState } from 'react'

import { useStorage } from '@/context/storageContext'
import { getActivities, getAssignmentSubmitted, getCourses, getVideoSubmitted } from '@/services'

export const useContentsFetcher = () => {
const [isLoading, setIsLoading] = useState(false)
const [progress, setProgress] = useState(0)
const { data, updateData } = useStorage()

const fetchContents = async () => {
if (isLoading) {
return
}

setIsLoading(true)
setProgress(0)

const courseList = await getCourses()
const maxPos = courseList.length * 2

const activityList = await Promise.all(
courseList.map(async (course, index) => {
const assignmentSubmittedArray = await getAssignmentSubmitted(course.id)
setProgress(((index * 2 + 1) / maxPos) * 100)
const videoSubmittedArray = await getVideoSubmitted(course.id)
setProgress(((index * 2 + 2) / maxPos) * 100)
return getActivities(course.title, course.id, assignmentSubmittedArray, videoSubmittedArray)
}),
).then(activityList => activityList.flat())

updateData({
contents: { courseList: [{ id: '-1', title: '전체' }, ...courseList], activityList },
meta: { ...data.meta, updateAt: new Date().toISOString() },
})

setIsLoading(false)
setProgress(0)
}

return { fetchContents, isLoading, progress }
}
Loading

0 comments on commit 1be2762

Please sign in to comment.