mirror of
https://github.com/estruyf/vscode-front-matter.git
synced 2026-08-08 01:42:58 +02:00
#176 - Image field support added for block field
This commit is contained in:
@@ -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';
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
|
||||
export interface BlockFieldData {
|
||||
parentFields: string[] | undefined;
|
||||
blockType: string | undefined;
|
||||
selectedIndex: number | undefined;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user