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