Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
191 changes: 109 additions & 82 deletions packages/unigraph-dev-explorer/src/examples/notes/NoteBlock.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable react/require-default-props */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable no-param-reassign */
import { Typography } from '@mui/material';
import { Typography, colors } from '@mui/material';
import { styled } from '@mui/styles';
import React from 'react';
import _ from 'lodash';
Expand Down Expand Up @@ -72,7 +72,6 @@ const BlockChild = ({ elindex, shortcuts, displayAs, isCollapsed, setCollapsed,
);

const BlockChildren = ({
isCollapsed,
isChildren,
subentities,
tabContext,
Expand All @@ -84,8 +83,12 @@ const BlockChildren = ({
childrenDisplayAs,
callbacks,
copyOrCutHandler,
}: any) =>
!(isCollapsed === true) ? (
}: any) => {
const addNoteBlock = React.useCallback(() => {
noteBlockCommands['add-child'](data, editorContext);
}, [data, editorContext]);

return (
<div style={{ width: '100%' }}>
{subentities.length || isChildren ? (
<DragandDrop
Expand Down Expand Up @@ -157,18 +160,12 @@ const BlockChildren = ({
displayAs={data?._value?.children?._displayAs || 'outliner'}
parentDisplayAs={displayAs}
>
<PlaceholderNoteBlock
callbacks={{
'add-child': () => noteBlockCommands['add-child'](data, editorContext),
}}
/>
<PlaceholderNoteBlock onClick={addNoteBlock} />
</OutlineComponent>
)}
</div>
) : (
// eslint-disable-next-line react/jsx-no-useless-fragment
<></>
);
};

const Outline = styled('div')({
flex: '0 0 auto',
Expand All @@ -177,36 +174,35 @@ const Outline = styled('div')({
position: 'relative',
});

const ChildrenLeftBorder = styled('div')({
flex: '0 0 1px',
height: 'calc(100% + 4px)',
width: '1px',
backgroundColor: 'lightgray',
position: 'absolute',
left: '-0.8rem',
});

const BulletContainer = styled('div')({
const ControlsContainer = styled('div')({
flex: '0 0 1rem',
display: 'flex',
width: '1rem',
height: '1rem',
borderRadius: '50%',
justifyContent: 'center',
alignItems: 'center',
transition: '0.1s ease-in',
transform: 'translateY(-0.15rem)', // fine tune vertical alignment
transform: 'translateY(-0.2rem)', // fine tune vertical alignment
position: 'relative',
});

const Bullet = styled('div')({
flex: '0 0 1rem',
display: 'flex',
width: '1rem',
height: '1rem',
borderRadius: '50%',
width: '0.375rem',
height: '0.375rem',
backgroundColor: '#333333',
justifyContent: 'center',
alignItems: 'center',
transition: 'background 0.1s ease-in',
'& > svg': {
fill: colors.common.black,
},
});

const Toggle = styled('button')({
flex: '0 0 1rem',
position: 'absolute',
top: 0,
left: '-1rem',
width: '1rem',
height: '1rem',
display: 'flex',
Expand All @@ -215,17 +211,30 @@ const Toggle = styled('button')({
cursor: 'pointer',
background: 'none',
border: 'none',
borderRadius: '50%',
outline: 'none',
position: 'absolute',
margin: 0,
padding: 0,
left: '-0.8rem',
top: '0.15rem',
padding: '0.1rem',
fontSize: '1.25rem',
transition: 'transform 0.1s ease-in',
'&:hover': {
backgroundColor: colors.grey[200],
},
});

const ChildrenLeftBorder = styled('div')({
flex: '0 0 1px',
height: 'calc(100% + 4px)',
width: '1px',
backgroundColor: colors.grey[300],
position: 'absolute',
left: 'calc(-0.8rem - 0.5px)',
});

const ChildrenContainer = styled('div')({
flexGrow: 1,
marginLeft: '0.3rem',
wordBreak: 'break-word',
});

interface OutlineComponentProps {
Expand All @@ -239,6 +248,9 @@ interface OutlineComponentProps {
parentDisplayAs?: string;
}

/** Hold the reference to the DOM of the content of an outline. */
const OutlineContentContext = React.createContext<React.MutableRefObject<HTMLDivElement | null>>({ current: null });

export function OutlineComponent({
children,
collapsed,
Expand All @@ -250,45 +262,50 @@ export function OutlineComponent({
parentDisplayAs,
}: OutlineComponentProps) {
const [hover, setHover] = React.useState(false);
const rOutlineEl = React.useRef<HTMLDivElement>(null);
const rContentEl = React.useRef<HTMLDivElement>(null);
const onPointerMove = React.useCallback((e: React.PointerEvent) => {
const outlineEl = rOutlineEl.current;
if (!outlineEl) return;
const rect = outlineEl.getBoundingClientRect();
const y = e.clientY - rect.top; // y position within the element
setHover(y > 0 && y < 24);
const contentEl = rContentEl.current;
if (!contentEl) return;
const rect = contentEl.getBoundingClientRect();
setHover(e.clientY > rect.top && e.clientY < rect.bottom);
}, []);
const onPointerOut = React.useCallback(() => setHover(false), []);
const onPointerLeave = React.useCallback(() => setHover(false), []);
const toggleChildren = React.useCallback(() => setCollapsed && setCollapsed(!collapsed), [collapsed, setCollapsed]);

return (
<Outline ref={rOutlineEl} onPointerMove={onPointerMove} onPointerOut={onPointerOut}>
<Toggle
style={{
visibility: showCollapse && hover ? 'visible' : 'hidden',
transform: `rotate(${collapsed ? '0deg' : '90deg'})`,
}}
onClick={toggleChildren}
>
<ChevronRight />
</Toggle>
{displayAs === 'outliner' && (
<>
<ChildrenLeftBorder
style={{
display: parentDisplayAs === 'outliner' ? 'block' : 'none',
}}
/>
<BulletContainer
<Outline onPointerMove={onPointerMove} onPointerLeave={onPointerLeave}>
<ControlsContainer>
<Toggle
style={{
visibility: showCollapse && hover ? 'visible' : 'hidden',
transform: `rotate(${collapsed ? '0deg' : '90deg'})`,
}}
onClick={toggleChildren}
>
<ChevronRight fontSize="inherit" />
</Toggle>
{displayAs === 'outliner' && (
<Bullet
style={{
backgroundColor: collapsed ? '#e4e4e4' : 'transparent',
backgroundColor: collapsed ? colors.grey[200] : 'transparent',
}}
>
<Bullet />
</BulletContainer>
</>
<svg width="0.375rem" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<circle cx="8" cy="8" r="8" />
</svg>
</Bullet>
)}
</ControlsContainer>
{displayAs === 'outliner' && (
<ChildrenLeftBorder
style={{
display: parentDisplayAs === 'outliner' ? 'block' : 'none',
}}
/>
)}
<ChildrenContainer style={{ flexGrow: 1 }}>{children}</ChildrenContainer>
<ChildrenContainer>
<OutlineContentContext.Provider value={rContentEl}>{children}</OutlineContentContext.Provider>
</ChildrenContainer>
</Outline>
);
}
Expand Down Expand Up @@ -551,6 +568,15 @@ export function DetailedOutlinerBlock({

const childrenDisplayAs = data?._value?.children?._displayAs || 'outliner';

const rOutlineContentEl = React.useContext(OutlineContentContext);
const combinedRef = React.useCallback(
(node: HTMLDivElement) => {
rOutlineContentEl.current = node;
editorRef.current = node;
},
[rOutlineContentEl],
);

return (
<NoteViewPageWrapper isRoot={!isChildren}>
<div
Expand Down Expand Up @@ -581,7 +607,7 @@ export function DetailedOutlinerBlock({
>
<div
key="editor-frame"
ref={editorRef}
ref={combinedRef}
tabIndex={textInput?.current ? undefined : -1}
onClick={(ev) => {
if (textInput?.current && !isEditing) {
Expand Down Expand Up @@ -654,20 +680,21 @@ export function DetailedOutlinerBlock({
) : (
[]
)}
<BlockChildren
isCollapsed={isCollapsed}
isChildren={isChildren}
subentities={subentities}
tabContext={tabContext}
data={data}
isChildrenCollapsed={isChildrenCollapsed}
setIsChildrenCollapsed={setIsChildrenCollapsed}
editorContext={editorContext}
displayAs={displayAs}
childrenDisplayAs={childrenDisplayAs}
callbacks={callbacks}
copyOrCutHandler={copyOrCutHandler}
/>
{!isCollapsed && (
<BlockChildren
isChildren={isChildren}
subentities={subentities}
tabContext={tabContext}
data={data}
isChildrenCollapsed={isChildrenCollapsed}
setIsChildrenCollapsed={setIsChildrenCollapsed}
editorContext={editorContext}
displayAs={displayAs}
childrenDisplayAs={childrenDisplayAs}
callbacks={callbacks}
copyOrCutHandler={copyOrCutHandler}
/>
)}
{!isChildren ? <ParentsAndReferences data={data} /> : []}
</div>
</NoteViewPageWrapper>
Expand Down Expand Up @@ -859,12 +886,12 @@ export function DetailedEmbedBlock({

const onKeyDownHandler = React.useCallback(
(
ev,
isEditing: any,
setIsEditing: any,
ev: React.KeyboardEvent,
isEditing: boolean,
setIsEditing: React.Dispatch<React.SetStateAction<boolean>>,
getCurrentText: any,
textInput: any,
edited: any,
edited: React.MutableRefObject<boolean>,
editorRef: any,
editorContext: any,
) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
/* eslint-disable react/require-default-props */
import { MouseEventHandler } from 'react';
import { Typography } from '@mui/material';
import { grey } from '@mui/material/colors';
import { styled } from '@mui/styles';
import { mdiNoteOutline } from '@mdi/js';
import { Icon } from '@mdi/react';
import { AutoDynamicView } from '../../components/ObjectView/AutoDynamicView';
Expand Down Expand Up @@ -51,18 +55,22 @@ export function NoteBlock({ data, inline }: any) {
);
}

export function PlaceholderNoteBlock({ callbacks }: any) {
/** Placeholder note block */
const FillParentWidth = styled('div')({
width: '100%',
});

const PlaceholderText = styled(Typography)({
fontStyle: 'italic',
color: grey[500],
});

export function PlaceholderNoteBlock({ onClick }: { onClick?: MouseEventHandler<HTMLSpanElement> }) {
return (
<div style={{ width: '100%' }}>
<Typography
variant="body1"
style={{ fontStyle: 'italic' }}
onClick={() => {
callbacks['add-child']();
}}
>
<FillParentWidth>
<PlaceholderText variant="body1" onClick={onClick}>
Click here to start writing
</Typography>
</div>
</PlaceholderText>
</FillParentWidth>
);
}