initial commit

This commit is contained in:
Fedor Katurov 2023-04-24 19:13:40 +06:00
commit 9a4eb6ef58
23 changed files with 2490 additions and 0 deletions

View file

@ -0,0 +1,55 @@
import { DockviewApi, DockviewReadyEvent } from "dockview";
import { useEffect, useRef } from "react";
import { createDefaultLayout } from "../utils/createDefaultLayout";
export const useGridLayoutPersistance = () => {
const api = useRef<DockviewApi>();
const onReady = (event: DockviewReadyEvent) => {
api.current = event.api;
const layoutString = localStorage.getItem("dockview_persistance_layout");
if (!layoutString) {
createDefaultLayout(event.api);
return;
}
try {
const layout = JSON.parse(layoutString);
event.api.fromJSON(layout);
} catch (err) {
console.log(err);
createDefaultLayout(event.api);
}
};
useEffect(() => {
if (!api.current) {
return;
}
const disposable = api.current.onDidLayoutChange(() => {
if (!api.current) {
return;
}
if (!api.current.groups.length) {
createDefaultLayout(api.current);
}
const layout = api.current.toJSON();
localStorage.setItem(
"dockview_persistance_layout",
JSON.stringify(layout)
);
});
return () => {
disposable.dispose();
};
}, []);
return { api, onReady };
};

View file

@ -0,0 +1,32 @@
import { DockviewReact, IDockviewPanelProps } from "dockview";
import { useGridLayoutPersistance } from "./hooks/useGridLayoutPersistance";
import { FC, createElement, useMemo } from "react";
import { GridLayoutComponentProps } from "../../types";
export interface GridLayoutProps {
component: FC<GridLayoutComponentProps>;
}
export const GridLayout: FC<GridLayoutProps> = ({ component }) => {
const { onReady } = useGridLayoutPersistance();
const components = useMemo(
() => ({
default: (props: IDockviewPanelProps<{ title: string }>) => {
return createElement(component, {
id: props.api.id,
title: props.params.title,
});
},
}),
[component]
);
return (
<DockviewReact
components={components}
onReady={onReady}
className="dockview-theme-abyss"
/>
);
};

View file

@ -0,0 +1,16 @@
import { DockviewApi } from "dockview";
import { v4 } from "uuid";
export const createDefaultLayout = (api: DockviewApi) => {
api.addPanel({
id: v4(),
component: "default",
title: "New editor",
params: {
title: "Panel 1",
},
});
// panel.group.locked = true;
// panel.group.header.hidden = true;
};

View file

@ -0,0 +1,47 @@
import { FC } from "react";
import {
Editor,
rootCtx,
defaultValueCtx,
editorViewOptionsCtx,
} from "@milkdown/core";
import { Milkdown, useEditor } from "@milkdown/react";
import { commonmark } from "@milkdown/preset-commonmark";
import { listener, listenerCtx } from "@milkdown/plugin-listener";
import { clipboard } from "@milkdown/plugin-clipboard";
import styles from "./styles.module.scss";
interface MarkdownEditorProps {
value?: string;
onChange?: (val: string) => void;
}
export const MarkdownEditor: FC<MarkdownEditorProps> = ({
value = "",
onChange,
}) => {
useEditor((root) =>
Editor.make()
.config((ctx) => {
ctx.set(rootCtx, root);
ctx.set(defaultValueCtx, value);
ctx.get(listenerCtx).markdownUpdated((_, markdown) => {
onChange?.(markdown);
});
ctx.update(editorViewOptionsCtx, (prev) => ({
...prev,
attributes: {
class: styles.editor,
spellcheck: "false",
},
}));
})
.use(commonmark)
.use(listener)
.use(clipboard)
);
return <Milkdown />;
};

View file

@ -0,0 +1,22 @@
.editor {
outline: none;
height: 100%;
& > :first-child {
margin-top: 0 !important;
margin-left: 0 !important;
}
& > :last-child {
margin-bottom: 0 !important;
margin-right: 0 !important;
}
}
div[data-milkdown-root="true"] {
height: 100%;
:global(.milkdown) {
height: 100%;
}
}

View file

@ -0,0 +1,36 @@
import { useEffect, useState } from "react";
const prefix = "VALUE__";
const safelyGetStringValue = (key: string) => {
try {
return localStorage.getItem(key) ?? "";
} catch (error) {
console.warn(error);
return "";
}
};
const safelySetStringValue = (key: string, value: string) => {
try {
return localStorage.setItem(key, value);
} catch (error) {
console.warn(error);
}
};
export const usePersistedValue = (
id: string
): [string, (val: string) => void] => {
const key = `${prefix}${id}`;
const [value, setValue] = useState(safelyGetStringValue(key));
useEffect(() => {
setValue(safelyGetStringValue(key));
}, [id, key]);
useEffect(() => {
safelySetStringValue(key, value);
}, [key, value]);
return [value, setValue];
};

View file

@ -0,0 +1,23 @@
import { MilkdownProvider } from "@milkdown/react";
import { FC } from "react";
import { MarkdownEditor } from "../../components/MarkdownEditor";
import styles from "./styles.module.scss";
import { usePersistedValue } from "./hooks/usePersistedValue";
interface MarkdownEditorContainerProps {
id: string;
}
export const MarkdownEditorContainer: FC<MarkdownEditorContainerProps> = ({
id,
}) => {
const [value, setValue] = usePersistedValue(id);
return (
<div className={styles.editor}>
<MilkdownProvider>
<MarkdownEditor value={value} onChange={setValue} />
</MilkdownProvider>
</div>
);
};

View file

@ -0,0 +1,6 @@
.editor {
height: 100%;
padding: 16px;
overflow: scroll;
box-sizing: border-box;
}

View file

@ -0,0 +1,4 @@
export interface GridLayoutComponentProps {
id: string;
title: string;
}