heartb-web-oa-master/app/page.tsx

233 lines
8.7 KiB
TypeScript

"use client";
import { Snippet } from "@nextui-org/snippet";
import { Code } from "@nextui-org/code";
import { Icon } from "@iconify/react";
import {
Button,
ScrollShadow,
Select,
SelectItem,
} from "@nextui-org/react";
import { cn } from "@nextui-org/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { title } from "@/components/primitives";
import { ChapterItem, Chapters, readFile, splitChapter } from "./utils/chapter";
import { ChapterCard } from "@/components/ChapterCard";
export default function Home() {
const [text,setText] = useState("")
const [chapters,setChapters] = useState<Chapters>([])
const [activeChapter,setActiveChapter] = useState<number|null>(null)
const textareaRef = useRef<HTMLTextAreaElement|null>(null)
const onSplit = useCallback(async ()=>{
const dom = textareaRef.current
if(!dom) return
const index = dom.selectionStart
if(index<0) return
console.log("bookmark",index)
const content = '====SPLIT CHAPTER===='
setText(txt=>{
return txt.substring(0,index)+'\n'+content+"\n"+txt.substring(15)
})
},[])
const onMergeNext = useCallback((chapter:ChapterItem)=>{
const current = chapters.findIndex(detail=>detail.key===chapter.key)
if(current==-1) return
const next = current+1
const nextChapter = chapters[next]
if(!nextChapter) return
chapter.text += ["",nextChapter.title,nextChapter.text].join("\n")
const newChapters = chapters.slice()
newChapters.splice(next,1)
const newText = newChapters.reduce<string>((txt,chapter)=>{
txt += chapter.rawTitle+'\n'
txt += chapter.text+"\n"
return txt
},"")
setText(newText)
},[chapters])
const selectFile = useCallback(()=>{
const input = document.createElement("input")
input.type = 'file'
input.accept = '.txt'
input.addEventListener("change",async function(event){
setActiveChapter(null)
const [file] = input.files||[]
if(!file) {
setText("")
return
}
console.time("read-txt")
setText(await readFile(file))
console.timeEnd("read-txt")
})
input.click()
},[])
useEffect(()=>{
console.time("split-chapter")
const chapters = splitChapter(text)
setChapters(chapters)
if(chapters.length>0) {
setActiveChapter(1)
}
console.timeEnd("split-chapter")
},[text])
return (
<section className="flex flex-col items-center justify-center gap-4 py-8 md:py-10">
{/* <div className="flex flex-row pt-6 w-48"> */}
<div className="flex flex-row pt-6 w-auto">
<Select
className="flex-auto"
items={[
{ key: "story-1", label: "story-1" },
{ key: "story-2", label: "story-2" },
{ key: "story-3", label: "story-3" },
]}
label="Story"
placeholder="Select a story"
>
{(story) => <SelectItem key={story.key}>{story.label}</SelectItem>}
</Select>
<Button className="flex-auto w-auto" onPress={selectFile}>Select</Button>
</div>
<div className="pt-6">
<div className="flex flex-row ">
<div
className={cn(
"relative flex h-full w-96 max-w-[384px] flex-1 flex-col !border-r-small border-divider pr-6 transition-[transform,opacity,margin] duration-250 ease-in-out",
)}
id="menu"
>
<header className="flex items-center text-md font-medium text-default-500 group-data-[selected=true]:text-foreground">
<Icon
className="text-default-500 mr-2"
icon="solar:clipboard-text-outline"
width={24}
/>
Chapters
</header>
<ScrollShadow
className="max-h-[calc(500px)] -mr-4"
id="menu-scroll"
>
<div className="flex flex-col gap-4 py-3 pr-4">
{
chapters.map((chapter,index)=>{
return <ChapterCard
chapter={chapter}
active={activeChapter===chapter.number}
key={`chapter-${chapter.key}`}
onPress={async ()=>{
setActiveChapter(chapter.number)
if(!textareaRef.current) return
textareaRef.current.focus()
await new Promise<void>(resolve=>{
requestAnimationFrame(()=>{
resolve()
})
})
textareaRef.current.selectionStart = chapter.indexOf
textareaRef.current.selectionEnd = chapter.indexOf
}}
onMergeNext={index===chapters.length-1?undefined:onMergeNext}
/>
})
}
</div>
</ScrollShadow>
</div>
<div className="w-full flex-1 flex-col min-w-[600px] pl-4">
<div className="flex flex-col">
<header className="flex items-center justify-between pb-2">
<div className="flex items-center gap-3">
<Button isIconOnly size="sm" variant="light">
<Icon
className="hideTooltip text-default-500"
height={24}
icon="solar:sidebar-minimalistic-outline"
width={24}
/>
</Button>
<h4 className="text-md">Chapter 1 - Chapter 1 title</h4>
</div>
</header>
<div className="w-full flex-1 flex-col min-w-[400px]">
<div className={cn("flex flex-col gap-4")}>
<div className="flex flex-col items-start">
<div className="relative mb-5 w-full h-[400px] bg-slate-50 dark:bg-gray-800 rounded-lg">
<div className="absolute inset-x-4 top-4 z-10 flex justify-between items-center">
<div className="flex justify-between">
<Button
className="mr-2 bg-white dark:bg-gray-700"
size="sm"
startContent={
<Icon
className="text-default-500"
icon="ant-design:highlight-outlined"
width={24}
/>
}
variant="flat"
>
button-1
</Button>
</div>
<Button
className="mr-2 bg-white dark:bg-gray-700"
size="sm"
startContent={
<Icon
className="text-default-500"
icon="material-symbols:save-outline"
width={24}
/>
}
variant="flat"
onPress={onSplit}
>
Split
</Button>
</div>
<div>
<ScrollShadow className="editScrollShow absolute left-2 right-2 bottom-10 top-12 text-base p-3 resize-none rounded-md border-solid border-inherit bg-slate-50 dark:bg-gray-800">
<div className="flex w-full h-full bg-slate-50 dark:bg-gray-200 rounded-lg p-2">
{/* Adjusted to use flex display for layout */}
<textarea
className="flex-1 p-3 resize-none rounded-md border border-transparent bg-slate-50 dark:bg-gray-200 text-gray-900" // Use flex-1 to allow the textarea to fill available space
value={text}
onChange={(event)=>{
setText(event.target.value)
}}
ref={textareaRef}
/>
<div className="bg-gray-100 p-1 rounded-md self-end ml-2">
{/* Added margin-left to separate from textarea, align-self to position at the bottom */}
</div>
</div>
</ScrollShadow>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
);
}