Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
areknawo committed Aug 30, 2024
1 parent 24b3f1d commit 7f3edb5
Show file tree
Hide file tree
Showing 23 changed files with 903 additions and 416 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"totp",
"unsign",
"Unsubscribable",
"vals",
"weaviate"
],
"unocss.root": ["apps/web"],
Expand Down
2 changes: 1 addition & 1 deletion apps/docs/.astro/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1722930418730
"lastUpdateCheck": 1723974048314
}
}
2 changes: 1 addition & 1 deletion apps/landing-page/.astro/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_variables": {
"lastUpdateCheck": 1722073136775
"lastUpdateCheck": 1723187347646
}
}
2 changes: 1 addition & 1 deletion apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@vrite/editor": "workspace:*",
"@vrite/scripts": "workspace:*",
"@vrite/sdk": "workspace:*",
"@vrite/tiptap-solid": "^1.0.2",
"@vrite/tiptap-solid": "^1.0.4",
"clsx": "^2.1.0",
"dayjs": "^1.11.10",
"dompurify": "^3.0.8",
Expand Down
230 changes: 153 additions & 77 deletions apps/web/public/sandbox.js

Large diffs are not rendered by default.

24 changes: 19 additions & 5 deletions apps/web/scripts/sandbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import {
ExtensionBaseViewContext,
ExtensionBaseContext,
Val,
generateId
generateId,
isVal
} from "@vrite/sdk/extensions";

// eslint-disable-next-line init-declarations
Expand Down Expand Up @@ -43,10 +44,13 @@ type SerializedContext<C extends ExtensionBaseContext> = Omit<

const { createClient } = await import("@vrite/sdk/api");
const client = createClient({
...(location.ancestorOrigins.item(0)?.includes("//localhost") && {
baseURL: "http://localhost:4444"
}),
token,
extensionId
});
const wrapInVal = (value: ContextValue, path: string): Val => {
const wrapInVal = (value: ContextValue, path: string, previousVal?: Val): Val => {
const output = (() => output[metadata!.__value]) as Val;

output[metadata!.__id] = `${path}`;
Expand All @@ -64,12 +68,16 @@ type SerializedContext<C extends ExtensionBaseContext> = Omit<
return output;
};
const unwrapVal = (value: Val): ContextValue => {
if (!isVal(value as Val<ContextValue>) && typeof value !== "function") {
return value as unknown as ContextValue;
}

const output = value();

if (typeof output === "object" && !Array.isArray(output) && output !== null) {
return Object.fromEntries(
Object.keys(output).map((key) => {
return [key, unwrapVal(output[key] as Val)];
return [key, unwrapVal(output[key] as Val<ContextValue>)];
})
);
}
Expand All @@ -81,7 +89,7 @@ type SerializedContext<C extends ExtensionBaseContext> = Omit<

env.data = {};
Object.keys(serializedEnvData).forEach((key) => {
env!.data[key] = wrapInVal(serializedEnvData[key], key);
env!.data[key] = wrapInVal(serializedEnvData[key], key, env!.data[key]);
});
};
const serializeEnvData = (): SerializedEnvData => {
Expand Down Expand Up @@ -169,6 +177,7 @@ type SerializedContext<C extends ExtensionBaseContext> = Omit<
} else if (ctx.usableEnv.writable.includes(parts[1])) {
const setter = (value: any): void => {
getVal()[metadata!.__value] = wrapInVal(value, path)[metadata!.__value];
extension?.triggerEffects(getVal()[metadata!.__id]);
};

return [getter, setter];
Expand Down Expand Up @@ -251,9 +260,14 @@ type SerializedContext<C extends ExtensionBaseContext> = Omit<
envData: serializeEnvData()
};
},
updateEnvData: (envData: SerializedEnvData) => {
updateEnvData: async (updatedIds: string[], envData: SerializedEnvData) => {
updateEnvData(envData);

if (updatedIds.length && extension && "triggerEffects" in extension) {
await extension.triggerEffects(...updatedIds);
Websandbox.connection?.remote.flush(serializeEnvData());
}

return {
envData: serializeEnvData()
};
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/components/fragments/collapsible-section.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Component, JSX, Show, createSignal } from "solid-js";
import { mdiChevronDown } from "@mdi/js";
import clsx from "clsx";
import { Card, Heading, IconButton } from "#components/primitives";
import { Heading, IconButton } from "#components/primitives";

interface CollapsibleSectionProps {
icon: JSX.Element;
Expand Down
2 changes: 2 additions & 0 deletions apps/web/src/lib/editor/extensions/element/node.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ const Element = BaseElement.extend<
newNode.type.name !== "element"
) {
if (uid && customViews.has(uid)) {
customViews.get(uid)?.extension.sandbox?.removeScope(`view:${uid}`);
customViews.delete(uid);
}

Expand All @@ -590,6 +591,7 @@ const Element = BaseElement.extend<
storage.customElements[node.attrs.type.toLowerCase()]
) {
if (uid && customViews.has(uid)) {
customViews.get(uid)?.extension.sandbox?.removeScope(`view:${uid}`);
customViews.delete(uid);
}

Expand Down
169 changes: 152 additions & 17 deletions apps/web/src/lib/extensions/component-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
JSX,
Match,
on,
onMount,
Show,
splitProps,
Switch
Expand All @@ -18,22 +17,134 @@ import { createStore } from "solid-js/store";
import { ExtensionElement, ExtensionSpec, ContextObject } from "@vrite/sdk/extensions";
import { Dynamic } from "solid-js/web";
import clsx from "clsx";
import { useNotifications } from "#context";
import { Button, Card, Icon, IconButton, Loader, Select, Tooltip } from "#components/primitives";
import { InputField } from "#components/fragments";
import { mdiChevronLeft, mdiClose } from "@mdi/js";
import {
Button,
Card,
Heading,
Icon,
IconButton,
Loader,
Select,
Tooltip
} from "#components/primitives";
import { CollapsibleSection, InputField } from "#components/fragments";
import { ExtensionDetails, useLocalStorage, useNotifications } from "#context";

interface ComponentRendererProps {
view: ExtensionElement | string;
spec: ExtensionSpec;
contentEditable?: boolean;
components?: Record<string, Component<any>>;
}
type SidePanelHeaderSection = {
label: string;
icon: string;
id: string;
action?: ExtensionElement;
};
type RenderedComponentProps<O = Record<string, any>> = {
contentEditable?: boolean;
children: JSX.Element;
extension: ExtensionDetails;
renderView(view: ExtensionElement): JSX.Element;
} & O;

const baseComponents = {
SidePanelContent: (props: RenderedComponentProps) => {
return (
<div class="flex-col h-full relative flex overflow-hidden">
<div class="w-full h-full overflow-x-hidden overflow-y-auto scrollbar-sm-contrast px-5">
<div class="flex justify-start flex-col min-h-full items-start w-full gap-5 pb-5">
{props.children}
</div>
</div>
</div>
);
},
SidePanelHeader: (
props: RenderedComponentProps<{
defaultSection: string;
section: string;
sections: SidePanelHeaderSection[];
onBack?: () => void;
}>
) => {
const getSection = (sectionId: string): SidePanelHeaderSection => {
return (
props.sections.find(({ id }) => id === sectionId) ||
props.sections[0] || {
id: "",
label: props.extension.spec.displayName,
icon: undefined
}
);
};
const section = (): SidePanelHeaderSection => getSection(props.section || props.defaultSection);
const defaultSection = (): SidePanelHeaderSection => getSection(props.defaultSection);
const isDefaultSection = (): boolean => props.section === props.defaultSection;
const { setStorage } = useLocalStorage();

return (
<div
class={clsx(
"flex justify-start items-start mb-4 px-5 flex-col",
isDefaultSection() ? "pt-5" : "pt-2"
)}
>
<IconButton
variant="text"
class={clsx("m-0 h-6 -mb-1", isDefaultSection() && "hidden")}
onClick={() => {
if (!isDefaultSection()) {
props.onBack?.();
}
}}
label={defaultSection().label}
size="small"
path={mdiChevronLeft}
></IconButton>
<div class="flex justify-center items-center w-full">
<Show
when={isDefaultSection()}
fallback={
<>
<Show when={section().icon}>
<IconButton
class="m-0 mr-1"
path={section().icon}
variant="text"
hover={false}
badge
/>
</Show>
<Heading level={2} class="flex-1">
{section().label}
</Heading>
</>
}
>
<IconButton
path={mdiClose}
text="soft"
badge
class="flex md:hidden mr-2 m-0"
onClick={() => {
setStorage((storage) => ({
...storage,
sidePanelWidth: 0
}));
}}
/>
<Heading level={1} class="py-1 flex-1">
{section().label}
</Heading>
</Show>
<Show when={section().action}>{props.renderView(section().action!)}</Show>
</div>
</div>
);
},
Field: (props: RenderedComponentProps<ComponentProps<typeof InputField>>) => {
return (
<InputField
Expand Down Expand Up @@ -128,6 +239,25 @@ const baseComponents = {
Show: (props: RenderedComponentProps<{ when: boolean }>) => {
return <Show when={props.when}>{props.children}</Show>;
},
CollapsibleSection: (
props: RenderedComponentProps<{
icon: string;
label: string;
action?: ExtensionElement;
color?: "base" | "primary";
defaultOpened?: boolean;
}>
) => {
return (
<CollapsibleSection
icon={props.icon}
label={props.label}
action={props.action ? props.renderView(props.action) : undefined}
>
{props.children}
</CollapsibleSection>
);
},
Switch: (props: RenderedComponentProps) => {
return <Switch>{props.children}</Switch>;
},
Expand Down Expand Up @@ -170,11 +300,11 @@ renderer.link = (href, title, text) => {
};

const ComponentRenderer: Component<ComponentRendererProps> = (props) => {
const { notify } = useNotifications();
const components = createMemo<Record<string, Component<any>>>(() => ({
...baseComponents,
...props.components
}));
const { notify } = useNotifications();
const { envData, setEnvData, extension } = useViewContext();
const { sandbox } = extension;

Expand All @@ -190,13 +320,24 @@ const ComponentRenderer: Component<ComponentRendererProps> = (props) => {
const componentName = props.view.component.match(/^(.+?)(?:\[|\.|$)/)?.[1] || "";
const [componentProps, setComponentProps] = createStore<Record<string, any>>({});
const viewProps = props.view.props || {};
const renderView = (view: ExtensionElement | string): JSX.Element => {
return (
<ComponentRenderer
view={view}
spec={props.spec}
contentEditable={props.contentEditable}
components={props.components}
/>
);
};

Object.keys(viewProps).forEach((key) => {
const value = viewProps[key];

if (key.startsWith("bind:")) {
const bindKey = key.slice(5);
const pathParts = `${value}`.split(".");
const path = `${value}`;
const pathParts = path.split(".");

createEffect(
on(
Expand All @@ -218,7 +359,7 @@ const ComponentRenderer: Component<ComponentRendererProps> = (props) => {
);
setComponentProps(`set${bindKey[0].toUpperCase()}${bindKey.slice(1)}`, () => {
return (value: any) => {
setEnvData((envData) => {
setEnvData([path], (envData) => {
const newValue = { ...(envData as ContextObject) };

let currentObject = newValue;
Expand Down Expand Up @@ -251,8 +392,7 @@ const ComponentRenderer: Component<ComponentRendererProps> = (props) => {
`${value}`,
{
contextFunctions: [],
usableEnv: { readable: [], writable: [] },
config: {}
usableEnv: { readable: [], writable: [] }
},
{ notify }
);
Expand All @@ -268,19 +408,14 @@ const ComponentRenderer: Component<ComponentRendererProps> = (props) => {
component={components()[componentName]}
contentEditable={props.contentEditable}
view={props.view}
renderView={renderView}
extension={extension}
{...componentProps}
>
{props.view.slot?.length && (
<For each={props.view.slot}>
{(view) => {
return (
<ComponentRenderer
view={view}
spec={props.spec}
contentEditable={props.contentEditable}
components={props.components}
/>
);
return renderView(view);
}}
</For>
)}
Expand Down
Loading

0 comments on commit 7f3edb5

Please sign in to comment.