1+ import React , { PropsWithChildren , useContext , useMemo , useState } from "react" ;
2+ import { CookiesProvider , useCookies } from 'react-cookie' ;
3+ import * as replaceAll from 'string.prototype.replaceall' ;
4+ import CodeBlock from '@theme/CodeBlock' ;
5+ import Twig from 'twig' ;
6+ import { useDebouncedChecker } from "./debouncer" ;
7+
8+ enum InstanceKind {
9+ AUTHZED = 'authzed' ,
10+ SPICEDB = 'spicedb' ,
11+ }
12+
13+ type SampleConfigContextValue = {
14+ readonly defaultTenant : string ,
15+ readonly instanceKind : ( ) => InstanceKind | undefined ,
16+ readonly setInstanceKind : ( kind : InstanceKind ) => void ,
17+ readonly token : ( ) => string | undefined ;
18+ readonly setToken : ( token : string ) => void ;
19+ readonly tenant : ( ) => string | undefined ;
20+ readonly setTenant : ( token : string ) => void ;
21+ readonly endpoint : ( ) => string | undefined ;
22+ readonly setEndpoint : ( token : string ) => void ;
23+ readonly buildTempalte : ( template : string ) => any ;
24+ } ;
25+
26+ const SampleConfigContext = React . createContext < SampleConfigContextValue | undefined > ( undefined ) ;
27+ const DEFAULT_SAMPLE_TOKEN = 't_your_token_here_1234567deadbeef' ;
28+ const DEFAULT_ENDPOINT = 'localhost:50051' ;
29+ const AUTHZED_ENDPOINT = 'grpc.authzed.com:443' ;
30+
31+ /**
32+ * SampleConfigProvider is a React context provider which holds state for SampleCodeBlocks.
33+ */
34+ export function SampleConfigProvider ( props : PropsWithChildren < { defaultTenant : string } > ) : JSX . Element {
35+ const [ tenant , setTenant ] = useState < string | undefined > ( undefined ) ;
36+ const [ token , setToken ] = useState < string | undefined > ( undefined ) ;
37+ const [ endpoint , setEndpoint ] = useState < string | undefined > ( undefined ) ;
38+ const [ instanceKind , setInstanceKind ] = useState < InstanceKind | undefined > ( undefined ) ;
39+ const [ templateCache , setTemplateCache ] = useState < Record < string , any > > ( { } ) ;
40+
41+ const contextValue = useMemo ( ( ) => {
42+ return {
43+ defaultTenant : props . defaultTenant ,
44+ token : ( ) => token ,
45+ setToken : setToken ,
46+ instanceKind : ( ) => instanceKind ,
47+ setInstanceKind : setInstanceKind ,
48+ tenant : ( ) => tenant ,
49+ setTenant : setTenant ,
50+ endpoint : ( ) => endpoint ,
51+ setEndpoint : setEndpoint ,
52+ buildTemplate : ( template : string ) => {
53+ if ( template in templateCache ) {
54+ return templateCache [ template ] ;
55+ }
56+
57+ const built = Twig . twig ( {
58+ data : template
59+ } ) ;
60+ templateCache [ template ] = built ;
61+ setTemplateCache ( templateCache ) ;
62+ return built ;
63+ }
64+ }
65+ } , [ props . defaultTenant , token , setToken , endpoint , setEndpoint , instanceKind , setInstanceKind , tenant , setTenant , templateCache , setTemplateCache ] ) ;
66+
67+ return (
68+ < SampleConfigContext . Provider value = { contextValue } >
69+ { props . children }
70+ </ SampleConfigContext . Provider >
71+ ) ;
72+ }
73+
74+ function normalizeContent ( children : React . ReactNode [ ] ) : string {
75+ return Array . isArray ( children )
76+ ? children . map ( ( child : React . ReactNode ) => {
77+ if ( React . isValidElement ( child ) ) {
78+ return normalizeContent ( child . props . children ) ;
79+ } else {
80+ return child . toString ( )
81+ }
82+ } ) . join ( '' )
83+ : ( children as string ) ;
84+ }
85+
86+ /**
87+ * SampleCodeBlock is a component which takes as its child a single *twig* template
88+ * string, to produce a CodeBlock with the values found in the SampleConfigContext.
89+ *
90+ * Exmaple:
91+ * <SampleCodeBlock lang="java">
92+ * {`class SomeClass {
93+ * ·
94+ * private int someField = 2;
95+ * ·
96+ * void DoSomething() { ... }
97+ * }` }
98+ * </SampleCodeBlock>
99+ *
100+ * NOTE the use of · on the otherwise blanks lines to ensure MDX properly parses the template
101+ * string.
102+ */
103+ export function SampleCodeBlock ( props : PropsWithChildren < { lang : string } > ) {
104+ const context = useContext ( SampleConfigContext ) ;
105+ const token = context . token ( ) ? context . token ( ) : DEFAULT_SAMPLE_TOKEN ;
106+ const endpoint = context . instanceKind ( ) !== InstanceKind . SPICEDB ? AUTHZED_ENDPOINT : ( context . endpoint ( ) ?? DEFAULT_ENDPOINT ) ;
107+
108+ let tenant = context . tenant ( ) ? context . tenant ( ) : context . defaultTenant ;
109+ tenant = tenant . replace ( '/' , '' ) ;
110+
111+ let content = normalizeContent ( props . children ) ;
112+
113+ // NOTE: due to a bug in MDX, a completely blank line is interpreted as breaking up
114+ // the template string typically passed to SampleCodeBlock. Therefore, we support ·
115+ // for otherwise blank links, to allow for MDX parsing.
116+ content = replaceAll ( content , '·' , '' ) ;
117+
118+ const template = context . buildTemplate ( content ) ;
119+ const processed = template . render ( { token : token , endpoint : endpoint , tenant : tenant , authzed : context . instanceKind ( ) !== InstanceKind . SPICEDB } ) ;
120+ return < CodeBlock className = { `language-${ props . lang } ` } > { processed } </ CodeBlock >
121+ }
122+
123+ /**
124+ * SampleConfigEditor is the editor for the values stored in the SampleConfigContext.
125+ */
126+ export function SampleConfigEditor ( ) {
127+ const context = useContext ( SampleConfigContext ) ;
128+ const instanceKind = context . instanceKind ( ) ;
129+ const [ token , setToken ] = useState ( context . token ( ) ?? '' ) ;
130+ const [ tenant , setTenant ] = useState ( context . tenant ( ) ?? '' ) ;
131+ const [ endpoint , setEndpoint ] = useState ( context . endpoint ( ) ?? '' ) ;
132+
133+ const debouncedUpdateToken = useDebouncedChecker ( 100 , context . setToken ) ;
134+ const debouncedUpdateTenant = useDebouncedChecker ( 100 , context . setTenant ) ;
135+ const debouncedUpdateEndpoint = useDebouncedChecker ( 100 , context . setEndpoint ) ;
136+
137+ const handleChangeToken = ( e : React . ChangeEvent ) => {
138+ setToken ( e . target . value ) ;
139+ debouncedUpdateToken . run ( e . target . value ) ;
140+ } ;
141+
142+ const handleChangeTenant = ( e : React . ChangeEvent ) => {
143+ setTenant ( e . target . value ) ;
144+ debouncedUpdateTenant . run ( e . target . value ) ;
145+ } ;
146+
147+ const handleChangeEndpoint = ( e : React . ChangeEvent ) => {
148+ setEndpoint ( e . target . value ) ;
149+ debouncedUpdateEndpoint . run ( e . target . value ) ;
150+ } ;
151+
152+ return < div className = "sample-config-editor" >
153+ < div className = "system-options" >
154+ < div className = "system-option" >
155+ < input type = "radio" id = "authzed" name = "system" value = "authzed"
156+ checked = { instanceKind === InstanceKind . AUTHZED }
157+ onChange = { ( ) => context . setInstanceKind ( InstanceKind . AUTHZED ) } />
158+ < label for = "authzed" > I have an < a href = "https://app.authzed.com" target = "_blank" > Authzed permissions system</ a > created</ label >
159+ </ div >
160+ < div className = "system-option" >
161+ < input type = "radio" id = "spicedb" name = "system" value = "spicedb"
162+ checked = { instanceKind === InstanceKind . SPICEDB }
163+ onChange = { ( ) => {
164+ setTenant ( undefined ) ;
165+ debouncedUpdateTenant . run ( '' ) ;
166+ context . setInstanceKind ( InstanceKind . SPICEDB ) ;
167+ } } />
168+ < label for = "spicedb" > I have a < a href = "https://github.com/authzed/spicedb" target = "_blank" > SpiceDB instance</ a > running</ label >
169+ </ div >
170+ </ div >
171+ { instanceKind !== undefined &&
172+ < div className = "system-parameters" >
173+ { instanceKind === InstanceKind . SPICEDB && < div >
174+ < label > Endpoint</ label >
175+ < input type = "text" value = { endpoint } onChange = { handleChangeEndpoint } placeholder = "localhost:50051" />
176+ < label > Preshared key</ label >
177+ < input type = "text" value = { token } onChange = { handleChangeToken } placeholder = "Enter your preshared key" />
178+ </ div > }
179+ { instanceKind === InstanceKind . AUTHZED && < div >
180+ < label > Permissions System Prefix</ label >
181+ < input type = "text" value = { tenant } onChange = { handleChangeTenant } placeholder = "mypermissionssystem/" />
182+ < label > Token</ label >
183+ < input type = "text" value = { token } onChange = { handleChangeToken } placeholder = "tc_some_token" />
184+ </ div > }
185+ </ div >
186+ }
187+ </ div > ;
188+ }
0 commit comments