1- import React , { useState } from "react" ;
1+ import React , { useState , useCallback , useRef } from "react" ;
22import {
33 Checkbox ,
44 InputGroup ,
@@ -10,6 +10,8 @@ import {
1010} from "@blueprintjs/core" ;
1111import Description from "roamjs-components/components/Description" ;
1212import idToTitle from "roamjs-components/util/idToTitle" ;
13+ import useSingleChildValue from "roamjs-components/components/ConfigPanels/useSingleChildValue" ;
14+ import getShallowTreeByParentUid from "roamjs-components/queries/getShallowTreeByParentUid" ;
1315import {
1416 getGlobalSetting ,
1517 setGlobalSetting ,
@@ -20,6 +22,12 @@ import {
2022} from "../utils/accessors" ;
2123import type { FeatureFlags } from "../utils/zodSchema" ;
2224
25+ type RoamBlockSyncProps = {
26+ parentUid ?: string ;
27+ uid ?: string ;
28+ order ?: number ;
29+ } ;
30+
2331type TextGetter = ( keys : string [ ] ) => string | undefined ;
2432type TextSetter = ( keys : string [ ] , value : string ) => void ;
2533
@@ -40,7 +48,7 @@ type BaseTextPanelProps = {
4048 setter : TextSetter ;
4149 defaultValue ?: string ;
4250 placeholder ?: string ;
43- } ;
51+ } & RoamBlockSyncProps ;
4452
4553type BaseFlagPanelProps = {
4654 title : string ;
@@ -52,7 +60,7 @@ type BaseFlagPanelProps = {
5260 disabled ?: boolean ;
5361 onBeforeChange ?: ( checked : boolean ) => Promise < boolean > ;
5462 onChange ?: ( checked : boolean ) => void ;
55- } ;
63+ } & RoamBlockSyncProps ;
5664
5765type BaseNumberPanelProps = {
5866 title : string ;
@@ -63,7 +71,7 @@ type BaseNumberPanelProps = {
6371 defaultValue ?: number ;
6472 min ?: number ;
6573 max ?: number ;
66- } ;
74+ } & RoamBlockSyncProps ;
6775
6876type BaseSelectPanelProps = {
6977 title : string ;
@@ -73,7 +81,7 @@ type BaseSelectPanelProps = {
7381 setter : TextSetter ;
7482 options : string [ ] ;
7583 defaultValue ?: string ;
76- } ;
84+ } & RoamBlockSyncProps ;
7785
7886type BaseMultiTextPanelProps = {
7987 title : string ;
@@ -82,8 +90,7 @@ type BaseMultiTextPanelProps = {
8290 getter : MultiTextGetter ;
8391 setter : MultiTextSetter ;
8492 defaultValue ?: string [ ] ;
85- } ;
86-
93+ } & RoamBlockSyncProps ;
8794
8895const BaseTextPanel = ( {
8996 title,
@@ -93,13 +100,33 @@ const BaseTextPanel = ({
93100 setter,
94101 defaultValue = "" ,
95102 placeholder,
103+ parentUid,
104+ uid,
105+ order,
96106} : BaseTextPanelProps ) => {
97107 const [ value , setValue ] = useState ( ( ) => getter ( settingKeys ) ?? defaultValue ) ;
108+ const hasBlockSync = parentUid !== undefined && order !== undefined ;
109+ const { onChange : syncToBlock } = useSingleChildValue ( {
110+ title,
111+ parentUid : parentUid ?? "" ,
112+ order : order ?? 0 ,
113+ uid,
114+ defaultValue,
115+ transform : ( s : string ) => s ,
116+ toStr : ( s : string ) => s ,
117+ } ) ;
98118
99119 const handleChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
100120 const newValue = e . target . value ;
101121 setValue ( newValue ) ;
102122 setter ( settingKeys , newValue ) ;
123+ if ( hasBlockSync ) {
124+ syncToBlock ( newValue ) ;
125+ }
126+ // TODO: remove debug logging
127+ setTimeout ( ( ) => {
128+ console . log ( `[TextPanel] "${ title } " blockProp:` , getter ( settingKeys ) ) ;
129+ } , 500 ) ;
103130 } ;
104131
105132 return (
@@ -125,8 +152,34 @@ const BaseFlagPanel = ({
125152 disabled = false ,
126153 onBeforeChange,
127154 onChange,
155+ parentUid,
156+ uid : initialBlockUid ,
157+ order,
128158} : BaseFlagPanelProps ) => {
129159 const [ value , setValue ] = useState ( ( ) => getter ( settingKeys ) ?? defaultValue ) ;
160+ const blockUidRef = useRef ( initialBlockUid ) ;
161+
162+ const syncFlagToBlock = useCallback (
163+ ( checked : boolean ) => {
164+ if ( parentUid === undefined || order === undefined ) return ;
165+ if ( checked ) {
166+ if ( blockUidRef . current ) return ;
167+ const newUid = window . roamAlphaAPI . util . generateUID ( ) ;
168+ void window . roamAlphaAPI . createBlock ( {
169+ block : { string : title , uid : newUid } ,
170+ // eslint-disable-next-line @typescript-eslint/naming-convention
171+ location : { order, "parent-uid" : parentUid } ,
172+ } ) ;
173+ blockUidRef . current = newUid ;
174+ } else if ( blockUidRef . current ) {
175+ void window . roamAlphaAPI . deleteBlock ( {
176+ block : { uid : blockUidRef . current } ,
177+ } ) ;
178+ blockUidRef . current = undefined ;
179+ }
180+ } ,
181+ [ title , parentUid , order ] ,
182+ ) ;
130183
131184 const handleChange = async ( e : React . FormEvent < HTMLInputElement > ) => {
132185 const { checked } = e . target as HTMLInputElement ;
@@ -138,7 +191,12 @@ const BaseFlagPanel = ({
138191
139192 setValue ( checked ) ;
140193 setter ( settingKeys , checked ) ;
194+ syncFlagToBlock ( checked ) ;
141195 onChange ?.( checked ) ;
196+ // TODO: remove debug logging
197+ setTimeout ( ( ) => {
198+ console . log ( `[FlagPanel] "${ title } " blockProp:` , getter ( settingKeys ) ) ;
199+ } , 500 ) ;
142200 } ;
143201
144202 return (
@@ -165,13 +223,33 @@ const BaseNumberPanel = ({
165223 defaultValue = 0 ,
166224 min,
167225 max,
226+ parentUid,
227+ uid,
228+ order,
168229} : BaseNumberPanelProps ) => {
169230 const [ value , setValue ] = useState ( ( ) => getter ( settingKeys ) ?? defaultValue ) ;
231+ const hasBlockSync = parentUid !== undefined && order !== undefined ;
232+ const { onChange : syncToBlock } = useSingleChildValue ( {
233+ title,
234+ parentUid : parentUid ?? "" ,
235+ order : order ?? 0 ,
236+ uid,
237+ defaultValue,
238+ transform : ( s : string ) => parseInt ( s , 10 ) ,
239+ toStr : ( v : number ) => `${ v } ` ,
240+ } ) ;
170241
171242 const handleChange = ( valueAsNumber : number ) => {
172243 if ( Number . isNaN ( valueAsNumber ) ) return ;
173244 setValue ( valueAsNumber ) ;
174245 setter ( settingKeys , valueAsNumber ) ;
246+ if ( hasBlockSync ) {
247+ syncToBlock ( valueAsNumber ) ;
248+ }
249+ // TODO: remove debug logging
250+ setTimeout ( ( ) => {
251+ console . log ( `[NumberPanel] "${ title } " blockProp:` , getter ( settingKeys ) ) ;
252+ } , 500 ) ;
175253 } ;
176254
177255 return (
@@ -197,22 +275,47 @@ const BaseSelectPanel = ({
197275 setter,
198276 options,
199277 defaultValue,
278+ parentUid,
279+ uid,
280+ order,
200281} : BaseSelectPanelProps ) => {
201282 const [ value , setValue ] = useState (
202283 ( ) => getter ( settingKeys ) ?? defaultValue ?? options [ 0 ] ,
203284 ) ;
285+ const hasBlockSync = parentUid !== undefined && order !== undefined ;
286+ const { onChange : syncToBlock } = useSingleChildValue ( {
287+ title,
288+ parentUid : parentUid ?? "" ,
289+ order : order ?? 0 ,
290+ uid,
291+ defaultValue : defaultValue ?? options [ 0 ] ?? "" ,
292+ transform : ( s : string ) => s ,
293+ toStr : ( s : string ) => s ,
294+ } ) ;
204295
205296 const handleChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
206297 const newValue = e . target . value ;
207298 setValue ( newValue ) ;
208299 setter ( settingKeys , newValue ) ;
300+ if ( hasBlockSync ) {
301+ syncToBlock ( newValue ) ;
302+ }
303+ // TODO: remove debug logging
304+ setTimeout ( ( ) => {
305+ console . log ( `[SelectPanel] "${ title } " blockProp:` , getter ( settingKeys ) ) ;
306+ } , 500 ) ;
209307 } ;
210308
211309 return (
212310 < Label >
213311 { idToTitle ( title ) }
214312 < Description description = { description } />
215- < HTMLSelect value = { value } onChange = { handleChange } fill options = { options } />
313+ < HTMLSelect
314+ value = { value }
315+ onChange = { handleChange }
316+ fill
317+ options = { options }
318+ />
216319 </ Label >
217320 ) ;
218321} ;
@@ -224,18 +327,67 @@ const BaseMultiTextPanel = ({
224327 getter,
225328 setter,
226329 defaultValue = [ ] ,
330+ parentUid,
331+ uid : initialBlockUid ,
332+ order,
227333} : BaseMultiTextPanelProps ) => {
228334 const [ values , setValues ] = useState < string [ ] > (
229335 ( ) => getter ( settingKeys ) ?? defaultValue ,
230336 ) ;
231337 const [ inputValue , setInputValue ] = useState ( "" ) ;
338+ const hasBlockSync = parentUid !== undefined && order !== undefined ;
339+ const blockUidRef = useRef ( initialBlockUid ) ;
340+ const childUidsRef = useRef < string [ ] > (
341+ initialBlockUid
342+ ? getShallowTreeByParentUid ( initialBlockUid ) . map (
343+ ( c : { uid : string } ) => c . uid ,
344+ )
345+ : [ ] ,
346+ ) ;
232347
233- const handleAdd = ( ) => {
348+ const ensureParentBlock = useCallback ( async ( ) : Promise <
349+ string | undefined
350+ > => {
351+ if ( blockUidRef . current ) return blockUidRef . current ;
352+ if ( parentUid === undefined || order === undefined ) return undefined ;
353+ const newUid = window . roamAlphaAPI . util . generateUID ( ) ;
354+ await window . roamAlphaAPI . createBlock ( {
355+ block : { string : title , uid : newUid } ,
356+ // eslint-disable-next-line @typescript-eslint/naming-convention
357+ location : { order, "parent-uid" : parentUid } ,
358+ } ) ;
359+ blockUidRef . current = newUid ;
360+ return newUid ;
361+ } , [ title , parentUid , order ] ) ;
362+
363+ const handleAdd = async ( ) => {
234364 if ( inputValue . trim ( ) && ! values . includes ( inputValue . trim ( ) ) ) {
235- const newValues = [ ...values , inputValue . trim ( ) ] ;
365+ const trimmed = inputValue . trim ( ) ;
366+ const newValues = [ ...values , trimmed ] ;
236367 setValues ( newValues ) ;
237368 setter ( settingKeys , newValues ) ;
238369 setInputValue ( "" ) ;
370+
371+ const parent = await ensureParentBlock ( ) ;
372+ if ( parent ) {
373+ const valueUid = window . roamAlphaAPI . util . generateUID ( ) ;
374+ void window . roamAlphaAPI . createBlock ( {
375+ block : { string : trimmed , uid : valueUid } ,
376+ location : {
377+ order : childUidsRef . current . length ,
378+ // eslint-disable-next-line @typescript-eslint/naming-convention
379+ "parent-uid" : parent ,
380+ } ,
381+ } ) ;
382+ childUidsRef . current = [ ...childUidsRef . current , valueUid ] ;
383+ }
384+ // TODO: remove debug logging
385+ setTimeout ( ( ) => {
386+ console . log (
387+ `[MultiTextPanel] "${ title } " blockProp:` ,
388+ getter ( settingKeys ) ,
389+ ) ;
390+ } , 500 ) ;
239391 }
240392 } ;
241393
@@ -244,12 +396,30 @@ const BaseMultiTextPanel = ({
244396 const newValues = values . filter ( ( _ , i ) => i !== index ) ;
245397 setValues ( newValues ) ;
246398 setter ( settingKeys , newValues ) ;
399+
400+ if ( hasBlockSync ) {
401+ const removedUid = childUidsRef . current [ index ] ;
402+ if ( removedUid ) {
403+ void window . roamAlphaAPI . deleteBlock ( { block : { uid : removedUid } } ) ;
404+ }
405+ childUidsRef . current = childUidsRef . current . filter (
406+ // eslint-disable-next-line @typescript-eslint/naming-convention
407+ ( _ , i ) => i !== index ,
408+ ) ;
409+ }
410+ // TODO: remove debug logging
411+ setTimeout ( ( ) => {
412+ console . log (
413+ `[MultiTextPanel] "${ title } " blockProp:` ,
414+ getter ( settingKeys ) ,
415+ ) ;
416+ } , 500 ) ;
247417 } ;
248418
249419 const handleKeyDown = ( e : React . KeyboardEvent ) => {
250420 if ( e . key === "Enter" ) {
251421 e . preventDefault ( ) ;
252- handleAdd ( ) ;
422+ void handleAdd ( ) ;
253423 }
254424 } ;
255425
@@ -265,7 +435,11 @@ const BaseMultiTextPanel = ({
265435 placeholder = "Add new item..."
266436 className = "flex-grow"
267437 />
268- < Button icon = "plus" onClick = { handleAdd } disabled = { ! inputValue . trim ( ) } />
438+ < Button
439+ icon = "plus"
440+ onClick = { ( ) => void handleAdd ( ) }
441+ disabled = { ! inputValue . trim ( ) }
442+ />
269443 </ div >
270444 { values . length > 0 && (
271445 < div className = "mt-2 flex flex-wrap gap-1" >
@@ -337,15 +511,16 @@ export const FeatureFlagPanel = ({
337511 onBeforeEnable ?: ( ) => Promise < boolean > ;
338512 onAfterChange ?: ( checked : boolean ) => void ;
339513} ) => {
340- const handleBeforeChange : ( ( checked : boolean ) => Promise < boolean > ) | undefined =
341- onBeforeEnable
342- ? async ( checked ) => {
343- if ( checked ) {
344- return onBeforeEnable ( ) ;
345- }
346- return true ;
514+ const handleBeforeChange :
515+ | ( ( checked : boolean ) => Promise < boolean > )
516+ | undefined = onBeforeEnable
517+ ? async ( checked ) => {
518+ if ( checked ) {
519+ return onBeforeEnable ( ) ;
347520 }
348- : undefined ;
521+ return true ;
522+ }
523+ : undefined ;
349524
350525 return (
351526 < BaseFlagPanel
0 commit comments