diff --git a/package.json b/package.json
index feac038..89e6ae5 100644
--- a/package.json
+++ b/package.json
@@ -49,6 +49,7 @@
"react-joyride": "^2.5.3",
"react-reflex": "^4.0.9",
"react-responsive-carousel": "^3.2.23",
+ "react-syntax-highlighter": "^15.6.1",
"sjcl": "^1.0.8",
"string-to-color": "^2.2.2",
"string.prototype.replaceall": "^1.0.6",
@@ -122,5 +123,6 @@
"last 1 firefox version",
"last 1 safari version"
]
- }
+ },
+ "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
diff --git a/src/components/CodeGenerator.tsx b/src/components/CodeGenerator.tsx
new file mode 100644
index 0000000..1663d47
--- /dev/null
+++ b/src/components/CodeGenerator.tsx
@@ -0,0 +1,181 @@
+import {AlertDialog} from "@/playground-ui/AlertDialog.tsx";
+import { useMemo, useState } from "react";
+
+import SyntaxHighlighter from 'react-syntax-highlighter';
+
+import React from "react";
+import Grid from '@material-ui/core/Grid';
+import Button from "@material-ui/core/Button";
+
+function generateRelUpdateJs(yaml: string): string {
+ let result = ""
+ for (const tuple of yaml.split("\n")) {
+ const match = tuple.match(/([^#]+)#([^@]+)@(.+)/);
+ if (!match) {
+ continue
+ }
+ const [obj, rel, sub] = [match[1], match[2], match[3]]
+ const [subType, subId] = sub.split(":");
+ const [objType, objId] = obj.split(":");
+ result += `v1.RelationshipUpdate.create({
+ relationship: v1.Relationship.create({
+ resource: v1.ObjectReference.create({
+ objectType: '${objType}',
+ objectId: '${objId}',
+ }),
+ relation: '${rel}',
+ subject: v1.SubjectReference.create({
+ object: v1.ObjectReference.create({
+ objectType: '${subType}',
+ objectId: '${subId}',
+ }),
+ }),
+ }),
+ operation: v1.RelationshipUpdate_Operation.CREATE,
+ }),`
+ }
+ return result
+
+}
+
+function LangGenerator(props: {schema: string, relationshipsYaml: string, lang: string}) {
+ if (props.lang == "Go") {
+ return `package main
+
+import (
+ "context"
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+ "log"
+
+ "github.com/authzed/authzed-go/proto/authzed/api/v1"
+ "github.com/authzed/authzed-go/v1"
+ "github.com/authzed/grpcutil"
+)
+
+func main() {
+ client, err := authzed.NewClient(
+ "localhost:50051",
+ grpc.WithTransportCredentials(insecure.NewCredentials()),
+ grpcutil.WithInsecureBearerToken("your_token_here"),
+ )
+ if err != nil {
+ log.Fatalf("unable to initialize client: %s", err)
+ }
+
+ // Write the schema.
+ resp, err := client.WriteSchema(context.Background(), &v1.WriteSchemaRequest{
+ Schema: ` + "`" + props.schema + "`" + `,
+ })
+ if err != nil {
+ log.Fatalf("failed to write schema: %s", err)
+ }
+ log.Println("Written at: " + resp.WrittenAt.String())
+
+ // Write relationships.
+ // TODO
+}
+
+ `} else if (props.lang == "JS") {
+ return `import { v1 } from '@authzed/authzed-node';
+// set up it on localhost like this:
+const client = v1.NewClient('your_token_here', 'localhost:50051', v1.ClientSecurity.INSECURE_LOCALHOST_ALLOWED);
+// const client = v1.NewClient('your_token_here');
+const { promises: promiseClient } = client; // access client.promises after instantiating client
+
+const writeRequest = v1.WriteSchemaRequest.create({
+ schema: ` + "`" + props.schema + "`" + `,
+});
+
+// Write the schema.
+await new Promise((resolve, reject) => {
+ client.writeSchema(writeRequest, function (err, response) {
+ if (err) reject(err);
+ resolve(response);
+ });
+});
+
+// Write relationships.
+const writeRelationshipRequest = v1.WriteRelationshipsRequest.create({
+ updates: [` +generateRelUpdateJs(props.relationshipsYaml) + `
+ ],
+});
+
+await new Promise((resolve, reject) => {
+ client.writeRelationships(writeRelationshipRequest, function (err, response) {
+ if (err) reject(err);
+ resolve(response);
+ });
+});
+
+`
+ } else {
+ return ""
+ }
+}
+
+export function GenerateContent({ schema, relationshipsYaml, langPicked, setLangPicked }) {
+ const generatedCode = useMemo(
+ () => LangGenerator({ lang: langPicked, schema, relationshipsYaml }),
+ [langPicked, schema, relationshipsYaml]
+ );
+
+ const syntaxLang = langPicked === "Go" ? "go" : "javascript";
+
+ return (
+
+
+
+
+
+
+
+ {generatedCode}
+
+
+
+ );
+}
+
+export function CodeGenerator(props: { schema: string, relationshipsYaml: string, onClose: () => void }) {
+ const [showAlert] = useState(true);
+ const [langPicked, setLangPicked] = useState("Go");
+
+ // Always up-to-date code based on current language
+ const generatedCode = useMemo(
+ () => LangGenerator({ lang: langPicked, schema: props.schema, relationshipsYaml: props.relationshipsYaml }),
+ [langPicked, props.schema, props.relationshipsYaml]
+ );
+
+ const handleClose = () => {
+ navigator.clipboard.writeText(generatedCode)
+ props.onClose()
+ };
+
+ return (
+
+ }
+ buttonTitle="Copy"
+ />
+ );
+}
diff --git a/src/components/FullPlayground.tsx b/src/components/FullPlayground.tsx
index e2f824f..5743914 100644
--- a/src/components/FullPlayground.tsx
+++ b/src/components/FullPlayground.tsx
@@ -87,6 +87,7 @@ import { TerminalPanel, TerminalSummary } from "./panels/terminal";
import { ValidationPanel, ValidationSummary } from "./panels/validation";
import { VisualizerPanel, VisualizerSummary } from "./panels/visualizer";
import { WatchesPanel, WatchesSummary } from "./panels/watches";
+import {CodeGenerator} from "@/components/CodeGenerator.tsx";
const TOOLBAR_BREAKPOINT = 1550; // pixels
@@ -547,6 +548,40 @@ export function ThemedAppView(props: { datastore: DataStore }) {
}
};
+ function CodeGeneratorButton () {
+ const [showCodeGenerator, setShowCodeGenerator] = useState(false);
+ const [codeGenProps, setCodeGenProps] = useState({ schema: "", relationshipsYaml: "" });
+
+ const handleOpenCodeGenerator = () => {
+ const schema = datastore.getSingletonByKind(DataStoreItemKind.SCHEMA).editableContents!;
+ const relationshipsYaml = datastore.getSingletonByKind(DataStoreItemKind.RELATIONSHIPS).editableContents!;
+ if (schema && relationshipsYaml) {
+ setCodeGenProps({ schema, relationshipsYaml });
+ setShowCodeGenerator(true);
+ }
+ };
+
+ return (
+ <>
+ }
+ >
+ Generate code
+
+ {showCodeGenerator && (
+ setShowCodeGenerator(false)}
+ />
+ )}
+ >
+ );
+ }
+
const datastoreUpdated = () => {
if (sharingState.status !== SharingStatus.NOT_RUN) {
setSharingState({
@@ -772,6 +807,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
) : (
)}
+