diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 35d5c6e8..54e259f8 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -81,6 +81,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi multiple: viewData?.data?.multiple, value: viewData?.data?.value, position: viewData?.data?.position || null, + blockData: typeof viewData?.data?.blockData !== "undefined" ? viewData?.data?.blockData : undefined, alt: alt || "", caption: caption || "" }); diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index 7263ac25..7f33f42e 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -1,11 +1,8 @@ import { Messenger } from '@estruyf/vscode/dist/client'; -import { EventData } from '@estruyf/vscode/dist/models'; import {UploadIcon} from '@heroicons/react/outline'; import * as React from 'react'; -import { useRecoilState, useRecoilValue } from 'recoil'; -import { MediaInfo, MediaPaths } from '../../../models/MediaPaths'; -import { DashboardCommand } from '../../DashboardCommand'; -import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state'; +import { useRecoilValue } from 'recoil'; +import { LoadingAtom, MediaFoldersAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state'; import { Header } from '../Header'; import { Spinner } from '../Spinner'; import { SponsorMsg } from '../SponsorMsg'; diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 13f2800c..3383977b 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -305,7 +305,12 @@ export class MediaHelpers { MediaListener.getMediaSelection(); } else { MediaListener.getMediaSelection(); - DataListener.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents }); + DataListener.updateMetadata({ + field: data.fieldName, + value: data.image, + parents: data.parents, + blockData: data.blockData + }); } } } diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts index 0c3d2b31..b7902b6c 100644 --- a/src/listeners/panel/DataListener.ts +++ b/src/listeners/panel/DataListener.ts @@ -1,3 +1,4 @@ +import { BlockFieldData } from './../../models/BlockFieldData'; import { ImageHelper } from './../../helpers/ImageHelper'; import { Folders } from "../../commands/Folders"; import { Command } from "../../panelWebView/Command"; @@ -101,7 +102,18 @@ export class DataListener extends BaseListener { /** * Update the metadata of the article */ - public static async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) { + public static async updateMetadata({ + field, + parents, + value, + blockData + }: { + field: string, + value: any, + parents?: string[], + blockData?: BlockFieldData, + fieldData?: { multiple: boolean, value: string[] } + }) { if (!field) { return; } @@ -117,18 +129,63 @@ export class DataListener extends BaseListener { } const contentType = ArticleHelper.getContentType(article.data); - const dateFields = contentType.fields.filter((field) => field.type === "datetime"); - const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple); + const dateFields = contentType.fields.filter((f) => f.type === "datetime"); + const imageFields = contentType.fields.filter((f) => f.type === "image" && f.multiple); // Support multi-level fields let parentObj = article.data; - for (const parent of parents || []) { - // If parent doesn't yet exists, it needs to be created - if (!parentObj[parent]) { - parentObj[parent] = {}; + let allParents = Object.assign([], parents); + + // Add support for block fields + if (blockData?.parentFields) { + let crntField = null; + parentObj = article.data; + + // Loop through the parents of the block field + for (const parent of blockData?.parentFields) { + if (!parentObj[parent]) { + parentObj[parent] = {}; + } + + if (allParents[0] && allParents[0] === parent) { + allParents.shift(); + } + + parentObj = parentObj[parent]; + crntField = contentType.fields.find(f => f.name === parent); } - parentObj = parentObj[parent]; + // Fetches the current block + if (blockData && crntField && crntField.type === 'block') { + if (typeof blockData.selectedIndex !== 'undefined') { + parentObj = parentObj[blockData.selectedIndex]; + } else { + parentObj.push({ + fieldGroup: blockData.blockType + }); + parentObj = parentObj[parentObj.length - 1]; + } + } + + // Check if there are parents left + if (allParents.length > 0) { + for (const parent of allParents) { + if (!parentObj[parent]) { + parentObj[parent] = {}; + } + + parentObj = parentObj[parent]; + } + } + } else { + for (const parent of parents || []) { + // If parent doesn't yet exists, it needs to be created + if (!parentObj[parent]) { + parentObj[parent] = {}; + } + + parentObj = parentObj[parent]; + } } for (const dateField of dateFields) { diff --git a/src/models/BlockFieldData.ts b/src/models/BlockFieldData.ts new file mode 100644 index 00000000..7e1cf785 --- /dev/null +++ b/src/models/BlockFieldData.ts @@ -0,0 +1,7 @@ + + +export interface BlockFieldData { + parentFields: string[] | undefined; + blockType: string | undefined; + selectedIndex: number | undefined; +} \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index d10b96b9..680f6091 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,3 +1,4 @@ +export * from './BlockFieldData'; export * from './Choice'; export * from './ContentFolder'; export * from './CustomTaxonomyData'; diff --git a/src/panelWebView/components/DataBlock/DataBlockField.tsx b/src/panelWebView/components/DataBlock/DataBlockField.tsx index c3478584..ae60272f 100644 --- a/src/panelWebView/components/DataBlock/DataBlockField.tsx +++ b/src/panelWebView/components/DataBlock/DataBlockField.tsx @@ -1,12 +1,13 @@ import * as React from 'react'; -import { useState, useCallback } from 'react'; -import { Field, FieldGroup, PanelSettings } from '../../../models'; +import { useState, useCallback, useEffect, useMemo } from 'react'; +import { BlockFieldData, Field, FieldGroup, PanelSettings } from '../../../models'; import { PencilIcon } from '@heroicons/react/outline'; import { VsLabel } from '../VscodeComponents'; import { DataBlockRecords, DataBlockSelector } from '.'; import { SortEnd } from 'react-sortable-hoc'; import { arrayMoveImmutable } from 'array-move'; import { IMetadata } from '../Metadata'; +import { MessageHelper } from '../../../helpers/MessageHelper'; export interface IDataBlockFieldProps { label: string; @@ -14,22 +15,31 @@ export interface IDataBlockFieldProps { field: Field; parentFields: string[]; value: any; - fieldsRenderer: (ctFields: Field[], parent: IMetadata, parentFields?: string[], onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void) => (JSX.Element | null)[] | undefined + filePath: string; + fieldsRenderer: ( + ctFields: Field[], + parent: IMetadata, + parentFields?: string[], + blockData?: BlockFieldData, + onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void + ) => (JSX.Element | null)[] | undefined onSubmit: (data: any) => void; } -export const DataBlockField: React.FunctionComponent = ({ label, settings, field, parentFields = [], value, fieldsRenderer, onSubmit }: React.PropsWithChildren) => { +export const DataBlockField: React.FunctionComponent = ({ label, filePath, settings, field, parentFields = [], value, fieldsRenderer, onSubmit }: React.PropsWithChildren) => { const [ selectedIndex, setSelectedIndex ] = useState(null); const [ selectedGroup, setSelectedGroup ] = useState(null); const [ selectedBlockData, setSelectedBlockData ] = useState(null); + const stateKey = useMemo(() => `${filePath}-data_block-${field.name}`, [filePath, field.name]); + const onFieldUpdate = useCallback((crntField: string | undefined, crntValue: any, parents: string[]) => { const dataClone: any[] = Object.assign([], value); if (!crntField) { return; } - + if (selectedIndex !== null && selectedIndex !== undefined) { const data = Object.assign({}, dataClone[selectedIndex]); @@ -71,6 +81,7 @@ export const DataBlockField: React.FunctionComponent = ({ const newIndex = dataClone.length - 1; setSelectedIndex(newIndex); + updateState(newIndex); } onSubmit(dataClone); @@ -91,7 +102,8 @@ export const DataBlockField: React.FunctionComponent = ({ setSelectedGroup(group); if (!group) { - setSelectedIndex(null); + // Clear the selected index + onAdd(); } } @@ -99,9 +111,13 @@ export const DataBlockField: React.FunctionComponent = ({ setSelectedIndex(null); setSelectedGroup(null); setSelectedBlockData({}); + + updateState(undefined); }, [setSelectedIndex, setSelectedGroup, setSelectedBlockData]); const onEdit = useCallback((index: number) => { + updateState(index); + setSelectedIndex(index); const fieldData = value[index]; @@ -125,6 +141,31 @@ export const DataBlockField: React.FunctionComponent = ({ const newEntries = arrayMoveImmutable(value, oldIndex, newIndex); onSubmit(newEntries); }, [value, selectedIndex]); + + // Retrieving the state from local storage (does not need to be persistent) + const retrieveState = useCallback(() => { + const prevState = localStorage.getItem(stateKey); + return prevState ? parseInt(prevState) : undefined; + }, [stateKey]); + + // Storing the state to local storage (does not need to be persistent) + const updateState = useCallback((value: number | undefined) => { + localStorage.setItem(stateKey, value as any); + }, [stateKey]); + + useEffect(() => { + if (selectedIndex !== null) { + const fieldData = value[selectedIndex]; + setSelectedBlockData(fieldData); + } + } , [selectedIndex, value, setSelectedBlockData]); + + useEffect(() => { + const stateIdx = retrieveState(); + if (stateIdx !== undefined) { + onEdit(stateIdx); + } + }, []); return (
@@ -144,7 +185,23 @@ export const DataBlockField: React.FunctionComponent = ({ { selectedGroup?.fields && (
- { fieldsRenderer(selectedGroup?.fields, selectedBlockData || {}, [...parentFields, field.name], onFieldUpdate) } +

+ {selectedIndex !== null ? `Editing: ${selectedGroup.id} ${selectedIndex + 1}` : `Create a new ${selectedGroup?.id}`} +

+ + { + fieldsRenderer( + selectedGroup?.fields, + selectedBlockData || {}, + [...parentFields, field.name], + { + parentFields: [...parentFields, field.name], + blockType: selectedGroup?.id || undefined, + selectedIndex: selectedIndex === null ? undefined : selectedIndex + }, + onFieldUpdate + ) + }
) } diff --git a/src/panelWebView/components/Fields/PreviewImageField.tsx b/src/panelWebView/components/Fields/PreviewImageField.tsx index 11100645..b8aefc6f 100644 --- a/src/panelWebView/components/Fields/PreviewImageField.tsx +++ b/src/panelWebView/components/Fields/PreviewImageField.tsx @@ -1,6 +1,8 @@ import {PhotographIcon} from '@heroicons/react/outline'; import * as React from 'react'; +import { useCallback } from 'react'; import { MessageHelper } from '../../../helpers/MessageHelper'; +import { BlockFieldData } from '../../../models'; import { CommandToCode } from '../../CommandToCode'; import { VsLabel } from '../VscodeComponents'; import { PreviewImage } from './PreviewImage'; @@ -17,21 +19,32 @@ export interface IPreviewImageFieldProps { filePath: string | null; parents?: string[]; multiple?: boolean; + blockData?: BlockFieldData; onChange: (value: string | string[] | null) => void; } -export const PreviewImageField: React.FunctionComponent = ({label, fieldName, onChange, value, filePath, multiple, parents}: React.PropsWithChildren) => { +export const PreviewImageField: React.FunctionComponent = ({ + label, + fieldName, + blockData, + onChange, + value, + filePath, + multiple, + parents +}: React.PropsWithChildren) => { - const selectImage = () => { + const selectImage = useCallback(() => { MessageHelper.sendMessage(CommandToCode.selectImage, { filePath: filePath, fieldName, value, multiple, metadataInsert: true, - parents + parents, + blockData }); - }; + }, [filePath, fieldName, value, multiple, parents]); const onImageRemove = (imageToRemove: string) => { const newValue = value && Array.isArray(value) ? value.filter(image => image.original !== imageToRemove).map(i => i.original) : null; diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index ed9c5584..a1940d7c 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Field, PanelSettings } from '../../models'; +import { BlockFieldData, Field, PanelSettings } from '../../models'; import { CommandToCode } from '../CommandToCode'; import { MessageHelper } from '../../helpers/MessageHelper'; import { TagType } from '../TagType'; @@ -57,7 +57,13 @@ const Metadata: React.FunctionComponent = ({settings, metadata, return null; } - const renderFields = (ctFields: Field[], parent: IMetadata, parentFields: string[] = [], onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void) => { + const renderFields = ( + ctFields: Field[], + parent: IMetadata, + parentFields: string[] = [], + blockData?: BlockFieldData, + onFieldUpdate?: (field: string | undefined, value: any, parents: string[]) => void, + ) => { if (!ctFields) { return; } @@ -144,6 +150,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, parents={parentFields} value={parent[field.name] as PreviewImageValue | PreviewImageValue[] | null} multiple={field.multiple} + blockData={blockData} onChange={(value) => onSendUpdate(field.name, value, parentFields)} /> ); @@ -241,7 +248,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata,
- { renderFields(field.fields, subMetadata, [...parentFields, field.name], onFieldUpdate) } + { renderFields(field.fields, subMetadata, [...parentFields, field.name], blockData, onFieldUpdate) } ); @@ -262,8 +269,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, ); } else if (field.type === 'block') { - const blockData = parent[field.name]; - + const blockData = Object.assign([], parent[field.name]); + return ( = ({settings, metadata, value={blockData} fieldsRenderer={renderFields} parentFields={parentFields} + filePath={metadata.filePath as string} onSubmit={(value) => onSendUpdate(field.name, value, parentFields)} /> );