Skip to content

Commit

Permalink
feat(query-devtools): Add bulk edit option for simple objects and arr…
Browse files Browse the repository at this point in the history
…ays (#8095)
  • Loading branch information
ardeora authored Sep 25, 2024
1 parent be44da3 commit f7a82c4
Show file tree
Hide file tree
Showing 3 changed files with 192 additions and 17 deletions.
163 changes: 148 additions & 15 deletions packages/query-devtools/src/Devtools.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,8 @@ const QueryDetails = () => {
const queryClient = useQueryDevtoolsContext().client

const [restoringLoading, setRestoringLoading] = createSignal(false)
const [dataMode, setDataMode] = createSignal<'view' | 'edit'>('view')
const [dataEditError, setDataEditError] = createSignal<boolean>(false)

const errorTypes = createMemo(() => {
return useQueryDevtoolsContext().errorTypes || []
Expand Down Expand Up @@ -2047,22 +2049,85 @@ const QueryDetails = () => {
</Show>
</div>
<div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
Data Explorer
</div>
<div
style={{
padding: tokens.size[2],
}}
class="tsqd-query-details-explorer-container tsqd-query-details-data-explorer"
>
<Explorer
label="Data"
defaultExpanded={['Data']}
value={activeQueryStateData()}
editable={true}
activeQuery={activeQuery()}
/>
Data {dataMode() === 'view' ? 'Explorer' : 'Editor'}
</div>
<Show when={dataMode() === 'view'}>
<div
style={{
padding: tokens.size[2],
}}
class="tsqd-query-details-explorer-container tsqd-query-details-data-explorer"
>
<Explorer
label="Data"
defaultExpanded={['Data']}
value={activeQueryStateData()}
editable={true}
onEdit={() => setDataMode('edit')}
activeQuery={activeQuery()}
/>
</div>
</Show>
<Show when={dataMode() === 'edit'}>
<form
class={cx(
styles().devtoolsEditForm,
'tsqd-query-details-data-editor',
)}
onSubmit={(e) => {
e.preventDefault()
const formData = new FormData(e.currentTarget)
const data = formData.get('data') as string
try {
const parsedData = JSON.parse(data)
activeQuery()!.setState({
...activeQuery()!.state,
data: parsedData,
})
setDataMode('view')
} catch (error) {
setDataEditError(true)
}
}}
>
<textarea
name="data"
class={styles().devtoolsEditTextarea}
onFocus={() => setDataEditError(false)}
data-error={dataEditError()}
value={JSON.stringify(activeQueryStateData(), null, 2)}
></textarea>
<div class={styles().devtoolsEditFormActions}>
<span class={styles().devtoolsEditFormError}>
{dataEditError() ? 'Invalid Value' : ''}
</span>
<div class={styles().devtoolsEditFormActionContainer}>
<button
class={cx(
styles().devtoolsEditFormAction,
css`
color: ${t(colors.gray[600], colors.gray[300])};
`,
)}
type="button"
onClick={() => setDataMode('view')}
>
Cancel
</button>
<button
class={cx(
styles().devtoolsEditFormAction,
css`
color: ${t(colors.blue[600], colors.blue[400])};
`,
)}
>
Save
</button>
</div>
</div>
</form>
</Show>
<div class={cx(styles().detailsHeader, 'tsqd-query-details-header')}>
Query Explorer
</div>
Expand Down Expand Up @@ -3318,6 +3383,74 @@ const stylesFactory = (
}
}
`,
devtoolsEditForm: css`
padding: ${size[2]};
& > [data-error='true'] {
outline: 2px solid ${t(colors.red[200], colors.red[800])};
outline-offset: 2px;
border-radius: ${border.radius.xs};
}
`,
devtoolsEditTextarea: css`
width: 100%;
max-height: 500px;
font-family: 'Fira Code', monospace;
font-size: ${font.size.xs};
border-radius: ${border.radius.sm};
field-sizing: content;
padding: ${size[2]};
background-color: ${t(colors.gray[100], colors.darkGray[800])};
color: ${t(colors.gray[900], colors.gray[100])};
border: 1px solid ${t(colors.gray[200], colors.gray[700])};
resize: none;
&:focus {
outline-offset: 2px;
border-radius: ${border.radius.xs};
outline: 2px solid ${t(colors.blue[200], colors.blue[800])};
}
`,
devtoolsEditFormActions: css`
display: flex;
justify-content: space-between;
gap: ${size[2]};
align-items: center;
padding-top: ${size[1]};
font-size: ${font.size.xs};
`,
devtoolsEditFormError: css`
color: ${t(colors.red[700], colors.red[500])};
`,
devtoolsEditFormActionContainer: css`
display: flex;
gap: ${size[2]};
`,
devtoolsEditFormAction: css`
font-family: ui-sans-serif, Inter, system-ui, sans-serif, sans-serif;
font-size: ${font.size.xs};
padding: ${size[1]} ${tokens.size[2]};
display: flex;
border-radius: ${border.radius.sm};
background-color: ${t(colors.gray[100], colors.darkGray[600])};
border: 1px solid ${t(colors.gray[300], colors.darkGray[400])};
align-items: center;
gap: ${size[2]};
font-weight: ${font.weight.medium};
line-height: ${font.lineHeight.xs};
cursor: pointer;
&:focus-visible {
outline-offset: 2px;
border-radius: ${border.radius.xs};
outline: 2px solid ${colors.blue[800]};
}
&:hover {
background-color: ${t(colors.gray[200], colors.darkGray[500])};
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
`,
}
}

Expand Down
26 changes: 24 additions & 2 deletions packages/query-devtools/src/Explorer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { stringify } from 'superjson'
import { serialize, stringify } from 'superjson'
import { clsx as cx } from 'clsx'
import { Index, Match, Show, Switch, createMemo, createSignal } from 'solid-js'
import { Key } from '@solid-primitives/keyed'
Expand All @@ -9,7 +9,15 @@ import {
displayValue,
updateNestedDataByPath,
} from './utils'
import { Check, CopiedCopier, Copier, ErrorCopier, List, Trash } from './icons'
import {
Check,
CopiedCopier,
Copier,
ErrorCopier,
List,
Pencil,
Trash,
} from './icons'
import { useQueryDevtoolsContext, useTheme } from './contexts'
import type { Query } from '@tanstack/query-core'

Expand Down Expand Up @@ -243,6 +251,7 @@ type ExplorerProps = {
dataPath?: Array<string>
activeQuery?: Query
itemsDeletable?: boolean
onEdit?: () => void
}

function isIterable(x: any): x is Iterable<unknown> {
Expand Down Expand Up @@ -351,6 +360,19 @@ export default function Explorer(props: ExplorerProps) {
dataPath={currentDataPath}
/>
</Show>

<Show when={!!props.onEdit && !serialize(props.value).meta}>
<button
class={styles().actionButton}
title={'Bulk Edit Data'}
aria-label={'Bulk Edit Data'}
onClick={() => {
props.onEdit?.()
}}
>
<Pencil />
</button>
</Show>
</div>
</Show>
</div>
Expand Down
20 changes: 20 additions & 0 deletions packages/query-devtools/src/icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,26 @@ export function Copier() {
)
}

export function Pencil() {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.5 21.4998L8.04927 19.3655C8.40421 19.229 8.58168 19.1607 8.74772 19.0716C8.8952 18.9924 9.0358 18.901 9.16804 18.7984C9.31692 18.6829 9.45137 18.5484 9.72028 18.2795L21 6.99982C22.1046 5.89525 22.1046 4.10438 21 2.99981C19.8955 1.89525 18.1046 1.89524 17 2.99981L5.72028 14.2795C5.45138 14.5484 5.31692 14.6829 5.20139 14.8318C5.09877 14.964 5.0074 15.1046 4.92823 15.2521C4.83911 15.4181 4.77085 15.5956 4.63433 15.9506L2.5 21.4998ZM2.5 21.4998L4.55812 16.1488C4.7054 15.7659 4.77903 15.5744 4.90534 15.4867C5.01572 15.4101 5.1523 15.3811 5.2843 15.4063C5.43533 15.4351 5.58038 15.5802 5.87048 15.8703L8.12957 18.1294C8.41967 18.4195 8.56472 18.5645 8.59356 18.7155C8.61877 18.8475 8.58979 18.9841 8.51314 19.0945C8.42545 19.2208 8.23399 19.2944 7.85107 19.4417L2.5 21.4998Z"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
)
}

export function CopiedCopier(props: { theme: 'light' | 'dark' }) {
return (
<svg
Expand Down

0 comments on commit f7a82c4

Please sign in to comment.