chore: subscribe co value

This commit is contained in:
Aslam H
2024-11-04 16:45:29 +07:00
parent 7a57f81b07
commit b613bbd66c
9 changed files with 132 additions and 69 deletions

View File

@@ -14,12 +14,12 @@
"web" "web"
], ],
"dependencies": { "dependencies": {
"@tauri-apps/cli": "^2.0.3", "@tauri-apps/cli": "^2.0.4",
"@tauri-apps/plugin-fs": "^2.0.0", "@tauri-apps/plugin-fs": "^2.0.1",
"jazz-nodejs": "0.8.0" "jazz-nodejs": "0.8.0"
}, },
"devDependencies": { "devDependencies": {
"bun-types": "^1.1.31" "bun-types": "^1.1.34"
}, },
"prettier": { "prettier": {
"plugins": [ "plugins": [

View File

@@ -24,7 +24,7 @@ export const JournalSection: React.FC = () => {
if ( if (
user?.emailAddresses.some((email) => user?.emailAddresses.some((email) =>
response?.emails.includes(email.emailAddress), response?.emails?.includes(email.emailAddress),
) )
) { ) {
setIsFeatureActive(true) setIsFeatureActive(true)

View File

@@ -11,19 +11,16 @@ import { Link, useLocation } from "@tanstack/react-router"
import { PageSection } from "./partials/page-section" import { PageSection } from "./partials/page-section"
import { ProfileSection } from "./partials/profile-section" import { ProfileSection } from "./partials/profile-section"
import { JournalSection } from "./partials/journal-section" import { JournalSection } from "./partials/journal-section"
// import { TaskSection } from "./partials/task-section"
import { LinkCollection } from "./partials/link-collection" import { LinkCollection } from "./partials/link-collection"
interface SidebarContextType { interface SidebarItemProps {
isCollapsed: boolean label: string
setIsCollapsed: React.Dispatch<React.SetStateAction<boolean>> url: string
icon?: React.ReactNode
onClick?: () => void
children?: React.ReactNode
} }
const SidebarContext = React.createContext<SidebarContextType>({
isCollapsed: false,
setIsCollapsed: () => {},
})
const useSidebarCollapse = ( const useSidebarCollapse = (
isTablet: boolean, isTablet: boolean,
): [boolean, React.Dispatch<React.SetStateAction<boolean>>] => { ): [boolean, React.Dispatch<React.SetStateAction<boolean>>] => {
@@ -41,14 +38,6 @@ const useSidebarCollapse = (
return [isCollapsed, setIsCollapsed] return [isCollapsed, setIsCollapsed]
} }
interface SidebarItemProps {
label: string
url: string
icon?: React.ReactNode
onClick?: () => void
children?: React.ReactNode
}
const SidebarItem: React.FC<SidebarItemProps> = React.memo( const SidebarItem: React.FC<SidebarItemProps> = React.memo(
({ label, url, icon, onClick, children }) => { ({ label, url, icon, onClick, children }) => {
const { pathname } = useLocation() const { pathname } = useLocation()
@@ -132,7 +121,6 @@ const SidebarContent: React.FC = React.memo(() => {
<div className="h-2 shrink-0" /> <div className="h-2 shrink-0" />
{me._type === "Account" && <LinkCollection />} {me._type === "Account" && <LinkCollection />}
{me._type === "Account" && <JournalSection />} {me._type === "Account" && <JournalSection />}
{/* {me._type === "Account" && <TaskSection />} */}
{me._type === "Account" && <PageSection />} {me._type === "Account" && <PageSection />}
</div> </div>
@@ -157,11 +145,6 @@ const Sidebar: React.FC = () => {
isCollapsed ? "-translate-x-full" : "translate-x-0", isCollapsed ? "-translate-x-full" : "translate-x-0",
) )
const contextValue = React.useMemo(
() => ({ isCollapsed, setIsCollapsed }),
[isCollapsed, setIsCollapsed],
)
if (isTablet) { if (isTablet) {
return ( return (
<> <>
@@ -183,9 +166,7 @@ const Sidebar: React.FC = () => {
<div <div
className={cn(sidebarInnerClasses, "border-r border-r-primary/5")} className={cn(sidebarInnerClasses, "border-r border-r-primary/5")}
> >
<SidebarContext.Provider value={contextValue}> <SidebarContent />
<SidebarContent />
</SidebarContext.Provider>
</div> </div>
</div> </div>
</> </>
@@ -195,9 +176,7 @@ const Sidebar: React.FC = () => {
return ( return (
<div className={sidebarClasses}> <div className={sidebarClasses}>
<div className={sidebarInnerClasses}> <div className={sidebarInnerClasses}>
<SidebarContext.Provider value={contextValue}> <SidebarContent />
<SidebarContent />
</SidebarContext.Provider>
</div> </div>
</div> </div>
) )
@@ -205,4 +184,4 @@ const Sidebar: React.FC = () => {
Sidebar.displayName = "Sidebar" Sidebar.displayName = "Sidebar"
export { Sidebar, SidebarItem, SidebarContext } export { Sidebar, SidebarItem }

View File

@@ -74,3 +74,4 @@ export * from "./force-graph"
export * from "./env" export * from "./env"
export * from "./slug" export * from "./slug"
export * from "./url" export * from "./url"
export * from "./jazz"

39
web/app/lib/utils/jazz.ts Normal file
View File

@@ -0,0 +1,39 @@
import {
Account,
CoValue,
CoValueClass,
DepthsIn,
ID,
subscribeToCoValue,
} from "jazz-tools"
export function waitForCoValue<T extends CoValue>(
coMap: CoValueClass<T>,
valueId: ID<T>,
account: Account,
predicate: (value: T) => boolean,
depth: DepthsIn<T>,
) {
return new Promise<T>((resolve) => {
function subscribe() {
const unsubscribe = subscribeToCoValue(
coMap,
valueId,
account,
depth,
(value) => {
if (predicate(value)) {
resolve(value)
unsubscribe()
}
},
() => {
unsubscribe()
setTimeout(subscribe, 100)
},
)
}
subscribe()
})
}

View File

@@ -1,12 +1,12 @@
/* prettier-ignore-start */
/* eslint-disable */ /* eslint-disable */
// @ts-nocheck // @ts-nocheck
// noinspection JSUnusedGlobalSymbols // noinspection JSUnusedGlobalSymbols
// This file is auto-generated by TanStack Router // This file was automatically generated by TanStack Router.
// You should NOT make any changes in this file as it will be overwritten.
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { createFileRoute } from '@tanstack/react-router' import { createFileRoute } from '@tanstack/react-router'
@@ -540,8 +540,6 @@ export const routeTree = rootRoute
._addFileChildren(rootRouteChildren) ._addFileChildren(rootRouteChildren)
._addFileTypes<FileRouteTypes>() ._addFileTypes<FileRouteTypes>()
/* prettier-ignore-end */
/* ROUTE_MANIFEST_START /* ROUTE_MANIFEST_START
{ {
"routes": { "routes": {

View File

@@ -32,6 +32,7 @@ function PageDetailComponent() {
const { me } = useAccount({ root: { personalLinks: [] } }) const { me } = useAccount({ root: { personalLinks: [] } })
const isMobile = useMedia("(max-width: 770px)") const isMobile = useMedia("(max-width: 770px)")
const page = useCoState(PersonalPage, pageId as ID<PersonalPage>) const page = useCoState(PersonalPage, pageId as ID<PersonalPage>)
const navigate = useNavigate() const navigate = useNavigate()
const { deletePage } = usePageActions() const { deletePage } = usePageActions()
const confirm = useConfirm() const confirm = useConfirm()
@@ -63,10 +64,15 @@ function PageDetailComponent() {
handleDelete={handleDelete} handleDelete={handleDelete}
isMobile={isMobile} isMobile={isMobile}
/> />
<DetailPageForm key={pageId} page={page} me={me} /> <DetailPageForm page={page} me={me} />
</div> </div>
{!isMobile && ( {!isMobile && (
<SidebarActions page={page} handleDelete={handleDelete} /> <SidebarActions
key={pageId}
page={page}
handleDelete={handleDelete}
/>
)} )}
</div> </div>
</div> </div>
@@ -129,11 +135,40 @@ const DetailPageForm = ({
}) => { }) => {
const titleEditorRef = React.useRef<Editor | null>(null) const titleEditorRef = React.useRef<Editor | null>(null)
const contentEditorRef = React.useRef<Editor | null>(null) const contentEditorRef = React.useRef<Editor | null>(null)
const [isInitialSync, setIsInitialSync] = React.useState(true)
React.useEffect(() => {
if (!page) return
const unsubscribe = page.subscribe({}, (updatedPage) => {
if (
updatedPage &&
contentEditorRef.current &&
titleEditorRef.current &&
!isInitialSync
) {
const currentTItle = titleEditorRef.current.getText()
const newTitle = updatedPage.title
if (currentTItle !== newTitle) {
titleEditorRef.current.commands.setContent(newTitle as Content)
}
const currentContent = contentEditorRef.current.getJSON()
const newContent = updatedPage.content
if (JSON.stringify(currentContent) !== JSON.stringify(newContent)) {
contentEditorRef.current.commands.setContent(newContent as Content)
}
}
})
return () => unsubscribe()
}, [page, isInitialSync])
const updatePageContent = React.useCallback( const updatePageContent = React.useCallback(
(content: Content) => { (content: Content) => {
page.content = content page.applyDiff({ content, updatedAt: new Date() })
page.updatedAt = new Date()
}, },
[page], [page],
) )
@@ -142,7 +177,7 @@ const DetailPageForm = ({
(editor: Editor) => { (editor: Editor) => {
const newTitle = editor.getText() const newTitle = editor.getText()
if (newTitle !== page.title) { if (newTitle !== page.title) {
const slug = generateUniqueSlug(newTitle || "") const slug = generateUniqueSlug(newTitle)
page.title = newTitle page.title = newTitle
page.slug = slug page.slug = slug
page.updatedAt = new Date() page.updatedAt = new Date()
@@ -201,7 +236,8 @@ const DetailPageForm = ({
) )
const titleEditor = useEditor({ const titleEditor = useEditor({
immediatelyRender: false, immediatelyRender: true,
shouldRerenderOnTransaction: false,
extensions: [ extensions: [
FocusClasses, FocusClasses,
Paragraph, Paragraph,
@@ -231,7 +267,7 @@ const DetailPageForm = ({
}, },
onCreate: ({ editor }) => { onCreate: ({ editor }) => {
if (page.title) { if (page.title) {
editor.commands.setContent(`<p>${page.title}</p>`) editor.commands.setContent(page.title)
} }
titleEditorRef.current = editor titleEditorRef.current = editor
@@ -245,10 +281,13 @@ const DetailPageForm = ({
const handleCreate = React.useCallback( const handleCreate = React.useCallback(
({ editor }: { editor: Editor }) => { ({ editor }: { editor: Editor }) => {
contentEditorRef.current = editor
if (page.content) { if (page.content) {
editor.commands.setContent(page.content as Content) editor.commands.setContent(page.content as Content)
} }
contentEditorRef.current = editor
setIsInitialSync(false)
if (page.title) { if (page.title) {
editor.commands.focus() editor.commands.focus()
@@ -276,7 +315,9 @@ const DetailPageForm = ({
value={page.content as Content} value={page.content as Content}
placeholder="Add content..." placeholder="Add content..."
output="json" output="json"
throttleDelay={1000} throttleDelay={0}
immediatelyRender={true}
shouldRerenderOnTransaction={false}
editorProps={{ handleKeyDown: handleContentKeyDown }} editorProps={{ handleKeyDown: handleContentKeyDown }}
onCreate={handleCreate} onCreate={handleCreate}
onUpdate={updatePageContent} onUpdate={updatePageContent}

View File

@@ -12,15 +12,15 @@
"lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix" "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix"
}, },
"dependencies": { "dependencies": {
"@clerk/tanstack-start": "^0.4.17", "@clerk/tanstack-start": "^0.4.21",
"@clerk/themes": "^2.1.39", "@clerk/themes": "^2.1.40",
"@dnd-kit/core": "^6.1.0", "@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0", "@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0", "@dnd-kit/sortable": "^8.0.0",
"@hookform/resolvers": "^3.9.1", "@hookform/resolvers": "^3.9.1",
"@nothing-but/force-graph": "^0.9.5", "@nothing-but/force-graph": "^0.9.5",
"@nothing-but/utils": "^0.17.0", "@nothing-but/utils": "^0.17.0",
"@omit/react-confirm-dialog": "^1.1.5", "@omit/react-confirm-dialog": "^1.1.7",
"@omit/react-fancy-switch": "^1.0.2", "@omit/react-fancy-switch": "^1.0.2",
"@radix-ui/react-alert-dialog": "^1.1.2", "@radix-ui/react-alert-dialog": "^1.1.2",
"@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-avatar": "^1.1.1",
@@ -28,7 +28,7 @@
"@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-dismissable-layer": "^1.1.1", "@radix-ui/react-dismissable-layer": "^1.1.1",
"@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.1",
"@radix-ui/react-label": "^2.1.0", "@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-popover": "^1.1.2",
"@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-scroll-area": "^1.2.0",
@@ -39,12 +39,12 @@
"@radix-ui/react-toggle": "^1.1.0", "@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0", "@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.3", "@radix-ui/react-tooltip": "^1.1.3",
"@tanstack/react-query": "^5.59.16", "@tanstack/react-query": "^5.59.18",
"@tanstack/react-router": "^1.77.8", "@tanstack/react-router": "^1.78.3",
"@tanstack/react-router-with-query": "^1.77.8", "@tanstack/react-router-with-query": "^1.78.3",
"@tanstack/react-virtual": "^3.10.8", "@tanstack/react-virtual": "^3.10.8",
"@tanstack/router-zod-adapter": "^1.77.8", "@tanstack/router-zod-adapter": "^1.78.3",
"@tanstack/start": "^1.77.8", "@tanstack/start": "^1.78.3",
"@tiptap/core": "^2.9.1", "@tiptap/core": "^2.9.1",
"@tiptap/extension-code-block-lowlight": "^2.9.1", "@tiptap/extension-code-block-lowlight": "^2.9.1",
"@tiptap/extension-color": "^2.9.1", "@tiptap/extension-color": "^2.9.1",
@@ -65,13 +65,13 @@
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "1.0.2",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"framer-motion": "^11.11.10", "framer-motion": "^11.11.11",
"jazz-browser-media-images": "^0.8.7", "jazz-browser-media-images": "^0.8.12",
"jazz-react": "^0.8.7", "jazz-react": "^0.8.12",
"jazz-react-auth-clerk": "^0.8.7", "jazz-react-auth-clerk": "^0.8.12",
"jazz-tools": "^0.8.5", "jazz-tools": "^0.8.12",
"jotai": "^2.10.1", "jotai": "^2.10.1",
"lowlight": "^3.1.0", "lowlight": "^3.1.0",
"lucide-react": "^0.446.0", "lucide-react": "^0.446.0",
@@ -86,7 +86,7 @@
"react-textarea-autosize": "^8.5.4", "react-textarea-autosize": "^8.5.4",
"ronin": "^4.4.1", "ronin": "^4.4.1",
"slugify": "^1.6.6", "slugify": "^1.6.6",
"sonner": "^1.5.0", "sonner": "^1.6.1",
"streaming-markdown": "^0.0.14", "streaming-markdown": "^0.0.14",
"tailwind-merge": "^2.5.4", "tailwind-merge": "^2.5.4",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
@@ -95,11 +95,11 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@ronin/learn-anything": "^0.0.0-3460430517579", "@ronin/learn-anything": "^0.0.0-3460605731228",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@tanstack/react-query-devtools": "^5.59.16", "@tanstack/react-query-devtools": "^5.59.18",
"@tanstack/router-devtools": "^1.77.8", "@tanstack/router-devtools": "^1.78.3",
"@types/node": "^22.8.4", "@types/node": "^22.8.7",
"@types/react": "^18.3.12", "@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1", "@types/react-dom": "^18.3.1",
"@types/uuid": "^10.0.0", "@types/uuid": "^10.0.0",

View File

@@ -17,7 +17,12 @@ export interface LaEditorProps extends UseLaEditorProps {
export const LaEditor = React.forwardRef<HTMLDivElement, LaEditorProps>( export const LaEditor = React.forwardRef<HTMLDivElement, LaEditorProps>(
({ className, editorContentClassName, me, personalPage, ...props }, ref) => { ({ className, editorContentClassName, me, personalPage, ...props }, ref) => {
const editor = useLaEditor({ ...props, me, personalPage }) const editor = useLaEditor({
...props,
me,
personalPage,
shouldRerenderOnTransaction: true,
})
if (!editor) { if (!editor) {
return null return null