import { useObjectState } from '@hyper-hyper-space/react'; import { Block, BlockType, Page, WikiSpace } from '@hyper-hyper-space/wiki-collab'; import WikiSpaceBlock from './WikiSpaceBlock'; import { Box, Button, ListItemIcon, ListItemText, Menu, MenuItem, Stack, Typography, useMediaQuery, useTheme } from '@mui/material'; import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd'; import { CausalArray, Identity, MutationEvent } from '@hyper-hyper-space/core'; import { useOutletContext, useParams } from 'react-router'; import { WikiContext } from './WikiSpaceView'; import React, { useCallback, useEffect, useRef, useState } from 'react'; import WikiSpaceNavigation from './WikiSpaceNavigation'; function WikiSpacePage() { const { pageName } = useParams(); const { wiki, nav, spaceContext} = useOutletContext(); const { home } = spaceContext; //const wikiState = useObjectState(wiki, {debounceFreq: 250}); const wikiTitleState = useObjectState(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.title, debounceFreq: 250}); const pageArrayState = useObjectState(wiki, {filterMutations: (ev: MutationEvent) => ev.emitter === wiki?.pages, debounceFreq: 250}); const [page, setPage] = useState(); const [pageIsSaved, setPageIsSaved] = useState(); const [focusOnBlockWithHash, setfocusOnBlockWithHash] = useState(); const blocksListState = useObjectState>(page?.blocks, {debounceFreq: 250}); // check if blocks are draggable const wikiWriteFlags = useObjectState(wiki?.permissionLogic?.writeConfig, {debounceFreq: 250}) const wikiMembers = useObjectState(wiki?.permissionLogic?.members, {debounceFreq: 250}) const [editable, setEditable] = useState(false) useEffect(() => { let cancel = false page?.canUpdate(home?.getAuthor())?.then( canUpdate => { if (cancel) return; setEditable(canUpdate) }) return () => { cancel = true } }, [wikiWriteFlags, wikiMembers]) // remove debouncing after loading: useEffect(() => { const size = blocksListState?.getValue()?.size(); if (size !== undefined && size > 0 && blocksListState?.getDebounceFreq() !== undefined) { blocksListState?.setDebounceFreq(undefined); } }, [blocksListState]); useEffect(() => { if (pageName !== undefined && pageArrayState?.getValue() !== undefined) { let cancel = false const updateCurrentPage = async () => { console.log('PAGE IS "' + pageName + '"') if (page?.name !== pageName || !pageIsSaved) { const existingPage = pageArrayState?.getValue()?.getPage(pageName); if (existingPage !== undefined) { setPage(existingPage); setPageIsSaved(true); console.log('NAVIGATING TO EXISTING PAGE "' + pageName + '"') } else if (page === undefined || page?.name !== pageName){ setPage(pageArrayState?.getValue()?.createPage(pageName)); setPageIsSaved(false); console.log('NAVIGATING TO NEW PAGE "' + pageName + '"') } else { console.log('NOT NAVIGATING 1') } } else { if (!pageIsSaved && pageArrayState?.getValue()?.hasPage(pageName)) { await page?.save(); if (cancel) return; const foundPage = pageArrayState?.getValue()?.getPage(pageName); if (page !== undefined) { setPage(page); setPageIsSaved(true); console.log('FOUND PAGE "' + page + '", USING SAVED VERSION INSTEAD') } else { console.log('NOT NAVIGATING 2') } } else { console.log('NOT NAVIGATING 3') } } } updateCurrentPage(); return () => {cancel = true} } }, [pageName, page, pageArrayState]); const onDragEnd = async (result: DropResult) => { const from = result.source.index; let to = result.destination?.index; if (to === undefined) { return } page?.moveBlock(from, to, home?.getAuthor()!) } const startedEditing = () => { console.log('editing... pause watching for blocks updates') blocksListState?.getValue()?.dontWatchForChanges(); blocksListState?.setDebounceFreq(undefined); } const stoppedEditing = () => { console.log('continue watching for blocks updates'); blocksListState?.setDebounceFreq(250); blocksListState?.getValue()?.loadAndWatchForChanges(); } const addTextBlock = (idx?: number) => { console.log('adding text block with author:', home?.getAuthor(), '...') console.log('hasKeyPair()=', home?.getAuthor()?.hasKeyPair(), '...') const newBlock = page?.addBlock(idx, undefined, (home?.getAuthor() as Identity)!); newBlock?.then(block => { setfocusOnBlockWithHash(block?.getLastHash()) }) } const addImageBlock = (dataUrl: string, idx?: number) => { console.log('CALLED ADD IMAGE BLOCK') page?.addBlock(idx, BlockType.Image, home?.getAuthor() as Identity).then(async (b: Block) => { console.log('SET IMAGE VALUE TO ', dataUrl) await b.setValue(dataUrl, home?.getAuthor() as Identity); await b.save(); }); } const focusOnAdjacentBlock = // useCallback( (thisBlock: Block, distance=1) => { const adjacent = blocksListState?.value?.lookup(blocksListState?.value?.indexOf(thisBlock) + distance) setfocusOnBlockWithHash(adjacent?.getLastHash()) } const addBlockAfter = (thisBlock: Block) => { return addTextBlock(blocksListState?.value?.indexOf(thisBlock)! + 1); }; const blockElements = blocksListState?.getValue()?.contents().map((block, index) => ( {(provided) =>
page?.removeBlock(block, home?.getAuthor())} focusOnBlockWithHash={focusOnBlockWithHash} addBlockAfter={addBlockAfter} focusOnAdjacentBlock={focusOnAdjacentBlock} >
}
) ) const [anchorEl, setAnchorEl] = useState(null); const [newBlockIdx, setNewBlockIdx] = useState(); const [showAddBlock, setShowAddBlock] = useState(false); const showAddBlockMenu = (newAnchorEl: HTMLElement, newBlockIdx?: number) => { setAnchorEl(newAnchorEl); setNewBlockIdx(newBlockIdx); setShowAddBlock(true); }; const handleAddBlock = (event: React.MouseEvent) => { showAddBlockMenu(event.currentTarget); }; // image upload const newImageInputRef = useRef(null); const onNewPicture: React.ChangeEventHandler = (e: React.ChangeEvent) => { if (e.target.files !== null) { console.log('got a file:') console.log(e.target.files[0]); const fileReader = new FileReader(); fileReader.onload = () => { const dataUrl = fileReader.result?.toString(); if (dataUrl !== undefined) { addImageBlock(dataUrl, newBlockIdx); } }; fileReader.readAsDataURL(e.target.files[0]); } } return ( <> {/*
*/} {page === undefined && Loading... } {page !== undefined && { provided =>
{blockElements} {provided.placeholder}
}
{!blocksListState?.getValue()?.contents().length && wikiTitleState?.getValue()?.title?.getValue() !== undefined && This page looks empty. } {!blocksListState?.getValue()?.contents().length && wikiTitleState?.getValue()?.title?.getValue() === undefined && Fetching wiki contents... }
}
{setShowAddBlock(false); setAnchorEl(null);}} MenuListProps={{ 'aria-labelledby': 'add a block after this one', }} > {addTextBlock(newBlockIdx); setShowAddBlock(false); setAnchorEl(null);}}> Add text below {setShowAddBlock(false); setAnchorEl(null); newImageInputRef.current?.click();}}> Add an image below ) } export default WikiSpacePage;