#176 - Image field support added for block field

This commit is contained in:
Elio Struyf
2022-02-14 15:45:16 -08:00
parent e5a2194c23
commit 1aa2d41c95
9 changed files with 176 additions and 30 deletions
@@ -81,6 +81,7 @@ export const Item: React.FunctionComponent<IItemProps> = ({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 || ""
});
@@ -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';
+6 -1
View File
@@ -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
});
}
}
}
+65 -8
View File
@@ -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) {
+7
View File
@@ -0,0 +1,7 @@
export interface BlockFieldData {
parentFields: string[] | undefined;
blockType: string | undefined;
selectedIndex: number | undefined;
}
+1
View File
@@ -1,3 +1,4 @@
export * from './BlockFieldData';
export * from './Choice';
export * from './ContentFolder';
export * from './CustomTaxonomyData';
@@ -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<IDataBlockFieldProps> = ({ label, settings, field, parentFields = [], value, fieldsRenderer, onSubmit }: React.PropsWithChildren<IDataBlockFieldProps>) => {
export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({ label, filePath, settings, field, parentFields = [], value, fieldsRenderer, onSubmit }: React.PropsWithChildren<IDataBlockFieldProps>) => {
const [ selectedIndex, setSelectedIndex ] = useState<number | null>(null);
const [ selectedGroup, setSelectedGroup ] = useState<FieldGroup | undefined | null>(null);
const [ selectedBlockData, setSelectedBlockData ] = useState<any | null>(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<IDataBlockFieldProps> = ({
const newIndex = dataClone.length - 1;
setSelectedIndex(newIndex);
updateState(newIndex);
}
onSubmit(dataClone);
@@ -91,7 +102,8 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
setSelectedGroup(group);
if (!group) {
setSelectedIndex(null);
// Clear the selected index
onAdd();
}
}
@@ -99,9 +111,13 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
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<IDataBlockFieldProps> = ({
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 (
<div className='json_data__field'>
@@ -144,7 +185,23 @@ export const DataBlockField: React.FunctionComponent<IDataBlockFieldProps> = ({
{
selectedGroup?.fields && (
<div className='block_field__form'>
{ fieldsRenderer(selectedGroup?.fields, selectedBlockData || {}, [...parentFields, field.name], onFieldUpdate) }
<h3>
{selectedIndex !== null ? `Editing: ${selectedGroup.id} ${selectedIndex + 1}` : `Create a new ${selectedGroup?.id}`}
</h3>
{
fieldsRenderer(
selectedGroup?.fields,
selectedBlockData || {},
[...parentFields, field.name],
{
parentFields: [...parentFields, field.name],
blockType: selectedGroup?.id || undefined,
selectedIndex: selectedIndex === null ? undefined : selectedIndex
},
onFieldUpdate
)
}
</div>
)
}
@@ -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<IPreviewImageFieldProps> = ({label, fieldName, onChange, value, filePath, multiple, parents}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
export const PreviewImageField: React.FunctionComponent<IPreviewImageFieldProps> = ({
label,
fieldName,
blockData,
onChange,
value,
filePath,
multiple,
parents
}: React.PropsWithChildren<IPreviewImageFieldProps>) => {
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;
+13 -5
View File
@@ -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<IMetadataProps> = ({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<IMetadataProps> = ({settings, metadata,
parents={parentFields}
value={parent[field.name] as PreviewImageValue | PreviewImageValue[] | null}
multiple={field.multiple}
blockData={blockData}
onChange={(value) => onSendUpdate(field.name, value, parentFields)} />
</FieldBoundary>
);
@@ -241,7 +248,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
</div>
</VsLabel>
{ renderFields(field.fields, subMetadata, [...parentFields, field.name], onFieldUpdate) }
{ renderFields(field.fields, subMetadata, [...parentFields, field.name], blockData, onFieldUpdate) }
</div>
</FieldBoundary>
);
@@ -262,8 +269,8 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
</FieldBoundary>
);
} else if (field.type === 'block') {
const blockData = parent[field.name];
const blockData = Object.assign([], parent[field.name]);
return (
<FieldBoundary key={field.name} fieldName={field.title || field.name}>
<DataBlockField
@@ -273,6 +280,7 @@ const Metadata: React.FunctionComponent<IMetadataProps> = ({settings, metadata,
value={blockData}
fieldsRenderer={renderFields}
parentFields={parentFields}
filePath={metadata.filePath as string}
onSubmit={(value) => onSendUpdate(field.name, value, parentFields)} />
</FieldBoundary>
);