Skip to content

Commit 80edcbb

Browse files
committed
feat: allow toggling code/result visibility in non-editable blocks
- Add local state management for code and result visibility in non-editable blocks - Remove dependency on isEditable prop for visibility toggle buttons - Maintain dashboard mode controls while supporting local visibility state - Clean up unused isEditable prop from ExecutionStatusText components This change allows users to toggle code and result visibility even when blocks are not editable, storing the state locally instead of persisting it to the block.
1 parent 9306f56 commit 80edcbb

File tree

3 files changed

+187
-89
lines changed

3 files changed

+187
-89
lines changed

apps/web/src/components/ExecutionStatusText.tsx

+61-19
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,54 @@ import {
33
CheckCircleIcon,
44
CloudArrowDownIcon,
55
Cog8ToothIcon,
6+
CheckIcon,
7+
ChevronRightIcon,
8+
ChevronDownIcon,
69
} from '@heroicons/react/20/solid'
710
import { useEffect, useState } from 'react'
11+
import clsx from 'clsx'
12+
13+
function formatExecutionTime(time: string): string {
14+
return format(new Date(time), 'h:mm a - do MMM, yyyy')
15+
}
816

917
type StartExecutionStatusTextProps = {
1018
startExecutionTime: string | null
1119
}
1220

13-
type LastExecutedStatusTextProps = {
21+
interface LastExecutedStatusTextProps {
1422
lastExecutionTime: string
23+
isResultHidden: boolean
24+
onToggleResultHidden: () => void
1525
}
1626

17-
export const QuerySucceededText = ({
27+
export function QuerySucceededText({
1828
lastExecutionTime,
19-
}: LastExecutedStatusTextProps) => {
29+
isResultHidden,
30+
onToggleResultHidden,
31+
}: LastExecutedStatusTextProps) {
2032
return (
21-
<span className="font-syne text-gray-300 text-xs flex items-center select-none">
22-
<CheckCircleIcon className="w-4 h-4 mr-1" />
23-
<span className="pt-0.5">
24-
This query was last executed at{' '}
25-
{format(new Date(lastExecutionTime), "h:mm a '-' do MMM, yyyy")}
26-
</span>
27-
</span>
33+
<div className="flex items-center gap-x-1 text-gray-400">
34+
<div className="relative group w-4 h-4">
35+
<CheckCircleIcon
36+
className={clsx(
37+
'absolute inset-0 h-4 w-4',
38+
'group-hover:opacity-0 transition-opacity'
39+
)}
40+
/>
41+
<button
42+
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
43+
onClick={onToggleResultHidden}
44+
>
45+
{isResultHidden ? (
46+
<ChevronRightIcon className="h-4 w-4" />
47+
) : (
48+
<ChevronDownIcon className="h-4 w-4" />
49+
)}
50+
</button>
51+
</div>
52+
<span>Succeeded in {formatExecutionTime(lastExecutionTime)}</span>
53+
</div>
2854
)
2955
}
3056

@@ -95,16 +121,32 @@ export const ExecutingPythonText = ({
95121
)
96122
}
97123

98-
export const PythonSucceededText = ({
124+
export function PythonSucceededText({
99125
lastExecutionTime,
100-
}: LastExecutedStatusTextProps) => {
126+
isResultHidden,
127+
onToggleResultHidden,
128+
}: LastExecutedStatusTextProps) {
101129
return (
102-
<span className="font-syne text-gray-300 text-xs flex items-center select-none">
103-
<CheckCircleIcon className="w-4 h-4 mr-1" />
104-
<span className="pt-0.5">
105-
This code was last executed at{' '}
106-
{format(new Date(lastExecutionTime), "h:mm a '-' do MMM, yyyy")}
107-
</span>
108-
</span>
130+
<div className="flex items-center gap-x-1 text-gray-400">
131+
<div className="relative group w-4 h-4">
132+
<CheckCircleIcon
133+
className={clsx(
134+
'absolute inset-0 h-4 w-4',
135+
'group-hover:opacity-0 transition-opacity'
136+
)}
137+
/>
138+
<button
139+
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
140+
onClick={onToggleResultHidden}
141+
>
142+
{isResultHidden ? (
143+
<ChevronRightIcon className="h-4 w-4" />
144+
) : (
145+
<ChevronDownIcon className="h-4 w-4" />
146+
)}
147+
</button>
148+
</div>
149+
<span>Succeeded in {formatExecutionTime(lastExecutionTime)}</span>
150+
</div>
109151
)
110152
}

apps/web/src/components/v2Editor/customBlocks/python/index.tsx

+66-37
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import {
2525
import clsx from 'clsx'
2626
import type { ApiDocument, ApiWorkspace } from '@briefer/database'
2727
import { useEnvironmentStatus } from '@/hooks/useEnvironmentStatus'
28-
import { RefObject, useCallback, useMemo } from 'react'
28+
import { RefObject, useCallback, useMemo, useState } from 'react'
2929
import {
3030
ExecutingPythonText,
3131
LoadingEnvText,
@@ -91,19 +91,39 @@ function PythonBlock(props: Props) {
9191
startedAt: environmentStartedAt,
9292
} = useEnvironmentStatus(props.document.workspaceId)
9393

94+
const [localResultHidden, setLocalResultHidden] = useState<boolean | null>(
95+
null
96+
)
97+
9498
const toggleResultHidden = useCallback(() => {
95-
props.block.doc?.transact(() => {
96-
const currentIsResultHidden = props.block.getAttribute('isResultHidden')
97-
props.block.setAttribute('isResultHidden', !currentIsResultHidden)
98-
})
99-
}, [props.block])
99+
if (props.isEditable) {
100+
props.block.doc?.transact(() => {
101+
const currentIsResultHidden = props.block.getAttribute('isResultHidden')
102+
props.block.setAttribute('isResultHidden', !currentIsResultHidden)
103+
})
104+
} else {
105+
setLocalResultHidden((prev) => {
106+
const blockResultHidden = props.block.getAttribute('isResultHidden')
107+
return prev === null ? !blockResultHidden : !prev
108+
})
109+
}
110+
}, [props.block, props.isEditable])
111+
112+
const [localCodeHidden, setLocalCodeHidden] = useState<boolean | null>(null)
100113

101114
const toggleCodeHidden = useCallback(() => {
102-
props.block.doc?.transact(() => {
103-
const currentIsCodeHidden = props.block.getAttribute('isCodeHidden')
104-
props.block.setAttribute('isCodeHidden', !currentIsCodeHidden)
105-
})
106-
}, [props.block])
115+
if (props.isEditable) {
116+
props.block.doc?.transact(() => {
117+
const currentIsCodeHidden = props.block.getAttribute('isCodeHidden')
118+
props.block.setAttribute('isCodeHidden', !currentIsCodeHidden)
119+
})
120+
} else {
121+
setLocalCodeHidden((prev) => {
122+
const blockCodeHidden = props.block.getAttribute('isCodeHidden')
123+
return prev === null ? !blockCodeHidden : !prev
124+
})
125+
}
126+
}, [props.block, props.isEditable])
107127

108128
const executions = useBlockExecutions(
109129
props.executionQueue,
@@ -189,12 +209,34 @@ function PythonBlock(props: Props) {
189209
const startQueryTime = props.block.getAttribute('startQueryTime')
190210
const lastQueryTime = props.block.getAttribute('lastQueryTime')
191211

212+
const isCodeHidden =
213+
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode)) &&
214+
(props.isEditable
215+
? props.block.getAttribute('isCodeHidden') ?? false
216+
: localCodeHidden === null
217+
? props.block.getAttribute('isCodeHidden') ?? false
218+
: localCodeHidden)
219+
220+
const isResultHidden =
221+
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode)) &&
222+
(props.isEditable
223+
? props.block.getAttribute('isResultHidden') ?? false
224+
: localResultHidden === null
225+
? props.block.getAttribute('isResultHidden') ?? false
226+
: localResultHidden)
227+
192228
const queryStatusText: JSX.Element | null = useMemo(() => {
193229
switch (status) {
194230
case 'idle':
195231
case 'completed':
196232
if (source?.toJSON() === lastQuery && lastQueryTime) {
197-
return <PythonSucceededText lastExecutionTime={lastQueryTime} />
233+
return (
234+
<PythonSucceededText
235+
lastExecutionTime={lastQueryTime}
236+
isResultHidden={isResultHidden ?? false}
237+
onToggleResultHidden={toggleResultHidden}
238+
/>
239+
)
198240
}
199241
return null
200242
case 'running':
@@ -217,15 +259,10 @@ function PythonBlock(props: Props) {
217259
lastQueryTime,
218260
source.toJSON(),
219261
envStatus,
262+
isResultHidden,
263+
toggleResultHidden,
220264
])
221265

222-
const isCodeHidden =
223-
props.block.getAttribute('isCodeHidden') &&
224-
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode))
225-
const isResultHidden =
226-
props.block.getAttribute('isResultHidden') &&
227-
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode))
228-
229266
const { title } = getBaseAttributes(props.block)
230267
const onChangeTitle = useCallback(
231268
(e: React.ChangeEvent<HTMLInputElement>) => {
@@ -457,25 +494,17 @@ function PythonBlock(props: Props) {
457494
<div className="flex items-center justify-between px-3 pr-4 gap-x-4 font-sans h-12">
458495
<div className="select-none text-gray-300 text-xs flex items-center w-full h-full gap-x-1.5">
459496
<div className="relative group w-4 h-4">
460-
<CommandLineIcon
461-
className={clsx(
462-
'absolute inset-0 h-4 w-4 text-gray-400',
463-
props.isEditable &&
464-
'group-hover:opacity-0 transition-opacity'
497+
<CommandLineIcon className="absolute inset-0 h-4 w-4 text-gray-400 group-hover:opacity-0 transition-opacity" />
498+
<button
499+
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
500+
onClick={toggleCodeHidden}
501+
>
502+
{isCodeHidden ? (
503+
<ChevronRightIcon className="h-4 w-4" />
504+
) : (
505+
<ChevronDownIcon className="h-4 w-4" />
465506
)}
466-
/>
467-
{props.isEditable && (
468-
<button
469-
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
470-
onClick={toggleCodeHidden}
471-
>
472-
{isCodeHidden ? (
473-
<ChevronRightIcon className="h-4 w-4" />
474-
) : (
475-
<ChevronDownIcon className="h-4 w-4" />
476-
)}
477-
</button>
478-
)}
507+
</button>
479508
</div>
480509
<input
481510
type="text"

apps/web/src/components/v2Editor/customBlocks/sql/index.tsx

+60-33
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,39 @@ function SQLBlock(props: Props) {
115115
)
116116
}, [currentWorkspace, properties.data])
117117

118+
const [localResultHidden, setLocalResultHidden] = useState<boolean | null>(
119+
null
120+
)
121+
118122
const toggleResultHidden = useCallback(() => {
119-
props.block.doc?.transact(() => {
120-
const currentIsResultHidden = props.block.getAttribute('isResultHidden')
121-
props.block.setAttribute('isResultHidden', !currentIsResultHidden)
122-
})
123-
}, [props.block])
123+
if (props.isEditable) {
124+
props.block.doc?.transact(() => {
125+
const currentIsResultHidden = props.block.getAttribute('isResultHidden')
126+
props.block.setAttribute('isResultHidden', !currentIsResultHidden)
127+
})
128+
} else {
129+
setLocalResultHidden((prev) => {
130+
const blockResultHidden = props.block.getAttribute('isResultHidden')
131+
return prev === null ? !blockResultHidden : !prev
132+
})
133+
}
134+
}, [props.block, props.isEditable])
135+
136+
const [localCodeHidden, setLocalCodeHidden] = useState<boolean | null>(null)
124137

125138
const toggleCodeHidden = useCallback(() => {
126-
props.block.doc?.transact(() => {
127-
const currentIsCodeHidden = props.block.getAttribute('isCodeHidden')
128-
props.block.setAttribute('isCodeHidden', !currentIsCodeHidden)
129-
})
130-
}, [props.block])
139+
if (props.isEditable) {
140+
props.block.doc?.transact(() => {
141+
const currentIsCodeHidden = props.block.getAttribute('isCodeHidden')
142+
props.block.setAttribute('isCodeHidden', !currentIsCodeHidden)
143+
})
144+
} else {
145+
setLocalCodeHidden((prev) => {
146+
const blockCodeHidden = props.block.getAttribute('isCodeHidden')
147+
return prev === null ? !blockCodeHidden : !prev
148+
})
149+
}
150+
}, [props.block, props.isEditable])
131151

132152
const [selectedCode, setSelectedCode] = useState<string | null>(null)
133153
const onSQLSelectionChanged = useCallback((selectedCode: string | null) => {
@@ -153,11 +173,20 @@ function SQLBlock(props: Props) {
153173
} = getSQLAttributes(props.block, props.blocks)
154174

155175
const isCodeHidden =
156-
isCodeHiddenProp &&
157-
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode))
176+
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode)) &&
177+
(props.isEditable
178+
? props.block.getAttribute('isCodeHidden') ?? false
179+
: localCodeHidden === null
180+
? props.block.getAttribute('isCodeHidden') ?? false
181+
: localCodeHidden)
182+
158183
const isResultHidden =
159-
isResultHiddenProp &&
160-
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode))
184+
(!props.dashboardMode || !dashboardModeHasControls(props.dashboardMode)) &&
185+
(props.isEditable
186+
? props.block.getAttribute('isResultHidden') ?? false
187+
: localResultHidden === null
188+
? props.block.getAttribute('isResultHidden') ?? false
189+
: localResultHidden)
161190

162191
const { startedAt: environmentStartedAt } = useEnvironmentStatus(
163192
props.document.workspaceId
@@ -334,7 +363,13 @@ function SQLBlock(props: Props) {
334363
case 'idle':
335364
case 'completed': {
336365
if (source?.toJSON() === lastQuery && lastQueryTime) {
337-
return <QuerySucceededText lastExecutionTime={lastQueryTime} />
366+
return (
367+
<QuerySucceededText
368+
lastExecutionTime={lastQueryTime}
369+
isResultHidden={isResultHidden}
370+
onToggleResultHidden={toggleResultHidden}
371+
/>
372+
)
338373
}
339374

340375
return null
@@ -770,25 +805,17 @@ function SQLBlock(props: Props) {
770805
>
771806
<div className="select-none text-gray-300 text-xs flex items-center w-full h-full gap-x-1.5">
772807
<div className="relative group w-4 h-4">
773-
<CircleStackIcon
774-
className={clsx(
775-
'absolute inset-0 h-4 w-4 text-gray-400',
776-
props.isEditable &&
777-
'group-hover:opacity-0 transition-opacity'
808+
<CircleStackIcon className="absolute inset-0 h-4 w-4 text-gray-400 group-hover:opacity-0 transition-opacity" />
809+
<button
810+
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
811+
onClick={toggleCodeHidden}
812+
>
813+
{isCodeHidden ? (
814+
<ChevronRightIcon className="h-4 w-4" />
815+
) : (
816+
<ChevronDownIcon className="h-4 w-4" />
778817
)}
779-
/>
780-
{props.isEditable && (
781-
<button
782-
className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity"
783-
onClick={toggleCodeHidden}
784-
>
785-
{isCodeHidden ? (
786-
<ChevronRightIcon className="h-4 w-4" />
787-
) : (
788-
<ChevronDownIcon className="h-4 w-4" />
789-
)}
790-
</button>
791-
)}
818+
</button>
792819
</div>
793820
<input
794821
type="text"

0 commit comments

Comments
 (0)