Skip to content

Commit

Permalink
feat(fe): add code manually save button (#2095)
Browse files Browse the repository at this point in the history
* feat(fe): add code manually save button

* feat(fe): modify localStorage key

* fix(fe): fix to localstorage

* chore(fe): fix toast message

* fix(fe): remove code context

* chore(fe): fix code based on review

* chore(fe): add useEffect dependency
  • Loading branch information
Kohminchae authored Oct 5, 2024
1 parent ff0d201 commit 9eb1887
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 38 deletions.
72 changes: 66 additions & 6 deletions apps/frontend/components/EditorHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,25 @@ import { auth } from '@/lib/auth'
import { fetcherWithAuth } from '@/lib/utils'
import submitIcon from '@/public/submit.svg'
import useAuthModalStore from '@/stores/authModal'
import { CodeContext, useLanguageStore } from '@/stores/editor'
import {
useLanguageStore,
getKey,
setItem,
getItem,
CodeContext
} from '@/stores/editor'
import type {
Language,
ProblemDetail,
Submission,
Template
} from '@/types/type'
import JSConfetti from 'js-confetti'
import { Save } from 'lucide-react'
import type { Route } from 'next'
import Image from 'next/image'
import { useRouter } from 'next/navigation'
import { useContext, useEffect, useState } from 'react'
import { useContext, useEffect, useRef, useState } from 'react'
import { BsTrash3 } from 'react-icons/bs'
//import { IoPlayCircleOutline } from 'react-icons/io5'
import { useInterval } from 'react-use'
Expand All @@ -60,8 +67,12 @@ export default function Editor({
const [loading, setLoading] = useState(false)
const [submissionId, setSubmissionId] = useState<number | null>(null)
const [templateCode, setTemplateCode] = useState<string | null>(null)
const [userName, setUserName] = useState('')
const router = useRouter()
const confetti = typeof window !== 'undefined' ? new JSConfetti() : null
const storageKey = useRef(getKey(language, problem.id, userName, contestId))
const { currentModal, showSignIn } = useAuthModalStore((state) => state)

useInterval(
async () => {
const res = await fetcherWithAuth(`submission/${submissionId}`, {
Expand Down Expand Up @@ -90,14 +101,15 @@ export default function Editor({
loading && submissionId ? 500 : null
)

const { showSignIn } = useAuthModalStore((state) => state)
useEffect(() => {
auth().then((session) => {
if (!session) {
toast.info('Log in to use submission & auto save feature')
toast.info('Log in to use submission & save feature')
} else {
setUserName(session.user.username)
}
})
}, [])
}, [currentModal])

useEffect(() => {
if (!templateString) return
Expand All @@ -109,6 +121,11 @@ export default function Editor({
setTemplateCode(filteredTemplate[0].code[0].text)
}, [language])

useEffect(() => {
storageKey.current = getKey(language, problem.id, userName, contestId)
getLocalstorageCode()
}, [userName, problem, contestId, language, templateCode])

const submit = async () => {
if (code === '') {
toast.error('Please write code before submission')
Expand Down Expand Up @@ -136,6 +153,7 @@ export default function Editor({
}
})
if (res.ok) {
saveCode()
const submission: Submission = await res.json()
setSubmissionId(submission.id)
} else {
Expand All @@ -147,6 +165,40 @@ export default function Editor({
}
}

const saveCode = async () => {
const session = await auth()
if (!session) {
toast.error('Log in first to save your code')
} else {
if (storeCodeToLocalstorage())
toast.success('Successfully saved the code')
else toast.error('Failed to save the code')
}
}

const storeCodeToLocalstorage = () => {
if (storageKey.current !== undefined) {
setItem(storageKey.current, code)
return true
}
return false
}

const getLocalstorageCode = () => {
if (storageKey.current !== undefined) {
const storedCode = getItem(storageKey.current) ?? ''
setCode(storedCode ? JSON.parse(storedCode) : templateCode)
}
}

const resetCode = () => {
if (storageKey.current !== undefined) {
setItem(storageKey.current, templateCode ?? '')
setCode(templateCode ?? '')
toast.success('Successfully reset the code')
} else toast.error('Failed to reset the code')
}

return (
<div className="flex shrink-0 items-center justify-between border-b border-b-slate-700 bg-[#222939] px-6">
<div>
Expand All @@ -173,7 +225,7 @@ export default function Editor({
<AlertDialogCancel>Cancel</AlertDialogCancel>
<AlertDialogAction
className="bg-red-500 hover:bg-red-600"
onClick={() => setCode(templateCode ?? '')}
onClick={resetCode}
>
Reset
</AlertDialogAction>
Expand All @@ -182,6 +234,14 @@ export default function Editor({
</AlertDialog>
</div>
<div className="flex items-center gap-3">
<Button
size="icon"
className="size-7 h-8 w-[77px] shrink-0 gap-[5px] rounded-[4px] bg-[#D7E5FE] font-medium text-[#484C4D] hover:bg-[#c6d3ea]"
onClick={saveCode}
>
<Save className="stroke-1" size={22} />
Save
</Button>
{/* TODO: Add Test function
<Button
Expand Down
17 changes: 2 additions & 15 deletions apps/frontend/components/EditorResizablePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { ScrollArea } from '@/components/ui/scroll-area'
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { CodeContext, createCodeStore, useLanguageStore } from '@/stores/editor'
import type { Language, ProblemDetail, Template } from '@/types/type'
import type { Language, ProblemDetail } from '@/types/type'
import type { Route } from 'next'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
Expand All @@ -34,7 +34,7 @@ export default function EditorMainResizablePanel({
const pathname = usePathname()
const base = contestId ? `/contest/${contestId}` : ''
const { language, setLanguage } = useLanguageStore()
const store = createCodeStore(language, problem.id, contestId)
const store = createCodeStore()
useEffect(() => {
if (!problem.languages.includes(language)) {
setLanguage(problem.languages[0])
Expand Down Expand Up @@ -114,26 +114,13 @@ interface CodeEditorInEditorResizablePanelProps {
}

function CodeEditorInEditorResizablePanel({
templateString,
enableCopyPaste
}: CodeEditorInEditorResizablePanelProps) {
const { language } = useLanguageStore()
const store = useContext(CodeContext)
if (!store) throw new Error('CodeContext is not provided')
const { code, setCode } = useStore(store)

useEffect(() => {
if (!templateString) return
const parsedTemplates = JSON.parse(templateString)
const filteredTemplate = parsedTemplates.filter(
(template: Template) => template.language === language
)
if (!code) {
if (filteredTemplate.length === 0) return
setCode(filteredTemplate[0].code[0].text)
}
}, [language])

return (
<CodeEditor
value={code}
Expand Down
6 changes: 5 additions & 1 deletion apps/frontend/components/auth/SignIn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ interface Inputs {
export default function SignIn() {
const [disableButton, setDisableButton] = useState(false)
const [passwordShow, setPasswordShow] = useState<boolean>(false)
const { showSignUp, showRecoverAccount } = useAuthModalStore((state) => state)
const { hideModal, showSignUp, showRecoverAccount } = useAuthModalStore(
(state) => state
)
const router = useRouter()
const { register, handleSubmit, watch } = useForm<Inputs>()
const onSubmit: SubmitHandler<Inputs> = async (data) => {
Expand All @@ -40,6 +42,7 @@ export default function SignIn() {

if (!res?.error) {
router.refresh()
hideModal()
toast.success(`Welcome back, ${data.username}!`)
} else {
toast.error('Failed to log in')
Expand All @@ -51,6 +54,7 @@ export default function SignIn() {
setDisableButton(false)
}
}

return (
<div className="flex h-full w-full flex-col justify-between">
<div className="flex justify-center pt-4">
Expand Down
42 changes: 26 additions & 16 deletions apps/frontend/stores/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,42 @@ export const useLanguageStore = create(
}
)
)
type CodeStore = ReturnType<typeof createCodeStore>
interface CodeState {
code: string
setCode: (code: string) => void
}

type CodeStore = ReturnType<typeof createCodeStore>
export const createCodeStore = () => {
return createStore<CodeState>()((set) => ({
code: '',
setCode: (code) => {
set({ code })
}
}))
}

export const createCodeStore = (
export const getKey = (
language: Language,
problemId: number,
userName: string,
contestId?: number
) => {
const problemKey = `${problemId}${contestId ? `_${contestId}` : ''}_${language}`
return createStore<CodeState>()(
persist<CodeState>(
(set) => ({
code: '',
setCode: (code) => {
set({ code })
}
}),
{
name: problemKey
}
)
)
if (userName === '') return undefined
const problemKey = `${userName}_${problemId}${contestId ? `_${contestId}` : ''}_${language}`
return problemKey
}

export const getItem = (name: string) => {
const str = localStorage.getItem(name)
if (!str) return null
return str
}

export const setItem = (name: string, value: string) => {
localStorage.setItem(name, JSON.stringify(value))
}

export const removeItem = (name: string) => localStorage.removeItem(name)

export const CodeContext = createContext<CodeStore | null>(null)

0 comments on commit 9eb1887

Please sign in to comment.