Skip to content

Commit

Permalink
feat: implement feature to rename items
Browse files Browse the repository at this point in the history
  • Loading branch information
mslxl committed Mar 19, 2024
1 parent f0d3c8f commit ced112e
Show file tree
Hide file tree
Showing 6 changed files with 248 additions and 37 deletions.
7 changes: 4 additions & 3 deletions src/components/session/SessionContextMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { deleteSourceAtom, duplicateSourceAtom } from "@/store/source"
interface SessionContextMenuProps {
id: string
children: ReactNode
onRename: (id: string) => void
onChangeLanguage: (id: string) => void
}
export default function SessionContextMenu(props: SessionContextMenuProps) {
Expand All @@ -27,9 +28,9 @@ export default function SessionContextMenu(props: SessionContextMenuProps) {
<ContextMenuItem onClick={() => duplicateSource(props.id, (name) => `${name} - Copy`)}>
Duplicate
</ContextMenuItem>
<ContextMenuItem onClick={() => props.onChangeLanguage(props.id)}>
Change Language
</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem onClick={() => props.onRename(props.id)}>Rename</ContextMenuItem>
<ContextMenuItem onClick={() => props.onChangeLanguage(props.id)}>Change Language</ContextMenuItem>
<ContextMenuSeparator />
<ContextMenuItem>Reopen with...</ContextMenuItem>
</ContextMenuContent>
Expand Down
61 changes: 49 additions & 12 deletions src/components/session/index.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { activedSourceIdAtom, deleteSourceAtom, sourceAtom, sourceIdsAtom } from "@/store/source"
import { activedSourceIdAtom, createSourceAtom, deleteSourceAtom, sourceAtom, sourceIdsAtom } from "@/store/source"
import clsx from "clsx"
import { useAtom, useAtomValue, useSetAtom } from "jotai"
import { isEmpty } from "lodash/fp"
import { VscClose, VscFile } from "react-icons/vsc"
import { VscClose, VscFile, VscNewFile } from "react-icons/vsc"
import SessionContextMenu from "./SessionContextMenu"
import useChangeLanguageDialog from "@/hooks/useChangeLanguageDialog"

import * as log from 'tauri-plugin-log-api'
import * as log from "tauri-plugin-log-api"
import { defaultLanguageAtom, defaultMemoryLimitsAtom, defaultTimeLimitsAtom } from "@/store/setting/setup"
import { useHoverDirty } from "react-use"
import { useRef } from "react"
import { useRenameDialog } from "@/hooks/useRenameDialog"

interface FileItemProps {
id: string
Expand All @@ -28,7 +32,7 @@ function FileItem(props: FileItemProps) {
onClick={() => props.onClick(props.id)}
>
<VscFile className="mx-1" />
<span className="flex-1">{nameDisplay}</span>
<span className="flex-1 truncate">{nameDisplay}</span>
<button onClick={() => props.onRemove(props.id)}>
<VscClose></VscClose>
</button>
Expand All @@ -44,33 +48,66 @@ export default function SessionPanel(props: SessionPanelProps) {
const [activedSourceId, setActivedSourceId] = useAtom(activedSourceIdAtom)
const removeSource = useSetAtom(deleteSourceAtom)
const sourceStore = useAtomValue(sourceAtom)
const createSource = useSetAtom(createSourceAtom)
const defaultLanguage = useAtomValue(defaultLanguageAtom)
const defaultTimeLimit = useAtomValue(defaultTimeLimitsAtom)
const defaultMemoryLimit = useAtomValue(defaultMemoryLimitsAtom)

const [dialogChangeLanguage, showChangeLanguageDialog] = useChangeLanguageDialog()
const [dialogChangeLanguageElem, showChangeLanguageDialog] = useChangeLanguageDialog()
const [renameElem, showRenameDialog] = useRenameDialog()

const sessionPanelRef = useRef<HTMLDivElement>(null)
const isHover = useHoverDirty(sessionPanelRef)

async function changeLanguage(id: string){
async function changeLanguage(id: string) {
const src = sourceStore.get(id)
if(!src) return
if (!src) return
const newLanguage = await showChangeLanguageDialog(src.language)
if(newLanguage){
if (newLanguage) {
log.info(`change lang of ${id} to ${newLanguage}`)
src.language = newLanguage
}
}
async function renameSource(id: string) {
const src = sourceStore.get(id)
if (!src) return
const newName = await showRenameDialog(src.name.toString())
if (newName) {
log.info(`rename ${id} to ${newName}`)
src.name.delete(0, src.name.length)
src.name.insert(0, newName)
}
}

const filesList = sourceIds.map((v) => (
<li key={v}>
<SessionContextMenu id={v} onChangeLanguage={changeLanguage}>
<SessionContextMenu id={v} onChangeLanguage={changeLanguage} onRename={renameSource}>
<FileItem id={v} onClick={setActivedSourceId} actived={activedSourceId == v} onRemove={removeSource} />
</SessionContextMenu>
</li>
))

function newFile() {
createSource(defaultLanguage, defaultTimeLimit, defaultMemoryLimit)
}

return (
<>
{dialogChangeLanguage}
<div className={clsx(props.className, "h-full select-none flex flex-col min-h-0 min-w-0")}>
<ul className="overflow-auto divide-transparent">{filesList}</ul>
{dialogChangeLanguageElem}
{renameElem}
<div className={clsx(props.className, "h-full select-none flex flex-col min-h-0 min-w-0")} ref={sessionPanelRef}>
<ul className="overflow-auto divide-transparent">
<li className="sticky top-0 bg-accent">
<div className="shadow-sm flex pl-2">
<span className="truncate font-semibold">FILES</span>
<span className="flex-1"></span>
<button className={clsx("p-1 hover:bg-neutral-200", { hidden: !isHover })} onClick={newFile}>
<VscNewFile />
</button>
</div>
</li>
{filesList}
</ul>
</div>
</>
)
Expand Down
59 changes: 40 additions & 19 deletions src/hooks/useRenameDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,47 +1,68 @@
import { FaRandom } from "react-icons/fa"
import { Button } from "@/components/ui/button"
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { ReactNode, useState } from "react"
export function useRenameDialog(
callback: (newValue: string) => void,
): [ReactNode, (defaultValue: string) => void, boolean] {
import { ReactNode, useRef, useState } from "react"
import generateRandomName from "@/lib/names"
export function useRenameDialog(): [ReactNode, (defaultValue: string) => Promise<string | undefined>] {
const [renameDialogOpen, setRenameDialogOpen] = useState(false)
const [originTabName, setOriginTabName] = useState("")
const [targetTabName, setTargetName] = useState("")
function showDialog(defaultValue: string) {
setOriginTabName(defaultValue)
const [currentValue, setCurrentValue] = useState("")
const callback = useRef<(value: string | undefined) => void>()

function showDialog(defaultValue: string): Promise<string | undefined> {
setCurrentValue(defaultValue)
setRenameDialogOpen(true)
return new Promise((resolve) => {
callback.current = resolve
})
}

function cancel() {
setRenameDialogOpen(false)
if (callback.current) {
callback.current(undefined)
callback.current = undefined
}
}
function confirm() {
setRenameDialogOpen(false)
if (callback.current) {
callback.current(currentValue)
callback.current = undefined
}
}
function randomName() {
setCurrentValue(generateRandomName("'s code"))
}

let element = (
<Dialog open={renameDialogOpen} onOpenChange={setRenameDialogOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Rename {originTabName}</DialogTitle>
<DialogTitle>Rename File</DialogTitle>
</DialogHeader>
<div className="grid flex-1 gap-2">
<Label htmlFor="name" className="sr-only">
New Name
</Label>
<Input defaultValue={originTabName} onChange={(e) => setTargetName(e.target.value)} />
<div className="flex gap-1">
<Input className="flex-1" value={currentValue} onChange={(e) => setCurrentValue(e.target.value)} />
<Button size="icon" variant="ghost" onClick={randomName}>
<FaRandom />
</Button>
</div>
<span className="text-end">
<Button className="m-2" variant="outline" onClick={() => setRenameDialogOpen(false)}>
<Button className="m-2" variant="outline" onClick={cancel}>
Cancel
</Button>
<Button
className="m-2"
onClick={() => {
callback(targetTabName)
setRenameDialogOpen(false)
}}
>
<Button className="m-2" onClick={confirm}>
Rename
</Button>
</span>
</div>
</DialogContent>
</Dialog>
)
return [element, showDialog, renameDialogOpen]
return [element, showDialog]
}
146 changes: 146 additions & 0 deletions src/lib/names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { capitalize, random } from "lodash/fp"

export default function generateRandomName(suffix?: string){
const adj = random(0, adjectives.length)
const n = random(0, nouns.length)
return capitalize(`${adjectives[adj]} ${nouns[n]}${suffix}`.trim())
}


// Cop from https://github.com/zellij-org/zellij/blob/65a7fcf426e6131fe68b300fb746271419a08865/src/sessions.rs#L426-L560
// Thanks for the organization's fantastic work
const adjectives = [
"adamant",
"adept",
"adventurous",
"arcadian",
"auspicious",
"awesome",
"blossoming",
"brave",
"charming",
"chatty",
"circular",
"considerate",
"cubic",
"curious",
"delighted",
"didactic",
"diligent",
"effulgent",
"erudite",
"excellent",
"exquisite",
"fabulous",
"fascinating",
"friendly",
"glowing",
"gracious",
"gregarious",
"hopeful",
"implacable",
"inventive",
"joyous",
"judicious",
"jumping",
"kind",
"likable",
"loyal",
"lucky",
"marvellous",
"mellifluous",
"nautical",
"oblong",
"outstanding",
"polished",
"polite",
"profound",
"quadratic",
"quiet",
"rectangular",
"remarkable",
"rusty",
"sensible",
"sincere",
"sparkling",
"splendid",
"stellar",
"tenacious",
"tremendous",
"triangular",
"undulating",
"unflappable",
"unique",
"verdant",
"vitreous",
"wise",
"zippy",
]

const nouns = [
"aardvark",
"accordion",
"apple",
"apricot",
"bee",
"brachiosaur",
"cactus",
"capsicum",
"clarinet",
"cowbell",
"crab",
"cuckoo",
"cymbal",
"diplodocus",
"donkey",
"drum",
"duck",
"echidna",
"elephant",
"foxglove",
"galaxy",
"glockenspiel",
"goose",
"hill",
"horse",
"iguanadon",
"jellyfish",
"kangaroo",
"lake",
"lemon",
"lemur",
"magpie",
"megalodon",
"mountain",
"mouse",
"muskrat",
"newt",
"oboe",
"ocelot",
"orange",
"panda",
"peach",
"pepper",
"petunia",
"pheasant",
"piano",
"pigeon",
"platypus",
"quasar",
"rhinoceros",
"river",
"rustacean",
"salamander",
"sitar",
"stegosaurus",
"tambourine",
"tiger",
"tomato",
"triceratops",
"ukulele",
"viola",
"weasel",
"xylophone",
"yak",
"zebra",
]
3 changes: 1 addition & 2 deletions src/pages/Main/event/menu-event.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ export default function MenuEventReceiver() {
"fileMenu",
async (event) => {
if (event == "new") {
const src = createSource(defaultLanguage, defaultTimeLimit, defaultMemoryLimit)
src.name.insert(0, "Unamed")
createSource(defaultLanguage, defaultTimeLimit, defaultMemoryLimit)
} else if (event == "open") {
openFile(sourceStore, setSourceMeta)
} else if (event == "save" || event == "saveAs") {
Expand Down
9 changes: 8 additions & 1 deletion src/store/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { LanguageMode } from "@/lib/ipc"
import { createYjsHookAtom } from "@/hooks/useY"
import cache from "@/lib/fs/cache"
import { fromSource } from "@/lib/fs/model"
import generateRandomName from "@/lib/names"

export const docAtom = atom(new Doc())
export const sourceAtom = atom((get) => new SourceStore(get(docAtom)))
Expand Down Expand Up @@ -67,13 +68,19 @@ export const activedSourceAtom = atom((get) => {
*/
export const createSourceAtom = atom(
null,
(get, _, targetLanguage: LanguageMode, defaultTimeLimits: number, defaultMemoryLimits: number) => {
(get, _, targetLanguage: LanguageMode, defaultTimeLimits: number, defaultMemoryLimits: number, name?: string) => {
const store = get(sourceAtom)
const [source, id] = store.create()
store.doc.transact(() => {
source.language = targetLanguage
source.timelimit = defaultTimeLimits
source.memorylimit = defaultMemoryLimits
if(name){
source.name.insert(0, name)
}else{
const name = generateRandomName("'s code")
source.name.insert(0, name)
}
log.info(`create new source: ${id}`)
log.info(JSON.stringify(get(sourceIdsAtom)))
})
Expand Down

0 comments on commit ced112e

Please sign in to comment.