mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-24 04:23:57 +03:00
Create new field type JSON (#4729)
### Description Create new field type JSON ### Refs https://github.com/twentyhq/twenty/issues/3900 ### Demo https://github.com/twentyhq/twenty/assets/140154534/9ebdf4d4-f332-4940-b9d8-d9cf91935b67 Fixes #3900 --------- Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com> Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
This commit is contained in:
parent
f25d58b0d9
commit
584d90ec89
@ -1,5 +1,8 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
|
import { JsonFieldDisplay } from '@/object-record/record-field/meta-types/display/components/JsonFieldDisplay';
|
||||||
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
|
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||||
@ -59,5 +62,7 @@ export const FieldDisplay = () => {
|
|||||||
<SelectFieldDisplay />
|
<SelectFieldDisplay />
|
||||||
) : isFieldAddress(fieldDefinition) ? (
|
) : isFieldAddress(fieldDefinition) ? (
|
||||||
<AddressFieldDisplay />
|
<AddressFieldDisplay />
|
||||||
|
) : isFieldRawJson(fieldDefinition) ? (
|
||||||
|
<JsonFieldDisplay />
|
||||||
) : null;
|
) : null;
|
||||||
};
|
};
|
||||||
|
@ -2,9 +2,11 @@ import { useContext } from 'react';
|
|||||||
|
|
||||||
import { AddressFieldInput } from '@/object-record/record-field/meta-types/input/components/AddressFieldInput';
|
import { AddressFieldInput } from '@/object-record/record-field/meta-types/input/components/AddressFieldInput';
|
||||||
import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput';
|
import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput';
|
||||||
|
import { RawJsonFieldInput } from '@/object-record/record-field/meta-types/input/components/RawJsonFieldInput';
|
||||||
import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput';
|
import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput';
|
||||||
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
|
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
|
||||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||||
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
import { getScopeIdFromComponentId } from '@/ui/utilities/recoil-scope/utils/getScopeIdFromComponentId';
|
||||||
|
|
||||||
@ -137,6 +139,14 @@ export const FieldInput = ({
|
|||||||
onTab={onTab}
|
onTab={onTab}
|
||||||
onShiftTab={onShiftTab}
|
onShiftTab={onShiftTab}
|
||||||
/>
|
/>
|
||||||
|
) : isFieldRawJson(fieldDefinition) ? (
|
||||||
|
<RawJsonFieldInput
|
||||||
|
onEnter={onEnter}
|
||||||
|
onEscape={onEscape}
|
||||||
|
onClickOutside={onClickOutside}
|
||||||
|
onTab={onTab}
|
||||||
|
onShiftTab={onShiftTab}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
@ -5,6 +5,8 @@ import { isFieldAddress } from '@/object-record/record-field/types/guards/isFiel
|
|||||||
import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||||
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
||||||
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
|
import { isFieldRawJsonValue } from '@/object-record/record-field/types/guards/isFieldRawJsonValue';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
||||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
@ -88,6 +90,10 @@ export const usePersistField = () => {
|
|||||||
isFieldAddress(fieldDefinition) &&
|
isFieldAddress(fieldDefinition) &&
|
||||||
isFieldAddressValue(valueToPersist);
|
isFieldAddressValue(valueToPersist);
|
||||||
|
|
||||||
|
const fieldIsRawJson =
|
||||||
|
isFieldRawJson(fieldDefinition) &&
|
||||||
|
isFieldRawJsonValue(valueToPersist);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fieldIsRelation ||
|
fieldIsRelation ||
|
||||||
fieldIsText ||
|
fieldIsText ||
|
||||||
@ -101,7 +107,8 @@ export const usePersistField = () => {
|
|||||||
fieldIsCurrency ||
|
fieldIsCurrency ||
|
||||||
fieldIsFullName ||
|
fieldIsFullName ||
|
||||||
fieldIsSelect ||
|
fieldIsSelect ||
|
||||||
fieldIsAddress
|
fieldIsAddress ||
|
||||||
|
fieldIsRawJson
|
||||||
) {
|
) {
|
||||||
const fieldName = fieldDefinition.metadata.fieldName;
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
set(
|
set(
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import { useJsonField } from '@/object-record/record-field/meta-types/hooks/useJsonField';
|
||||||
|
import { JsonDisplay } from '@/ui/field/display/components/JsonDisplay';
|
||||||
|
|
||||||
|
export const JsonFieldDisplay = () => {
|
||||||
|
const { fieldValue, maxWidth } = useJsonField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JsonDisplay
|
||||||
|
text={fieldValue ? JSON.stringify(JSON.parse(fieldValue), null, 2) : ''}
|
||||||
|
maxWidth={maxWidth}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,48 @@
|
|||||||
|
import { useContext } from 'react';
|
||||||
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
|
||||||
|
import { FieldJsonValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||||
|
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||||
|
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
|
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||||
|
import { isFieldRawJson } from '../../types/guards/isFieldRawJson';
|
||||||
|
import { isFieldTextValue } from '../../types/guards/isFieldTextValue';
|
||||||
|
|
||||||
|
export const useJsonField = () => {
|
||||||
|
const { entityId, fieldDefinition, hotkeyScope, maxWidth } =
|
||||||
|
useContext(FieldContext);
|
||||||
|
|
||||||
|
assertFieldMetadata(
|
||||||
|
FieldMetadataType.RawJson,
|
||||||
|
isFieldRawJson,
|
||||||
|
fieldDefinition,
|
||||||
|
);
|
||||||
|
|
||||||
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
|
||||||
|
const [fieldValue, setFieldValue] = useRecoilState<FieldJsonValue>(
|
||||||
|
recordStoreFamilySelector({
|
||||||
|
recordId: entityId,
|
||||||
|
fieldName: fieldName,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const fieldTextValue = isFieldTextValue(fieldValue) ? fieldValue : '';
|
||||||
|
|
||||||
|
const { setDraftValue, getDraftValueSelector } =
|
||||||
|
useRecordFieldInput<FieldJsonValue>(`${entityId}-${fieldName}`);
|
||||||
|
|
||||||
|
const draftValue = useRecoilValue(getDraftValueSelector());
|
||||||
|
|
||||||
|
return {
|
||||||
|
draftValue,
|
||||||
|
setDraftValue,
|
||||||
|
maxWidth,
|
||||||
|
fieldDefinition,
|
||||||
|
fieldValue: fieldTextValue,
|
||||||
|
setFieldValue,
|
||||||
|
hotkeyScope,
|
||||||
|
};
|
||||||
|
};
|
@ -0,0 +1,83 @@
|
|||||||
|
import { isValidJSON } from '@/object-record/record-field/utils/isFieldValueJson';
|
||||||
|
import { FieldTextAreaOverlay } from '@/ui/field/input/components/FieldTextAreaOverlay';
|
||||||
|
import { TextAreaInput } from '@/ui/field/input/components/TextAreaInput';
|
||||||
|
|
||||||
|
import { usePersistField } from '../../../hooks/usePersistField';
|
||||||
|
import { useJsonField } from '../../hooks/useJsonField';
|
||||||
|
|
||||||
|
import { FieldInputEvent } from './DateFieldInput';
|
||||||
|
|
||||||
|
export type RawJsonFieldInputProps = {
|
||||||
|
onClickOutside?: FieldInputEvent;
|
||||||
|
onEnter?: FieldInputEvent;
|
||||||
|
onEscape?: FieldInputEvent;
|
||||||
|
onTab?: FieldInputEvent;
|
||||||
|
onShiftTab?: FieldInputEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RawJsonFieldInput = ({
|
||||||
|
onEnter,
|
||||||
|
onEscape,
|
||||||
|
onClickOutside,
|
||||||
|
onTab,
|
||||||
|
onShiftTab,
|
||||||
|
}: RawJsonFieldInputProps) => {
|
||||||
|
const { fieldDefinition, draftValue, hotkeyScope, setDraftValue } =
|
||||||
|
useJsonField();
|
||||||
|
|
||||||
|
const persistField = usePersistField();
|
||||||
|
|
||||||
|
const handlePersistField = (newText: string) => {
|
||||||
|
if (!newText || isValidJSON(newText)) persistField(newText || null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEnter = (newText: string) => {
|
||||||
|
onEnter?.(() => handlePersistField(newText));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEscape = (newText: string) => {
|
||||||
|
onEscape?.(() => handlePersistField(newText));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickOutside = (
|
||||||
|
_event: MouseEvent | TouchEvent,
|
||||||
|
newText: string,
|
||||||
|
) => {
|
||||||
|
onClickOutside?.(() => handlePersistField(newText));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTab = (newText: string) => {
|
||||||
|
onTab?.(() => handlePersistField(newText));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleShiftTab = (newText: string) => {
|
||||||
|
onShiftTab?.(() => handlePersistField(newText));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange = (newText: string) => {
|
||||||
|
setDraftValue(newText);
|
||||||
|
};
|
||||||
|
|
||||||
|
const value =
|
||||||
|
draftValue && isValidJSON(draftValue)
|
||||||
|
? JSON.stringify(JSON.parse(draftValue), null, 2)
|
||||||
|
: draftValue ?? '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FieldTextAreaOverlay>
|
||||||
|
<TextAreaInput
|
||||||
|
placeholder={fieldDefinition.metadata.placeHolder}
|
||||||
|
autoFocus
|
||||||
|
value={value}
|
||||||
|
onClickOutside={handleClickOutside}
|
||||||
|
onEnter={handleEnter}
|
||||||
|
onEscape={handleEscape}
|
||||||
|
onShiftTab={handleShiftTab}
|
||||||
|
onTab={handleTab}
|
||||||
|
hotkeyScope={hotkeyScope}
|
||||||
|
onChange={handleChange}
|
||||||
|
maxRows={25}
|
||||||
|
/>
|
||||||
|
</FieldTextAreaOverlay>
|
||||||
|
);
|
||||||
|
};
|
@ -78,6 +78,7 @@ export type FieldAddressMetadata = {
|
|||||||
export type FieldRawJsonMetadata = {
|
export type FieldRawJsonMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
|
placeHolder: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldDefinitionRelationType =
|
export type FieldDefinitionRelationType =
|
||||||
@ -146,3 +147,4 @@ export type FieldRatingValue = (typeof RATING_VALUES)[number];
|
|||||||
export type FieldSelectValue = string | null;
|
export type FieldSelectValue = string | null;
|
||||||
|
|
||||||
export type FieldRelationValue = EntityForSelect | null;
|
export type FieldRelationValue = EntityForSelect | null;
|
||||||
|
export type FieldJsonValue = string;
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { isNull, isString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
import { FieldJsonValue } from '../FieldMetadata';
|
||||||
|
|
||||||
|
// TODO: add zod
|
||||||
|
export const isFieldRawJsonValue = (
|
||||||
|
fieldValue: unknown,
|
||||||
|
): fieldValue is FieldJsonValue => isString(fieldValue) || isNull(fieldValue);
|
@ -13,6 +13,7 @@ import { isFieldLink } from '@/object-record/record-field/types/guards/isFieldLi
|
|||||||
import { isFieldLinkValue } from '@/object-record/record-field/types/guards/isFieldLinkValue';
|
import { isFieldLinkValue } from '@/object-record/record-field/types/guards/isFieldLinkValue';
|
||||||
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
import { isFieldNumber } from '@/object-record/record-field/types/guards/isFieldNumber';
|
||||||
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
import { isFieldRating } from '@/object-record/record-field/types/guards/isFieldRating';
|
||||||
|
import { isFieldRawJson } from '@/object-record/record-field/types/guards/isFieldRawJson';
|
||||||
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/record-field/types/guards/isFieldRelation';
|
||||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||||
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
import { isFieldSelectValue } from '@/object-record/record-field/types/guards/isFieldSelectValue';
|
||||||
@ -39,7 +40,8 @@ export const isFieldValueEmpty = ({
|
|||||||
isFieldRating(fieldDefinition) ||
|
isFieldRating(fieldDefinition) ||
|
||||||
isFieldEmail(fieldDefinition) ||
|
isFieldEmail(fieldDefinition) ||
|
||||||
isFieldBoolean(fieldDefinition) ||
|
isFieldBoolean(fieldDefinition) ||
|
||||||
isFieldRelation(fieldDefinition)
|
isFieldRelation(fieldDefinition) ||
|
||||||
|
isFieldRawJson(fieldDefinition)
|
||||||
//|| isFieldPhone(fieldDefinition)
|
//|| isFieldPhone(fieldDefinition)
|
||||||
) {
|
) {
|
||||||
return isValueEmpty(fieldValue);
|
return isValueEmpty(fieldValue);
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
import { isString } from '@sniptt/guards';
|
||||||
|
|
||||||
|
export const isValidJSON = (str: string) => {
|
||||||
|
try {
|
||||||
|
if (isString(JSON.parse(str))) {
|
||||||
|
throw new Error(`Strings are not supported as JSON: ${str}`);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
@ -75,6 +75,9 @@ export const generateEmptyFieldValue = (
|
|||||||
case FieldMetadataType.MultiSelect: {
|
case FieldMetadataType.MultiSelect: {
|
||||||
throw new Error('Not implemented yet');
|
throw new Error('Not implemented yet');
|
||||||
}
|
}
|
||||||
|
case FieldMetadataType.RawJson: {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new Error('Unhandled FieldMetadataType');
|
throw new Error('Unhandled FieldMetadataType');
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconCoins,
|
IconCoins,
|
||||||
|
IconJson,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconLink,
|
IconLink,
|
||||||
IconMail,
|
IconMail,
|
||||||
@ -117,4 +118,9 @@ export const SETTINGS_FIELD_TYPE_CONFIGS: Record<
|
|||||||
addressLng: -118.2437,
|
addressLng: -118.2437,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[FieldMetadataType.RawJson]: {
|
||||||
|
label: 'JSON',
|
||||||
|
Icon: IconJson,
|
||||||
|
defaultValue: `{ "key": "value" }`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
@ -69,6 +69,7 @@ const previewableTypes = [
|
|||||||
FieldMetadataType.Relation,
|
FieldMetadataType.Relation,
|
||||||
FieldMetadataType.Text,
|
FieldMetadataType.Text,
|
||||||
FieldMetadataType.Address,
|
FieldMetadataType.Address,
|
||||||
|
FieldMetadataType.RawJson,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SettingsDataModelFieldSettingsFormCard = ({
|
export const SettingsDataModelFieldSettingsFormCard = ({
|
||||||
|
@ -2,5 +2,5 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
|||||||
|
|
||||||
export type SettingsSupportedFieldType = Exclude<
|
export type SettingsSupportedFieldType = Exclude<
|
||||||
FieldMetadataType,
|
FieldMetadataType,
|
||||||
FieldMetadataType.Position | FieldMetadataType.RawJson
|
FieldMetadataType.Position
|
||||||
>;
|
>;
|
||||||
|
@ -35,6 +35,7 @@ export type AppTooltipProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
anchorSelect?: string;
|
anchorSelect?: string;
|
||||||
content?: string;
|
content?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
delayHide?: number;
|
delayHide?: number;
|
||||||
offset?: number;
|
offset?: number;
|
||||||
noArrow?: boolean;
|
noArrow?: boolean;
|
||||||
@ -53,6 +54,7 @@ export const AppTooltip = ({
|
|||||||
offset,
|
offset,
|
||||||
place,
|
place,
|
||||||
positionStrategy,
|
positionStrategy,
|
||||||
|
children,
|
||||||
}: AppTooltipProps) => (
|
}: AppTooltipProps) => (
|
||||||
<StyledAppTooltip
|
<StyledAppTooltip
|
||||||
{...{
|
{...{
|
||||||
@ -65,6 +67,7 @@ export const AppTooltip = ({
|
|||||||
offset,
|
offset,
|
||||||
place,
|
place,
|
||||||
positionStrategy,
|
positionStrategy,
|
||||||
|
children,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -22,9 +22,11 @@ const StyledOverflowingText = styled.div<{ cursorPointer: boolean }>`
|
|||||||
export const OverflowingTextWithTooltip = ({
|
export const OverflowingTextWithTooltip = ({
|
||||||
text,
|
text,
|
||||||
className,
|
className,
|
||||||
|
mutliline,
|
||||||
}: {
|
}: {
|
||||||
text: string | null | undefined;
|
text: string | null | undefined;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
mutliline?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const textElementId = `title-id-${uuidV4()}`;
|
const textElementId = `title-id-${uuidV4()}`;
|
||||||
|
|
||||||
@ -65,13 +67,15 @@ export const OverflowingTextWithTooltip = ({
|
|||||||
<div onClick={handleTooltipClick}>
|
<div onClick={handleTooltipClick}>
|
||||||
<AppTooltip
|
<AppTooltip
|
||||||
anchorSelect={`#${textElementId}`}
|
anchorSelect={`#${textElementId}`}
|
||||||
content={text ?? ''}
|
content={mutliline ? undefined : text ?? ''}
|
||||||
delayHide={0}
|
delayHide={0}
|
||||||
offset={5}
|
offset={5}
|
||||||
noArrow
|
noArrow
|
||||||
place="bottom"
|
place="bottom"
|
||||||
positionStrategy="absolute"
|
positionStrategy="absolute"
|
||||||
/>
|
>
|
||||||
|
{mutliline ? <pre>{text}</pre> : ''}
|
||||||
|
</AppTooltip>
|
||||||
</div>,
|
</div>,
|
||||||
document.body,
|
document.body,
|
||||||
)}
|
)}
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
import { EllipsisDisplay } from './EllipsisDisplay';
|
||||||
|
|
||||||
|
type JsonDisplayProps = {
|
||||||
|
text: string;
|
||||||
|
maxWidth?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const JsonDisplay = ({ text, maxWidth }: JsonDisplayProps) => (
|
||||||
|
<EllipsisDisplay maxWidth={maxWidth}>{text}</EllipsisDisplay>
|
||||||
|
);
|
@ -19,6 +19,7 @@ export type TextAreaInputProps = {
|
|||||||
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
||||||
hotkeyScope: string;
|
hotkeyScope: string;
|
||||||
onChange?: (newText: string) => void;
|
onChange?: (newText: string) => void;
|
||||||
|
maxRows?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledTextArea = styled(TextareaAutosize)`
|
const StyledTextArea = styled(TextareaAutosize)`
|
||||||
@ -45,6 +46,7 @@ export const TextAreaInput = ({
|
|||||||
onShiftTab,
|
onShiftTab,
|
||||||
onClickOutside,
|
onClickOutside,
|
||||||
onChange,
|
onChange,
|
||||||
|
maxRows,
|
||||||
}: TextAreaInputProps) => {
|
}: TextAreaInputProps) => {
|
||||||
const [internalText, setInternalText] = useState(value);
|
const [internalText, setInternalText] = useState(value);
|
||||||
|
|
||||||
@ -84,6 +86,7 @@ export const TextAreaInput = ({
|
|||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
value={internalText}
|
value={internalText}
|
||||||
|
maxRows={maxRows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { JsonScalarType } from './json.scalar';
|
||||||
import { PositionScalarType } from './position.scalar';
|
import { PositionScalarType } from './position.scalar';
|
||||||
import { CursorScalarType } from './cursor.scalar';
|
import { CursorScalarType } from './cursor.scalar';
|
||||||
import { BigFloatScalarType } from './big-float.scalar';
|
import { BigFloatScalarType } from './big-float.scalar';
|
||||||
@ -24,4 +25,5 @@ export const scalars = [
|
|||||||
UUIDScalarType,
|
UUIDScalarType,
|
||||||
CursorScalarType,
|
CursorScalarType,
|
||||||
PositionScalarType,
|
PositionScalarType,
|
||||||
|
JsonScalarType,
|
||||||
];
|
];
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
import { isString } from 'class-validator';
|
||||||
|
import { GraphQLScalarType } from 'graphql';
|
||||||
|
import GraphQLJSON from 'graphql-type-json';
|
||||||
|
|
||||||
|
export const JsonScalarType = new GraphQLScalarType({
|
||||||
|
...GraphQLJSON,
|
||||||
|
parseValue: (value) => {
|
||||||
|
if (isString(value) && isString(JSON.parse(value))) {
|
||||||
|
throw new Error(`Strings are not supported as JSON: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GraphQLJSON.parseValue(value);
|
||||||
|
},
|
||||||
|
});
|
@ -14,7 +14,6 @@ import {
|
|||||||
GraphQLString,
|
GraphQLString,
|
||||||
GraphQLType,
|
GraphQLType,
|
||||||
} from 'graphql';
|
} from 'graphql';
|
||||||
import GraphQLJSON from 'graphql-type-json';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DateScalarMode,
|
DateScalarMode,
|
||||||
@ -39,6 +38,7 @@ import {
|
|||||||
UUIDScalarType,
|
UUIDScalarType,
|
||||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
|
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
|
||||||
|
import { JsonScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/json.scalar';
|
||||||
|
|
||||||
export interface TypeOptions<T = any> {
|
export interface TypeOptions<T = any> {
|
||||||
nullable?: boolean;
|
nullable?: boolean;
|
||||||
@ -71,7 +71,7 @@ export class TypeMapperService {
|
|||||||
[FieldMetadataType.PROBABILITY, GraphQLFloat],
|
[FieldMetadataType.PROBABILITY, GraphQLFloat],
|
||||||
[FieldMetadataType.RELATION, UUIDScalarType],
|
[FieldMetadataType.RELATION, UUIDScalarType],
|
||||||
[FieldMetadataType.POSITION, PositionScalarType],
|
[FieldMetadataType.POSITION, PositionScalarType],
|
||||||
[FieldMetadataType.RAW_JSON, GraphQLJSON],
|
[FieldMetadataType.RAW_JSON, JsonScalarType],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return typeScalarMapping.get(fieldMetadataType);
|
return typeScalarMapping.get(fieldMetadataType);
|
||||||
|
@ -72,7 +72,7 @@ const getSchemaComponentsProperties = (
|
|||||||
};
|
};
|
||||||
break;
|
break;
|
||||||
case FieldMetadataType.RAW_JSON:
|
case FieldMetadataType.RAW_JSON:
|
||||||
type: 'object';
|
itemProperty.type = 'object';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
itemProperty.type = 'string';
|
itemProperty.type = 'string';
|
||||||
|
@ -82,6 +82,7 @@ export {
|
|||||||
IconHierarchy2,
|
IconHierarchy2,
|
||||||
IconInbox,
|
IconInbox,
|
||||||
IconInfoCircle,
|
IconInfoCircle,
|
||||||
|
IconJson,
|
||||||
IconKey,
|
IconKey,
|
||||||
IconLanguage,
|
IconLanguage,
|
||||||
IconLayersLinked,
|
IconLayersLinked,
|
||||||
|
Loading…
Reference in New Issue
Block a user