Skip to content
Draft
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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-tabs": "^1.1.13",
"@reduxjs/toolkit": "^2.11.2",
"@tailwindcss/vite": "^4.2.1",
"@tanstack/react-pacer": "^0.20.0",
"@tanstack/react-router": "^1.163.2",
"@tanstack/react-router-devtools": "^1.163.2",
"@vercel/node": "^5.2.0",
"@xyflow/react": "^12.10.1",
"ajv": "8.18.0",
"ansi-to-html": "^0.7.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
Expand All @@ -58,6 +58,7 @@
"react-cookie": "^8.0.1",
"react-dom": "^18.3.1",
"react-joyride": "^2.5.3",
"react-redux": "^9.2.0",
"react-reflex": "^4.2.7",
"react-responsive-carousel": "^3.2.23",
"sjcl": "^1.0.8",
Expand Down
4 changes: 4 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { useGoogleAnalytics } from "./playground-ui/GoogleAnalyticsHook";
import PlaygroundUIThemed from "./playground-ui/PlaygroundUIThemed";
import AppConfig from "./services/configservice";
import { PLAYGROUND_UI_COLORS } from "./theme";
import { store } from './store'
import { Provider as ReduxProvider } from 'react-redux'

const rootRoute = createRootRoute({
component: () => (
Expand Down Expand Up @@ -81,6 +83,7 @@ function App() {
<Toaster />
{/* @ts-ignore-error react-cookie's types are screwy; CI and (local and vercel) disagree about whether there's an error or not. */}
<CookiesProvider>
<ReduxProvider store={store}>
<PHProvider>
<ThemeProvider>
<PlaygroundUIThemed {...PLAYGROUND_UI_COLORS} forceDarkMode={isEmbeddedPlayground}>
Expand All @@ -90,6 +93,7 @@ function App() {
</PlaygroundUIThemed>
</ThemeProvider>
</PHProvider>
</ReduxProvider>
</CookiesProvider>
</>
);
Expand Down
23 changes: 7 additions & 16 deletions src/components/DatastoreRelationshipEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Theme } from "@glideapps/glide-data-grid";
import { useDebouncedCallback } from "@tanstack/react-pacer/debouncer";
import { useCallback, useMemo, useState } from "react";
import { useMemo, useState } from "react";
import useDeepCompareEffect from "use-deep-compare-effect";

import {
DeveloperError,
DeveloperError_Source,
} from "@/spicedb-common/protodefs/developer/v1/developer_pb";

import { DataStore, DataStoreItemKind } from "../services/datastore";
import { Services } from "../services/services";
import { RelationshipOrComment, parseRelationshipsAndComments } from "../spicedb-common/parsing";

Expand All @@ -22,6 +21,8 @@ import {
RelationTupleHighlight,
RelationshipEditor,
} from "./relationshipeditor/RelationshipEditor";
import { useAppDispatch, useAppSelector } from "@/hooks";
import { setRelationships } from "@/store/editorSlice";

const partialRelationshipCommentPrefix = "partial relationship: ";

Expand Down Expand Up @@ -62,30 +63,22 @@ function toRelationshipsString(data: RelationshipDatum[]): string {
}

export type DatastoreRelationshipEditorProps = {
datastore: DataStore;
services: Services;
isReadOnly: boolean;
datastoreUpdated: () => void;
themeOverrides?: Partial<Theme> | undefined;
} & { dimensions?: { width: number; height: number } };

export function DatastoreRelationshipEditor(props: DatastoreRelationshipEditorProps) {
const relationshipsString = useAppSelector(state => state.editor.relationships)
const dispatch = useAppDispatch();
const debouncedUpdateDatastore = useDebouncedCallback(
(data: RelationshipDatum[]) => {
const editableContents = toRelationshipsString(data);
props.datastore.update(relationshipsItem, editableContents);
props.datastoreUpdated();
dispatch(setRelationships(editableContents))
},
{ wait: 50 },
);

const handleDataUpdated = useCallback(
(data: RelationshipDatum[]) => {
debouncedUpdateDatastore(data);
},
[debouncedUpdateDatastore],
);

const relErrors = useMemo(() => {
return props.services.problemService.requestErrors.filter(
(error: DeveloperError) => error.source === DeveloperError_Source.RELATIONSHIP,
Expand All @@ -105,8 +98,6 @@ export function DatastoreRelationshipEditor(props: DatastoreRelationshipEditorPr
);
}, [relErrors]);

const relationshipsItem = props.datastore.getSingletonByKind(DataStoreItemKind.RELATIONSHIPS);
const relationshipsString = relationshipsItem.editableContents;
const relationshipData: RelationshipDatum[] = useMemo(() => {
return fromRelationshipsString(relationshipsString);
}, [relationshipsString]);
Expand All @@ -117,7 +108,7 @@ export function DatastoreRelationshipEditor(props: DatastoreRelationshipEditorPr
relationshipData={relationshipData}
resolver={props.services.localParseService.state.resolver}
highlights={highlights}
dataUpdated={handleDataUpdated}
dataUpdated={debouncedUpdateDatastore}
isReadOnly={props.isReadOnly}
/>
);
Expand Down
69 changes: 23 additions & 46 deletions src/components/EditorDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import "react-reflex/styles.css";
import { useMediaQuery } from "@/hooks/use-media-query";

import { ScrollLocation, useCookieService } from "../services/cookieservice";
import { DataStore, DataStoreItem, DataStoreItemKind } from "../services/datastore";
import { LocalParseState } from "../services/localparse";
import { useLocalParseService } from "../services/localparse";
import { Services } from "../services/services";
import registerDSLanguage, {
DS_DARK_THEME_NAME,
Expand All @@ -31,13 +30,11 @@ import registerTupleLanguage, {
TUPLE_LANGUAGE_NAME,
TUPLE_THEME_NAME,
} from "./relationshipeditor/tuplelang";
import { PATHS } from "@/constants";

export type EditorDisplayProps = {
datastore: DataStore;
services: Services;
currentItem: DataStoreItem | undefined;
isReadOnly: boolean;
datastoreUpdated: () => void;
defaultWidth?: string;
defaultHeight?: string;
disableMouseWheelScrolling?: boolean;
Expand All @@ -54,34 +51,37 @@ interface LocationState {
range?: TextRange | undefined;
}

const languageNameMap = {
[PATHS.SCHEMA]: DS_LANGUAGE_NAME,
[PATHS.RELATIONSHIPS]: TUPLE_LANGUAGE_NAME,
[PATHS.ASSERTIONS]: "yaml",
[PATHS.EXPECTED_RELATIONS]: "yaml",
}

/**
* EditorDisplays display the editor in the playground.
*/
export function EditorDisplay(props: EditorDisplayProps) {
const monacoRef = useMonaco();
const [monacoReady, setMonacoReady] = useState(false);
const [localIndex, setLocalIndex] = useState(0);
const localParseState = useRef<LocalParseState>(props.services.localParseService.state);
// TODO: this should be global?
const { resolver } = useLocalParseService()

// Effect: Register the languages in monaco.
useEffect(() => {
if (monacoRef) {
registerDSLanguage(monacoRef);
registerTupleLanguage(monacoRef, () => localParseState.current);
if (resolver) {
registerTupleLanguage(monacoRef, resolver);
}
setMonacoReady(true);
}
}, [monacoRef]);

useEffect(() => {
localParseState.current = props.services.localParseService.state;
}, [props.services.localParseService.state]);
}, [monacoRef, resolver]);

const navigate = useNavigate();
const location = useLocation();

const datastore = props.datastore;
const currentItem = props.currentItem;

const editorRefs = useRef<Record<string, monaco.editor.IStandaloneCodeEditor>>({});

// Select the theme and language.
Expand All @@ -92,20 +92,20 @@ export function EditorDisplay(props: EditorDisplayProps) {
return props.themeName;
}

switch (currentItem?.kind) {
case DataStoreItemKind.SCHEMA:
switch (location.pathname) {
case PATHS.SCHEMA:
// Schema.
return prefersDarkMode ? DS_DARK_THEME_NAME : DS_THEME_NAME;

case DataStoreItemKind.RELATIONSHIPS:
case PATHS.RELATIONSHIPS:
// Validation tuples.
return prefersDarkMode ? TUPLE_DARK_THEME_NAME : TUPLE_THEME_NAME;

case DataStoreItemKind.EXPECTED_RELATIONS:
case PATHS.EXPECTED_RELATIONS:
// Expected Relations YAML.
return prefersDarkMode ? "vs-dark" : "vs";

case DataStoreItemKind.ASSERTIONS:
case PATHS.ASSERTIONS:
// Assertions YAML.
return prefersDarkMode ? "vs-dark" : "vs";

Expand All @@ -114,33 +114,12 @@ export function EditorDisplay(props: EditorDisplayProps) {
return prefersDarkMode ? DS_DARK_THEME_NAME : DS_THEME_NAME;

default:
console.log(`Unknown item kind ${currentItem?.kind} in theme name`);
console.log(`Unknown path ${location.pathname} in theme name`);
return "vs";
}
}, [prefersDarkMode, currentItem?.kind, props.themeName]);

const languageName = useMemo(() => {
switch (currentItem?.kind) {
case DataStoreItemKind.SCHEMA:
return DS_LANGUAGE_NAME;
}, [prefersDarkMode, location.pathname, props.themeName]);

case DataStoreItemKind.RELATIONSHIPS:
// Validation tuples.
return TUPLE_LANGUAGE_NAME;

case DataStoreItemKind.EXPECTED_RELATIONS:
// Expected Relations => YAML.
return "yaml";

case DataStoreItemKind.ASSERTIONS:
// Assertions => YAML.
return "yaml";

default:
console.log("Unknown item kind in language name");
return "yaml";
}
}, [currentItem?.kind]);
const languageName = languageNameMap[location.pathname] || "yaml";

const handleEditorChange = (value: string | undefined) => {
if (props.services.validationService.isRunning || props.isReadOnly) {
Expand All @@ -164,8 +143,6 @@ export function EditorDisplay(props: EditorDisplayProps) {
if (updated && updated.pathname !== location.pathname) {
void navigate({ to: updated.pathname, replace: true });
}

props.datastoreUpdated();
});
};

Expand Down
Loading
Loading