Skip to content

Commit

Permalink
feat: render path state
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Feb 22, 2025
1 parent 9524c53 commit 91f61ea
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 124 deletions.
131 changes: 65 additions & 66 deletions packages/devtools/src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,20 @@ import {
DevtoolsField,
DevtoolsForm,
EncodedNode,
FieldState,
fieldToState,
FormState,
formToState,
NODE_TYPE,
NodeState,
PathState,
} from './types';
import type { FormReturns, FormField } from '@core/index';
import { ComponentInternalInstance, toValue } from 'vue';
import { toValue } from 'vue';
import { getPluginColors } from './constants';
import { brandMessage, buildFormTree } from './utils';
import { setInPath } from '@core/utils/path';
import { getField, getForm } from './registry';

export function buildFieldState(
state: Pick<PathState, 'errors' | 'touched' | 'dirty' | 'value' | 'valid'>,
): CustomInspectorState {
export function buildFieldState(state: FieldState | PathState): CustomInspectorState {
return {
'Field state': [
{ key: 'errors', value: state.errors },
Expand All @@ -42,133 +40,134 @@ export function buildFieldState(
};
}

export function buildFormState(form: FormReturns): CustomInspectorState {
const { isSubmitting, isTouched, isDirty, isValid, submitAttemptsCount, values, getErrors } = form;

export function buildFormState(form: FormState): CustomInspectorState {
return {
'Form state': [
{
key: 'submitCount',
value: submitAttemptsCount.value,
value: form.submitCount,
},
{
key: 'isSubmitting',
value: isSubmitting.value,
value: form.isSubmitting,
},
{
key: 'touched',
value: isTouched(),
value: form.touched,
},
{
key: 'dirty',
value: isDirty(),
value: form.dirty,
},
{
key: 'valid',
value: isValid(),
value: form.valid,
},
{
key: 'currentValues',
value: values,
value: form.value,
},
{
key: 'errors',
value: getErrors(),
value: form.errors,
},
],
};
}

export function encodeNodeId(nodeState?: NodeState): string {
const type = (() => {
if (!nodeState) {
return 'unknown';
const ff = (() => {
if (nodeState?.type !== 'field') {
return '';
}

if ('id' in nodeState) {
return 'form';
} else if ('path' in nodeState) {
return 'field';
} else {
return 'pathState';
}
return nodeState.path;
})();

const ff = (() => {
if (!nodeState) {
const fp = (() => {
if (nodeState?.type !== 'path') {
return '';
}

if ('path' in nodeState) {
return nodeState.path;
} else {
return '';
}
return nodeState.path;
})();

const form = (() => {
if (!nodeState) {
return '';
}

if ('id' in nodeState) {
if (nodeState.type === 'form') {
return nodeState.id;
}

if ('formId' in nodeState && nodeState.formId) {
return nodeState.formId;
if (nodeState.type === 'field' || nodeState.type === 'path') {
return nodeState.formId ?? '';
}

return '';
})();

const idObject = { f: form, ff, type } satisfies EncodedNode;
const idObject = { f: form, ff, fp, type: nodeState?.type ?? 'unknown' } satisfies EncodedNode;

return btoa(encodeURIComponent(JSON.stringify(idObject)));
}

export function decodeNodeId(nodeId: string): {
field?: FormField<unknown> & { _vm?: ComponentInternalInstance | null };
form?: FormReturns & { _vm?: ComponentInternalInstance | null };
state?: PathState;
type?: NODE_TYPE;
} {
export function decodeNode(nodeId: string): NodeState | null {
try {
const idObject = JSON.parse(decodeURIComponent(atob(nodeId))) as EncodedNode;
const form = getForm(idObject.f);

// standalone field
if (!form && idObject.ff) {
const field = getField(idObject.ff);
if (idObject.type === 'field') {
if (!idObject.ff) {
return null;
}

const field = getField(idObject.ff, idObject.f);
if (!field) {
return {};
return null;
}

return fieldToState(field, idObject.f);
}

if (idObject.type === 'path') {
if (!idObject.fp) {
return null;
}

const form = getForm(idObject.f);

// Should not happen, path should always be relative to a form
if (!form || '_isRoot' in form) {
return null;
}

return {
type: idObject.type,
field,
type: 'path',
path: idObject.fp,
formId: idObject.f,
touched: form.isTouched(idObject.fp),
dirty: form.isDirty(idObject.fp),
valid: form.isValid(idObject.fp),
errors: form.getErrors(idObject.fp),
value: form.getValue(idObject.fp),
};
}

if (!form || '_isRoot' in form) {
return {};
}
if (idObject.type === 'form') {
const form = getForm(idObject.f);

const field = form.fields.get(idObject.ff);
const state = formToState(form);

return {
type: idObject.type,
form,
state,
field,
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (err) {
if (!form || '_isRoot' in form) {
return null;
}

return formToState(form);
}
} catch {
console.error(brandMessage(`Failed to parse node id ${nodeId}`));
}

return {};
return null;
}

/**
Expand Down Expand Up @@ -218,7 +217,7 @@ export function mapFormForDevtoolsInspector(form: DevtoolsForm, filter?: string)
setInPath(formTreeNodes, toValue(state.getPath() ?? ''), mapFieldForDevtoolsInspector(state, form));
});

const { children } = buildFormTree(formTreeNodes);
const { children } = buildFormTree(formTreeNodes, [], form);

return {
id: encodeNodeId(formState),
Expand Down
59 changes: 36 additions & 23 deletions packages/devtools/src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,28 @@ import { PathState } from './types';
import {
buildFieldState,
buildFormState,
decodeNodeId,
decodeNode,
mapFieldForDevtoolsInspector,
mapFormForDevtoolsInspector,
} from './helpers';
import { getInspectorId } from './constants';
import { getAllForms, getRootFields, registerField as _registerField, registerForm as _registerForm } from './registry';
import {
getAllForms,
getRootFields,
registerField as _registerField,
registerForm as _registerForm,
getForm,
getField,
} from './registry';
import { brandMessage } from './utils';

let SELECTED_NODE:
| { type: 'form'; form: FormReturns }
| { type: 'field'; field: FormField<unknown> }
| null
| {
type: 'pathState';
type: 'path';
state: PathState;
form: FormReturns;
} = null;

/**
Expand Down Expand Up @@ -120,33 +126,40 @@ async function installDevtoolsPlugin(app: App) {
return;
}

const { form, field, state, type } = decodeNodeId(payload.nodeId);

const node = decodeNode(payload.nodeId);
api.unhighlightElement();
if (!node) {
return;
}

if (node.type === 'form') {
payload.state = buildFormState(node);
const form = getForm(node.id);

if (form && '_vm' in form) {
SELECTED_NODE = { type: 'form', form };
api.highlightElement(form._vm);
}

if (form && type === 'form') {
payload.state = buildFormState(form);
SELECTED_NODE = { type: 'form', form };
api.highlightElement(form._vm);
return;
}

if (state && type === 'pathState' && form) {
payload.state = buildFieldState(state);
SELECTED_NODE = { type: 'pathState', state, form };
if (node.type === 'field') {
payload.state = buildFieldState(node);
const field = getField(node.path, node.formId);

if (field) {
SELECTED_NODE = { type: 'field', field };
api.highlightElement(field._vm);
}

return;
}

if (field && type === 'field') {
payload.state = buildFieldState({
errors: field.errors.value,
dirty: field.isDirty.value,
valid: field.isValid.value,
touched: field.isTouched.value,
value: field.fieldValue.value,
});
SELECTED_NODE = { field, type: 'field' };
api.highlightElement(field._vm);
if (node.type === 'path') {
payload.state = buildFieldState(node);
SELECTED_NODE = { type: 'path', state: node };

return;
}

Expand Down
Loading

0 comments on commit 91f61ea

Please sign in to comment.