Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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