diff --git a/src/FunctionDoc.tsx b/src/FunctionDoc.tsx index 7a911d1..c9f0089 100644 --- a/src/FunctionDoc.tsx +++ b/src/FunctionDoc.tsx @@ -123,7 +123,11 @@ const MiniParseq = ({ keyframes, fields }: MiniParseqProps) => { bpm={120} agGridStyle={{ width: '100%', minHeight: '50px', maxHeight: '500px', }} agGridProps={{ domLayout: 'autoHeight' }} - managedFields={fields} /> + managedFields={fields} + renderedData={renderedData||null} + showPrompt={false} + showInterpolatedValues={true} + /> const graph = graphableData && renderedData ? { bpm={120} agGridStyle={{ width: '100%', minHeight: '50px', maxHeight: '500px', }} agGridProps={{ domLayout: 'autoHeight' }} - managedFields={fields} /> + managedFields={fields} + renderedData={null} + showPrompt={false} + showInterpolatedValues={false} + /> const graph = graphableData && renderedData ? { const [graphAsPercentages, setGraphAsPercentages] = useState(false); const [showPromptMarkers, setShowPromptMarkers] = useState(false); const [showCursors, setShowCursors] = useState(false); + const [showInterpolatedValues, setShowInterpolatedValues] = useState(true); + const [showPrompts, setShowPrompts] = useState(true); const [beatMarkerInterval, setBeatMarkerInterval] = useState(0); const [showFlatSparklines, setShowFlatSparklines] = useState(false); const [keyframes, setKeyframes] = useState(); @@ -253,7 +255,7 @@ const ParseqUI = (props) => { setTimeout(() => { gridRef.current.columnApi.setColumnsVisible(allColumnIds, false); gridRef.current.columnApi.setColumnsVisible(columnsToShow, true); - gridRef.current.columnApi.setColumnsVisible(['frame', 'info'], true); + gridRef.current.columnApi.setColumnsVisible(['frame', 'info', 'prompt'], true); gridRef.current.api.onSortChanged(); }); @@ -944,6 +946,8 @@ const ParseqUI = (props) => { + + // Grid ------------------------ const grid = useMemo(() => { fps={options?.output_fps} bpm={options?.bpm} agGridStyle={{ width: '100%', minHeight: '150px', height: '150px', maxHeight: '1150px', }} - />, [rangeSelection, options, onCellValueChanged, onCellKeyPress, onGridReady, onFirstDataRendered, keyframeLock, showCursors, managedFields]); + renderedData={renderedData} + showPrompt={showPrompts} + showInterpolatedValues={showInterpolatedValues} + />, [rangeSelection, options, onCellValueChanged, onCellKeyPress, onGridReady, onFirstDataRendered, keyframeLock, showCursors, managedFields, renderedData, showInterpolatedValues, showPrompts]); const gridHeightToggle = useMemo(() => @@ -1027,6 +1034,52 @@ const ParseqUI = (props) => { ))} , [displayedFields, handleChangeDisplayedFields, managedFields]) + + const gridOptions = useMemo( + () => ( + + {displayedFieldSelector} + setShowPrompts(e.target.checked)} + /> + } + label={ + + Show computed prompts at keyframes + + } + /> + setShowInterpolatedValues(e.target.checked)} + /> + } + label={ + + Show interpolated values + + } + /> + {gridHeightToggle} + + ), + [ + displayedFieldSelector, + showPrompts, + showInterpolatedValues, + gridHeightToggle, + ] + ); + // Editable Graph ------------------------ // Prompt markers (also used on audio graph) @@ -1329,7 +1382,7 @@ const ParseqUI = (props) => { // Footer ------------------------ const debugStatus = useMemo(() => { - if (process.env.NODE_ENV === 'development' || debugMode) { + if (debugMode) { return Active version: {activeVersionId?.split("-")[1]}
    @@ -1581,10 +1634,7 @@ const ParseqUI = (props) => { // TODO: we always have to render the grid currently, else we lose keyframes because they reference grid data. renderChildrenWhenCollapsed={true} title="Keyframe grid"> - - {displayedFieldSelector} - {gridHeightToggle} - + {gridOptions} {grid} diff --git a/src/components/ParseqGrid.tsx b/src/components/ParseqGrid.tsx index a6249e3..4cdd713 100644 --- a/src/components/ParseqGrid.tsx +++ b/src/components/ParseqGrid.tsx @@ -1,14 +1,16 @@ +import { experimental_extendTheme as extendTheme, useColorScheme } from "@mui/material/styles"; +import { ValueFormatterParams, ValueGetterParams, ValueParserParams, ValueSetterParams } from 'ag-grid-community'; import { AgGridReact } from 'ag-grid-react'; import _ from 'lodash'; import { all, create } from 'mathjs'; import { forwardRef, useCallback, useMemo } from 'react'; -import { frameToXAxisType, xAxisTypeToFrame } from '../utils/maths'; +import { useHotkeysContext } from 'react-hotkeys-hook'; +import { RenderedData } from '../ParseqUI'; +import { themeFactory } from "../theme"; +import { frameToXAxisType, isValidNumber, xAxisTypeToFrame } from '../utils/maths'; import { fieldNametoRGBa } from '../utils/utils'; import { GridTooltip } from './GridToolTip'; -import { ValueParserParams, ValueSetterParams } from 'ag-grid-community'; -import { experimental_extendTheme as extendTheme, useColorScheme } from "@mui/material/styles"; -import { themeFactory } from "../theme"; -import { useHotkeysContext } from 'react-hotkeys-hook'; +import { PromptCellEditor } from './PromptCellEditor'; const config = {} const mathjs = create(all, config) @@ -32,12 +34,14 @@ type ParseqGridProps = { fps: number, bpm: number, managedFields: string[], - agGridProps: {} - agGridStyle: {} - + agGridProps: {}, + agGridStyle: {}, + renderedData: RenderedData|null, + showPrompt: boolean, + showInterpolatedValues: boolean, }; -export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridReady, onCellValueChanged, onCellKeyPress, onFirstDataRendered, onChangeGridCursorPosition, showCursors, keyframeLock, fps, bpm, managedFields, agGridProps, agGridStyle }: ParseqGridProps, gridRef) => { +export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridReady, onCellValueChanged, onCellKeyPress, onFirstDataRendered, onChangeGridCursorPosition, showCursors, keyframeLock, fps, bpm, managedFields, agGridProps, agGridStyle, renderedData, showPrompt, showInterpolatedValues }: ParseqGridProps, gridRef) => { const theme = extendTheme(themeFactory()); // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -128,6 +132,42 @@ export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridRea flex: 1, }, + ...(renderedData && showPrompt ? [{ + headerName: 'Prompt', + field: 'prompt', + valueGetter: (params: ValueGetterParams) => { + const frame = params.data['frame']; + if (renderedData && renderedData.rendered_frames && renderedData.rendered_frames[frame]) { + return renderedData.rendered_frames[frame].deforum_prompt || '⚠️ EMPTY prompt!! This is probably a mistake. Check the prompt section and make sure prompts cover the full duration of the animation.'; + } else { + return ''; + } + }, + readOnly: true, + pinned: 'left', + suppressMovable: true, + cellEditor: PromptCellEditor, + cellEditorPopup: true, + cellEditorPopupPosition: 'under', + cellEditorParams: { + }, + cellStyle: (params: any): any => { + if (isInRangeSelection(params)) { + return { + color: theme.vars.palette.text.secondary, + backgroundColor: theme.vars.palette.gridPromptField.dark, + borderRight: isSameCellPosition(params, params.api.getFocusedCell()) ? '' : '1px solid ' + theme.vars.palette.gridColSeparatorMajor.main + } + } else { + return { + color: theme.vars.palette.text.secondary, + backgroundColor: theme.vars.palette.gridPromptField.light, + borderRight: isSameCellPosition(params, params.api.getFocusedCell()) ? '' : '1px solid '+ theme.vars.palette.gridColSeparatorMajor.main + } + } + }, + flex: 2, + }] : []), { headerName: 'Info', field: 'info', @@ -162,6 +202,19 @@ export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridRea ...(managedFields ? managedFields.flatMap((field: string) => [ { field: field, + valueFormatter: (params: ValueFormatterParams) => { + const frame = params.data['frame']; + if (showInterpolatedValues + && !isValidNumber(params.data[field]) + && renderedData + && renderedData.rendered_frames + ) { + const renderedValue = renderedData.rendered_frames[frame][field]; + return isValidNumber(renderedValue) ? `(${renderedValue})` : ''; + } else { + return params.data[field]; + } + }, valueSetter: (params: ValueSetterParams) => { if (!params.newValue) { params.data[field] = ''; @@ -185,11 +238,13 @@ export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridRea cellStyle: (params: any) => { if (isInRangeSelection(params)) { return { + color: isValidNumber(params.data[field]) ? theme.vars.palette.text.primary : theme.vars.palette.greyedText.main, backgroundColor: fieldNametoRGBa(field, 0.4), borderRight: isSameCellPosition(params, params.api.getFocusedCell()) ? '' : '1px solid ' + theme.vars.palette.gridColSeparatorMinor.main } } else { return { + color: isValidNumber(params.data[field]) ? theme.vars.palette.text.primary : theme.vars.palette.greyedText.main, backgroundColor: fieldNametoRGBa(field, 0.1), borderRight: isSameCellPosition(params, params.api.getFocusedCell()) ? '' : '1px solid ' + theme.vars.palette.gridColSeparatorMinor.main } @@ -231,7 +286,7 @@ export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridRea ]) : []) ] - }, [managedFields, isInRangeSelection, keyframeLock, fps, bpm, theme]); + }, [managedFields, isInRangeSelection, keyframeLock, fps, bpm, theme, renderedData, showInterpolatedValues, showPrompt]); const defaultColDef = useMemo(() => ({ editable: true, @@ -277,6 +332,7 @@ export const ParseqGrid = forwardRef(({ rangeSelection, onSelectRange, onGridRea navigateToNextCell={navigateToNextCell} suppressColumnVirtualisation={process.env?.NODE_ENV === "test"} suppressRowVirtualisation={process.env?.NODE_ENV === "test"} + stopEditingWhenCellsLoseFocus={true} onCellKeyDown={(e: any) => { if (e.event.keyCode === 46 || e.event.keyCode === 8) { if (rangeSelection.anchor && rangeSelection.tip) { diff --git a/src/components/PromptCellEditor.tsx b/src/components/PromptCellEditor.tsx new file mode 100644 index 0000000..079320b --- /dev/null +++ b/src/components/PromptCellEditor.tsx @@ -0,0 +1,52 @@ + +import { Typography } from '@mui/material'; +import { experimental_extendTheme as extendTheme } from "@mui/material/styles"; +import { + forwardRef, + useImperativeHandle, + useRef +} from 'react'; +import { themeFactory } from "../theme"; + +// This is an "editor" from ag-grid's perspective, but is actually +// a read-only view of the prompt at this cell. +export const PromptCellEditor = forwardRef((props: any, ref) => { + const refText = useRef(null); + const theme = extendTheme(themeFactory()); + + /* Component Editor Lifecycle methods */ + useImperativeHandle(ref, () => { + return { + getValue() { + return props.value; + }, + }; + }); + + + const [positivePrompt, negativePrompt] = props.value.split('--neg'); + + return ( +
    +
    + {positivePrompt} + { + negativePrompt && <> + --neg + {negativePrompt} + + } +
    +
    + ); +}); diff --git a/src/index.css b/src/index.css index f5e2112..7789991 100644 --- a/src/index.css +++ b/src/index.css @@ -55,4 +55,8 @@ p { display: inline-block; font-size: 1em; line-height: .85em; +} + +div.ag-popup-child:has(div.prompt-details) { + width: 50% !important } \ No newline at end of file diff --git a/src/theme.ts b/src/theme.ts index f7e3a61..7a76426 100644 --- a/src/theme.ts +++ b/src/theme.ts @@ -11,6 +11,7 @@ declare module "@mui/material/styles" { graphBackground: PaletteColor; graphFont: PaletteColor; gridInfoField: PaletteColor; + gridPromptField: PaletteColor; gridColSeparatorMajor: PaletteColor; gridColSeparatorMinor: PaletteColor; codeBackground: PaletteColor; @@ -18,6 +19,7 @@ declare module "@mui/material/styles" { waveformEnd: PaletteColor; waveformProgressMaskStart: PaletteColor; waveformProgressMaskEnd: PaletteColor; + greyedText: PaletteColor; } interface PaletteOptions { negative: PaletteColor; @@ -29,6 +31,7 @@ declare module "@mui/material/styles" { graphBackground: PaletteColor; graphFont: PaletteColor; gridInfoField: PaletteColor; + gridPromptField: PaletteColor; gridColSeparatorMajor: PaletteColor; gridColSeparatorMinor: PaletteColor; codeBackground: PaletteColor; @@ -36,6 +39,7 @@ declare module "@mui/material/styles" { waveformEnd: PaletteColor; waveformProgressMaskStart: PaletteColor; waveformProgressMaskEnd: PaletteColor; + greyedText: PaletteColor; } } @@ -56,6 +60,7 @@ export const themeFactory = (): CssVarsThemeOptions => { graphBackground: palette.augmentColor({ color: { main: 'rgba(0, 0, 0, 0.1)' } }), graphFont: palette.augmentColor({ color: { main: '#666' } }), gridInfoField: palette.augmentColor({ color: { main: '#e5e5e5' } }), + gridPromptField: palette.augmentColor({ color: { main: '#f5f5f5' } }), gridColSeparatorMajor: palette.augmentColor({ color: { main: '#000' } }), gridColSeparatorMinor: palette.augmentColor({ color: { main: '#ccc' } }), codeBackground: palette.augmentColor({ color: { main: '#ccc' } }), @@ -63,6 +68,7 @@ export const themeFactory = (): CssVarsThemeOptions => { waveformEnd: palette.augmentColor({ color: { main: '#aaa' } }), waveformProgressMaskStart: palette.augmentColor({ color: { main: '#aaa' } }), waveformProgressMaskEnd: palette.augmentColor({ color: { main: '#ccc' } }), + greyedText: palette.augmentColor({ color: { main: 'rgba(0, 0, 0, 0.275)' } }), } }, @@ -78,6 +84,7 @@ export const themeFactory = (): CssVarsThemeOptions => { graphBackground: palette.augmentColor({ color: { main: 'rgba(255, 255, 255, 0.1)' } }), graphFont: palette.augmentColor({ color: { main: '#ddd' } }), gridInfoField: palette.augmentColor({ color: { main: '#444' } }), + gridPromptField: palette.augmentColor({ color: { main: '#222' } }), gridColSeparatorMajor: palette.augmentColor({ color: { main: '#fff' } }), gridColSeparatorMinor: palette.augmentColor({ color: { main: '#555' } }), codeBackground: palette.augmentColor({ color: { main: '#666' } }), @@ -85,6 +92,7 @@ export const themeFactory = (): CssVarsThemeOptions => { waveformEnd: palette.augmentColor({ color: { main: '#aaa' } }), waveformProgressMaskStart: palette.augmentColor({ color: { main: '#bbb' } }), waveformProgressMaskEnd: palette.augmentColor({ color: { main: '#555' } }), + greyedText: palette.augmentColor({ color: { main: 'rgba(255, 255, 255, 0.2)' } }), } } }