From 52a435b09013619209b8bcc6758baeca47d5d350 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Dec 2022 10:08:33 -0500
Subject: cleaned up animation effects to not reference presBox.  fixed anchors
 in text to have link properties set properly from properties view.

---
 src/client/views/animationtimeline/Keyframe.tsx | 426 +++++++++++++-----------
 1 file changed, 240 insertions(+), 186 deletions(-)

(limited to 'src/client/views/animationtimeline/Keyframe.tsx')

diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 92d3e2bed..21a5af83f 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -1,38 +1,37 @@
-import { action, computed, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import * as React from "react";
-import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { List } from "../../../fields/List";
-import { createSchema, defaultSpec, listSpec, makeInterface } from "../../../fields/Schema";
-import { Cast, NumCast } from "../../../fields/Types";
-import { Docs } from "../../documents/Documents";
-import { Transform } from "../../util/Transform";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import "../global/globalCssVariables.scss";
-import "./Keyframe.scss";
-import "./Timeline.scss";
-import { TimelineMenu } from "./TimelineMenu";
-
+import { action, computed, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { List } from '../../../fields/List';
+import { createSchema, defaultSpec, listSpec, makeInterface } from '../../../fields/Schema';
+import { Cast, NumCast } from '../../../fields/Types';
+import { Docs } from '../../documents/Documents';
+import { Transform } from '../../util/Transform';
+import { CollectionDockingView } from '../collections/CollectionDockingView';
+import '../global/globalCssVariables.scss';
+import { OpenWhereMod } from '../nodes/DocumentView';
+import './Keyframe.scss';
+import './Timeline.scss';
+import { TimelineMenu } from './TimelineMenu';
 
 /**
- * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also 
+ * Useful static functions that you can use. Mostly for logic, but you can also add UI logic here also
  */
 export namespace KeyframeFunc {
-
     export enum KeyframeType {
-        end = "end",
-        fade = "fade",
-        default = "default",
+        end = 'end',
+        fade = 'fade',
+        default = 'default',
     }
 
     export enum Direction {
-        left = "left",
-        right = "right"
+        left = 'left',
+        right = 'right',
     }
 
-    export const findAdjacentRegion = (dir: KeyframeFunc.Direction, currentRegion: Doc, regions: Doc[]): (RegionData | undefined) => {
-        let leftMost: (RegionData | undefined) = undefined;
-        let rightMost: (RegionData | undefined) = undefined;
+    export const findAdjacentRegion = (dir: KeyframeFunc.Direction, currentRegion: Doc, regions: Doc[]): RegionData | undefined => {
+        let leftMost: RegionData | undefined = undefined;
+        let rightMost: RegionData | undefined = undefined;
         regions.forEach(region => {
             const neighbor = RegionData(region);
             if (currentRegion.position! > neighbor.position) {
@@ -52,11 +51,12 @@ export namespace KeyframeFunc {
         }
     };
 
-    export const calcMinLeft = (region: Doc, currentBarX: number, ref?: Doc) => { //returns the time of the closet keyframe to the left
+    export const calcMinLeft = (region: Doc, currentBarX: number, ref?: Doc) => {
+        //returns the time of the closet keyframe to the left
         let leftKf: Opt<Doc>;
         let time: number = 0;
         const keyframes = DocListCast(region.keyframes!);
-        keyframes.map((kf) => {
+        keyframes.map(kf => {
             let compTime = currentBarX;
             if (ref) compTime = NumCast(ref.time);
             if (NumCast(kf.time) < compTime && NumCast(kf.time) >= time) {
@@ -67,11 +67,11 @@ export namespace KeyframeFunc {
         return leftKf;
     };
 
-
-    export const calcMinRight = (region: Doc, currentBarX: number, ref?: Doc) => { //returns the time of the closest keyframe to the right 
+    export const calcMinRight = (region: Doc, currentBarX: number, ref?: Doc) => {
+        //returns the time of the closest keyframe to the right
         let rightKf: Opt<Doc>;
         let time: number = Infinity;
-        DocListCast(region.keyframes!).forEach((kf) => {
+        DocListCast(region.keyframes!).forEach(kf => {
             let compTime = currentBarX;
             if (ref) compTime = NumCast(ref.time);
             if (NumCast(kf.time) > compTime && NumCast(kf.time) <= NumCast(time)) {
@@ -93,27 +93,31 @@ export namespace KeyframeFunc {
         return regiondata;
     };
 
-
-    export const convertPixelTime = (pos: number, unit: "mili" | "sec" | "min" | "hr", dir: "pixel" | "time", tickSpacing: number, tickIncrement: number) => {
-        const time = dir === "pixel" ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement;
+    export const convertPixelTime = (pos: number, unit: 'mili' | 'sec' | 'min' | 'hr', dir: 'pixel' | 'time', tickSpacing: number, tickIncrement: number) => {
+        const time = dir === 'pixel' ? (pos * tickSpacing) / tickIncrement : (pos / tickSpacing) * tickIncrement;
         switch (unit) {
-            case "mili": return time;
-            case "sec": return dir === "pixel" ? time / 1000 : time * 1000;
-            case "min": return dir === "pixel" ? time / 60000 : time * 60000;
-            case "hr": return dir === "pixel" ? time / 3600000 : time * 3600000;
-            default: return time;
+            case 'mili':
+                return time;
+            case 'sec':
+                return dir === 'pixel' ? time / 1000 : time * 1000;
+            case 'min':
+                return dir === 'pixel' ? time / 60000 : time * 60000;
+            case 'hr':
+                return dir === 'pixel' ? time / 3600000 : time * 3600000;
+            default:
+                return time;
         }
     };
 }
 
 export const RegionDataSchema = createSchema({
-    position: defaultSpec("number", 0),
-    duration: defaultSpec("number", 0),
+    position: defaultSpec('number', 0),
+    duration: defaultSpec('number', 0),
     keyframes: listSpec(Doc),
-    fadeIn: defaultSpec("number", 0),
-    fadeOut: defaultSpec("number", 0),
+    fadeIn: defaultSpec('number', 0),
+    fadeOut: defaultSpec('number', 0),
     functions: listSpec(Doc),
-    hasData: defaultSpec("boolean", false)
+    hasData: defaultSpec('boolean', false),
 });
 export type RegionData = makeInterface<[typeof RegionDataSchema]>;
 export const RegionData = makeInterface(RegionDataSchema);
@@ -130,50 +134,63 @@ interface IProps {
     makeKeyData: (region: RegionData, pos: number, kftype: KeyframeFunc.KeyframeType) => Doc;
 }
 
-
 /**
- * 
+ *
  * This class handles the green region stuff
  * Key facts:
- * 
+ *
  * Structure looks like this
- * 
+ *
  * region as a whole
  *  <------------------------------REGION------------------------------->
- * 
- * region broken down 
- * 
+ *
+ * region broken down
+ *
  *  <|---------|############ MAIN CONTENT #################|-----------|>      .....followed by void.........
  *  (start)                                             (Fade 2)
  *            (fade 1)                                              (finish)
- * 
- * 
- * As you can see, this is different from After Effect and Premiere Pro, but this is how TAG worked. 
- * If you want to checkout TAG, it's in the lockers, and the password is the usual lab door password. It's the blue laptop.  
- * If you want to know the exact location of the computer, message me. 
- * 
- * @author Andrew Kim 
+ *
+ *
+ * As you can see, this is different from After Effect and Premiere Pro, but this is how TAG worked.
+ * If you want to checkout TAG, it's in the lockers, and the password is the usual lab door password. It's the blue laptop.
+ * If you want to know the exact location of the computer, message me.
+ *
+ * @author Andrew Kim
  */
 @observer
 export class Keyframe extends React.Component<IProps> {
-
     @observable private _bar = React.createRef<HTMLDivElement>();
     @observable private _mouseToggled = false;
     @observable private _doubleClickEnabled = false;
 
-    @computed private get regiondata() { return RegionData(this.props.RegionData); }
-    @computed private get regions() { return DocListCast(this.props.node.regions); }
-    @computed private get keyframes() { return DocListCast(this.regiondata.keyframes); }
-    @computed private get pixelPosition() { return KeyframeFunc.convertPixelTime(this.regiondata.position, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); }
-    @computed private get pixelDuration() { return KeyframeFunc.convertPixelTime(this.regiondata.duration, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); }
-    @computed private get pixelFadeIn() { return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); }
-    @computed private get pixelFadeOut() { return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement); }
+    @computed private get regiondata() {
+        return RegionData(this.props.RegionData);
+    }
+    @computed private get regions() {
+        return DocListCast(this.props.node.regions);
+    }
+    @computed private get keyframes() {
+        return DocListCast(this.regiondata.keyframes);
+    }
+    @computed private get pixelPosition() {
+        return KeyframeFunc.convertPixelTime(this.regiondata.position, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+    }
+    @computed private get pixelDuration() {
+        return KeyframeFunc.convertPixelTime(this.regiondata.duration, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+    }
+    @computed private get pixelFadeIn() {
+        return KeyframeFunc.convertPixelTime(this.regiondata.fadeIn, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+    }
+    @computed private get pixelFadeOut() {
+        return KeyframeFunc.convertPixelTime(this.regiondata.fadeOut, 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+    }
 
     constructor(props: any) {
         super(props);
     }
     componentDidMount() {
-        setTimeout(() => {      //giving it a temporary 1sec delay... 
+        setTimeout(() => {
+            //giving it a temporary 1sec delay...
             if (!this.regiondata.keyframes) this.regiondata.keyframes = new List<Doc>();
             const start = this.props.makeKeyData(this.regiondata, this.regiondata.position, KeyframeFunc.KeyframeType.end);
             const fadeIn = this.props.makeKeyData(this.regiondata, this.regiondata.position + this.regiondata.fadeIn, KeyframeFunc.KeyframeType.fade);
@@ -202,12 +219,12 @@ export class Keyframe extends React.Component<IProps> {
                 this._doubleClickEnabled = false;
             }, 200);
             this._doubleClickEnabled = true;
-            document.addEventListener("pointermove", this.onBarPointerMove);
-            document.addEventListener("pointerup", (e: PointerEvent) => {
-                document.removeEventListener("pointermove", this.onBarPointerMove);
+            document.addEventListener('pointermove', this.onBarPointerMove);
+            document.addEventListener('pointerup', (e: PointerEvent) => {
+                document.removeEventListener('pointermove', this.onBarPointerMove);
             });
         }
-    }
+    };
 
     @action
     onBarPointerMove = (e: PointerEvent) => {
@@ -219,46 +236,46 @@ export class Keyframe extends React.Component<IProps> {
         const left = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions)!;
         const right = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions)!;
         const prevX = this.regiondata.position;
-        const futureX = this.regiondata.position + KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this.props.tickSpacing, this.props.tickIncrement);
+        const futureX = this.regiondata.position + KeyframeFunc.convertPixelTime(e.movementX, 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
         if (futureX <= 0) {
             this.regiondata.position = 0;
-        } else if ((left && left.position + left.duration >= futureX)) {
+        } else if (left && left.position + left.duration >= futureX) {
             this.regiondata.position = left.position + left.duration;
-        } else if ((right && right.position <= futureX + this.regiondata.duration)) {
+        } else if (right && right.position <= futureX + this.regiondata.duration) {
             this.regiondata.position = right.position - this.regiondata.duration;
         } else {
             this.regiondata.position = futureX;
         }
         const movement = this.regiondata.position - prevX;
-        this.keyframes.forEach(kf => kf.time = NumCast(kf.time) + movement);
-    }
+        this.keyframes.forEach(kf => (kf.time = NumCast(kf.time) + movement));
+    };
 
     @action
     onResizeLeft = (e: React.PointerEvent) => {
         e.preventDefault();
         e.stopPropagation();
-        document.addEventListener("pointermove", this.onDragResizeLeft);
-        document.addEventListener("pointerup", () => {
-            document.removeEventListener("pointermove", this.onDragResizeLeft);
+        document.addEventListener('pointermove', this.onDragResizeLeft);
+        document.addEventListener('pointerup', () => {
+            document.removeEventListener('pointermove', this.onDragResizeLeft);
         });
-    }
+    };
 
     @action
     onResizeRight = (e: React.PointerEvent) => {
         e.preventDefault();
         e.stopPropagation();
-        document.addEventListener("pointermove", this.onDragResizeRight);
-        document.addEventListener("pointerup", () => {
-            document.removeEventListener("pointermove", this.onDragResizeRight);
+        document.addEventListener('pointermove', this.onDragResizeRight);
+        document.addEventListener('pointerup', () => {
+            document.removeEventListener('pointermove', this.onDragResizeRight);
         });
-    }
+    };
 
     @action
     onDragResizeLeft = (e: PointerEvent) => {
         e.preventDefault();
         e.stopPropagation();
         const bar = this._bar.current!;
-        const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement);
+        const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
         const leftRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.left, this.regiondata, this.regions);
         if (leftRegion && this.regiondata.position + offset <= leftRegion.position + leftRegion.duration) {
             this.regiondata.position = leftRegion.position + leftRegion.duration;
@@ -275,90 +292,99 @@ export class Keyframe extends React.Component<IProps> {
         }
         this.keyframes[0].time = this.regiondata.position;
         this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn;
-    }
-
+    };
 
     @action
     onDragResizeRight = (e: PointerEvent) => {
         e.preventDefault();
         e.stopPropagation();
         const bar = this._bar.current!;
-        const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement);
+        const offset = KeyframeFunc.convertPixelTime(Math.round((e.clientX - bar.getBoundingClientRect().right) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
         const rightRegion = KeyframeFunc.findAdjacentRegion(KeyframeFunc.Direction.right, this.regiondata, this.regions);
         const fadeOutKeyframeTime = NumCast(this.keyframes[this.keyframes.length - 3].time);
-        if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= fadeOutKeyframeTime) { //case 1: when third to last keyframe is in the way 
+        if (this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut + offset <= fadeOutKeyframeTime) {
+            //case 1: when third to last keyframe is in the way
             this.regiondata.duration = fadeOutKeyframeTime - this.regiondata.position + this.regiondata.fadeOut;
-        } else if (rightRegion && (this.regiondata.position + this.regiondata.duration + offset >= rightRegion.position)) {
+        } else if (rightRegion && this.regiondata.position + this.regiondata.duration + offset >= rightRegion.position) {
             this.regiondata.duration = rightRegion.position - this.regiondata.position;
         } else {
             this.regiondata.duration += offset;
         }
         this.keyframes[this.keyframes.length - 2].time = this.regiondata.position + this.regiondata.duration - this.regiondata.fadeOut;
         this.keyframes[this.keyframes.length - 1].time = this.regiondata.position + this.regiondata.duration;
-    }
-
+    };
 
     @action
     createKeyframe = async (clientX: number) => {
         this._mouseToggled = true;
         const bar = this._bar.current!;
-        const offset = KeyframeFunc.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), "mili", "time", this.props.tickSpacing, this.props.tickIncrement);
-        if (offset > this.regiondata.fadeIn && offset < this.regiondata.duration - this.regiondata.fadeOut) { //make sure keyframe is not created inbetween fades and ends
+        const offset = KeyframeFunc.convertPixelTime(Math.round((clientX - bar.getBoundingClientRect().left) * this.props.transform.Scale), 'mili', 'time', this.props.tickSpacing, this.props.tickIncrement);
+        if (offset > this.regiondata.fadeIn && offset < this.regiondata.duration - this.regiondata.fadeOut) {
+            //make sure keyframe is not created inbetween fades and ends
             const position = this.regiondata.position;
             this.props.makeKeyData(this.regiondata, Math.round(position + offset), KeyframeFunc.KeyframeType.default);
             this.regiondata.hasData = true;
-            this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(Math.round(position + offset), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied
-
+            this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(Math.round(position + offset), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement)); //first move the keyframe to the correct location and make a copy so the correct file gets coppied
         }
-    }
-
+    };
 
     @action
     moveKeyframe = async (e: React.MouseEvent, kf: Doc) => {
         e.preventDefault();
         e.stopPropagation();
-        this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(NumCast(kf.time!), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement));
-    }
+        this.props.changeCurrentBarX(KeyframeFunc.convertPixelTime(NumCast(kf.time!), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement));
+    };
 
     /**
      * custom keyframe context menu items (when clicking on the keyframe circle)
      */
     @action
     makeKeyframeMenu = (kf: Doc, e: MouseEvent) => {
-        TimelineMenu.Instance.addItem("button", "Toggle Fade Only", () => {
+        TimelineMenu.Instance.addItem('button', 'Toggle Fade Only', () => {
             kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade;
         }),
-            TimelineMenu.Instance.addItem("button", "Show Data", action(() => {
-                const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
-                CollectionDockingView.AddSplit(kvp, "right");
-            })),
-            TimelineMenu.Instance.addItem("button", "Delete", action(() => {
-                (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
-                this.forceUpdate();
-            })),
-            TimelineMenu.Instance.addItem("input", "Move", action((val) => {
-                let cannotMove: boolean = false;
-                const kfIndex: number = this.keyframes.indexOf(kf);
-                if (val < 0 || (val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time))) {
-                    cannotMove = true;
-                }
-                if (!cannotMove) {
-                    this.keyframes[kfIndex].time = parseInt(val, 10);
-                    this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn;
-                }
-            }));
-        TimelineMenu.Instance.addMenu("Keyframe");
+            TimelineMenu.Instance.addItem(
+                'button',
+                'Show Data',
+                action(() => {
+                    const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
+                    CollectionDockingView.AddSplit(kvp, OpenWhereMod.right);
+                })
+            ),
+            TimelineMenu.Instance.addItem(
+                'button',
+                'Delete',
+                action(() => {
+                    (this.regiondata.keyframes as List<Doc>).splice(this.keyframes.indexOf(kf), 1);
+                    this.forceUpdate();
+                })
+            ),
+            TimelineMenu.Instance.addItem(
+                'input',
+                'Move',
+                action(val => {
+                    let cannotMove: boolean = false;
+                    const kfIndex: number = this.keyframes.indexOf(kf);
+                    if (val < 0 || val < NumCast(this.keyframes[kfIndex - 1].time) || val > NumCast(this.keyframes[kfIndex + 1].time)) {
+                        cannotMove = true;
+                    }
+                    if (!cannotMove) {
+                        this.keyframes[kfIndex].time = parseInt(val, 10);
+                        this.keyframes[1].time = this.regiondata.position + this.regiondata.fadeIn;
+                    }
+                })
+            );
+        TimelineMenu.Instance.addMenu('Keyframe');
         TimelineMenu.Instance.openMenu(e.clientX, e.clientY);
-    }
+    };
 
     /**
-     * context menu for region (anywhere on the green region). 
+     * context menu for region (anywhere on the green region).
      */
     @action
     makeRegionMenu = (kf: Doc, e: MouseEvent) => {
-        TimelineMenu.Instance.addItem("button", "Remove Region", () =>
-            Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)),
-            TimelineMenu.Instance.addItem("input", `fadeIn: ${this.regiondata.fadeIn}ms`, (val) => {
+        TimelineMenu.Instance.addItem('button', 'Remove Region', () => Cast(this.props.node.regions, listSpec(Doc))?.splice(this.regions.indexOf(this.props.RegionData), 1)),
+            TimelineMenu.Instance.addItem('input', `fadeIn: ${this.regiondata.fadeIn}ms`, val => {
                 runInAction(() => {
                     let cannotMove: boolean = false;
                     if (val < 0 || val > NumCast(this.keyframes[2].time) - this.regiondata.position) {
@@ -370,7 +396,7 @@ export class Keyframe extends React.Component<IProps> {
                     }
                 });
             }),
-            TimelineMenu.Instance.addItem("input", `fadeOut: ${this.regiondata.fadeOut}ms`, (val) => {
+            TimelineMenu.Instance.addItem('input', `fadeOut: ${this.regiondata.fadeOut}ms`, val => {
                 runInAction(() => {
                     let cannotMove: boolean = false;
                     if (val < 0 || val > this.regiondata.position + this.regiondata.duration - NumCast(this.keyframes[this.keyframes.length - 3].time)) {
@@ -382,34 +408,38 @@ export class Keyframe extends React.Component<IProps> {
                     }
                 });
             }),
-            TimelineMenu.Instance.addItem("input", `position: ${this.regiondata.position}ms`, (val) => {
+            TimelineMenu.Instance.addItem('input', `position: ${this.regiondata.position}ms`, val => {
                 runInAction(() => {
                     const prevPosition = this.regiondata.position;
                     let cannotMove: boolean = false;
-                    this.regions.map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })).forEach(({ pos, dur }) => {
-                        if (pos !== this.regiondata.position) {
-                            if ((val < 0) || (val > pos && val < pos + dur || (this.regiondata.duration + val > pos && this.regiondata.duration + val < pos + dur))) {
-                                cannotMove = true;
+                    this.regions
+                        .map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) }))
+                        .forEach(({ pos, dur }) => {
+                            if (pos !== this.regiondata.position) {
+                                if (val < 0 || (val > pos && val < pos + dur) || (this.regiondata.duration + val > pos && this.regiondata.duration + val < pos + dur)) {
+                                    cannotMove = true;
+                                }
                             }
-                        }
-                    });
+                        });
                     if (!cannotMove) {
                         this.regiondata.position = parseInt(val, 10);
                         this.updateKeyframes(this.regiondata.position - prevPosition);
                     }
                 });
             }),
-            TimelineMenu.Instance.addItem("input", `duration: ${this.regiondata.duration}ms`, (val) => {
+            TimelineMenu.Instance.addItem('input', `duration: ${this.regiondata.duration}ms`, val => {
                 runInAction(() => {
                     let cannotMove: boolean = false;
-                    this.regions.map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) })).forEach(({ pos, dur }) => {
-                        if (pos !== this.regiondata.position) {
-                            val += this.regiondata.position;
-                            if ((val < 0) || (val > pos && val < pos + dur)) {
-                                cannotMove = true;
+                    this.regions
+                        .map(region => ({ pos: NumCast(region.position), dur: NumCast(region.duration) }))
+                        .forEach(({ pos, dur }) => {
+                            if (pos !== this.regiondata.position) {
+                                val += this.regiondata.position;
+                                if (val < 0 || (val > pos && val < pos + dur)) {
+                                    cannotMove = true;
+                                }
                             }
-                        }
-                    });
+                        });
                     if (!cannotMove) {
                         this.regiondata.duration = parseInt(val, 10);
                         this.keyframes[this.keyframes.length - 1].time = this.regiondata.position + this.regiondata.duration;
@@ -417,9 +447,9 @@ export class Keyframe extends React.Component<IProps> {
                     }
                 });
             }),
-            TimelineMenu.Instance.addMenu("Region");
+            TimelineMenu.Instance.addMenu('Region');
         TimelineMenu.Instance.openMenu(e.clientX, e.clientY);
-    }
+    };
 
     @action
     updateKeyframes = (incr: number, filter: number[] = []) => {
@@ -428,7 +458,7 @@ export class Keyframe extends React.Component<IProps> {
                 kf.time = NumCast(kf.time) + incr;
             }
         });
-    }
+    };
 
     /**
      * hovering effect when hovered (hidden div darkens)
@@ -438,9 +468,9 @@ export class Keyframe extends React.Component<IProps> {
         e.preventDefault();
         e.stopPropagation();
         const div = ref.current!;
-        div.style.opacity = "1";
+        div.style.opacity = '1';
         Doc.BrushDoc(this.props.node);
-    }
+    };
 
     /**
      * hovering effect when hovered out (hidden div becomes invisible)
@@ -450,14 +480,12 @@ export class Keyframe extends React.Component<IProps> {
         e.preventDefault();
         e.stopPropagation();
         const div = ref.current!;
-        div.style.opacity = "0";
+        div.style.opacity = '0';
         Doc.UnBrushDoc(this.props.node);
-    }
-
+    };
 
     ///////////////////////UI STUFF /////////////////////////
 
-
     /**
      * drawing keyframe. Handles both keyframe with a circle (one that you create by double clicking) and one without circle (fades)
      * this probably needs biggest change, since everyone expected all keyframes to have a circle (and draggable)
@@ -465,32 +493,43 @@ export class Keyframe extends React.Component<IProps> {
     drawKeyframes = () => {
         const keyframeDivs: JSX.Element[] = [];
         return DocListCast(this.regiondata.keyframes).map(kf => {
-            if (kf.type as KeyframeFunc.KeyframeType !== KeyframeFunc.KeyframeType.end) {
-                return <>
-                    <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}>
-                        <div className="divider"></div>
-                        <div className="keyframeCircle keyframe-indicator"
-                            onPointerDown={(e) => { e.preventDefault(); e.stopPropagation(); this.moveKeyframe(e, kf); }}
-                            onContextMenu={(e: React.MouseEvent) => {
-                                e.preventDefault();
-                                e.stopPropagation();
-                                this.makeKeyframeMenu(kf, e.nativeEvent);
-                            }}
-                            onDoubleClick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
+            if ((kf.type as KeyframeFunc.KeyframeType) !== KeyframeFunc.KeyframeType.end) {
+                return (
+                    <>
+                        <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}>
+                            <div className="divider"></div>
+                            <div
+                                className="keyframeCircle keyframe-indicator"
+                                onPointerDown={e => {
+                                    e.preventDefault();
+                                    e.stopPropagation();
+                                    this.moveKeyframe(e, kf);
+                                }}
+                                onContextMenu={(e: React.MouseEvent) => {
+                                    e.preventDefault();
+                                    e.stopPropagation();
+                                    this.makeKeyframeMenu(kf, e.nativeEvent);
+                                }}
+                                onDoubleClick={e => {
+                                    e.preventDefault();
+                                    e.stopPropagation();
+                                }}></div>
                         </div>
-                    </div>
-                    <div className="keyframe-information" />
-                </>;
+                        <div className="keyframe-information" />
+                    </>
+                );
             } else {
-                return <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}>
-                    <div className="divider" />
-                </div>;
+                return (
+                    <div className="keyframe" style={{ left: `${KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement) - this.pixelPosition}px` }}>
+                        <div className="divider" />
+                    </div>
+                );
             }
         });
-    }
+    };
 
     /**
-     * drawing the hidden divs that partition different intervals within a region. 
+     * drawing the hidden divs that partition different intervals within a region.
      */
     @action
     drawKeyframeDividers = () => {
@@ -500,26 +539,36 @@ export class Keyframe extends React.Component<IProps> {
             if (index !== this.keyframes.length - 1) {
                 const right = this.keyframes[index + 1];
                 const bodyRef = React.createRef<HTMLDivElement>();
-                const kfPos = KeyframeFunc.convertPixelTime(NumCast(kf.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement);
-                const rightPos = KeyframeFunc.convertPixelTime(NumCast(right.time), "mili", "pixel", this.props.tickSpacing, this.props.tickIncrement);
+                const kfPos = KeyframeFunc.convertPixelTime(NumCast(kf.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
+                const rightPos = KeyframeFunc.convertPixelTime(NumCast(right.time), 'mili', 'pixel', this.props.tickSpacing, this.props.tickIncrement);
                 keyframeDividers.push(
-                    <div ref={bodyRef} className="body-container" style={{ left: `${kfPos - this.pixelPosition}px`, width: `${rightPos - kfPos}px` }}
-                        onPointerOver={(e) => { e.preventDefault(); e.stopPropagation(); this.onContainerOver(e, bodyRef); }}
-                        onPointerOut={(e) => { e.preventDefault(); e.stopPropagation(); this.onContainerOut(e, bodyRef); }}
-                        onContextMenu={(e) => {
+                    <div
+                        ref={bodyRef}
+                        className="body-container"
+                        style={{ left: `${kfPos - this.pixelPosition}px`, width: `${rightPos - kfPos}px` }}
+                        onPointerOver={e => {
+                            e.preventDefault();
+                            e.stopPropagation();
+                            this.onContainerOver(e, bodyRef);
+                        }}
+                        onPointerOut={e => {
+                            e.preventDefault();
+                            e.stopPropagation();
+                            this.onContainerOut(e, bodyRef);
+                        }}
+                        onContextMenu={e => {
                             e.preventDefault();
                             e.stopPropagation();
                             if (index !== 0 || index !== this.keyframes.length - 2) {
                                 this._mouseToggled = true;
                             }
                             this.makeRegionMenu(kf, e.nativeEvent);
-                        }}>
-                    </div>
+                        }}></div>
                 );
             }
         });
         return keyframeDividers;
-    }
+    };
 
     /**
      * rendering that green region
@@ -527,13 +576,18 @@ export class Keyframe extends React.Component<IProps> {
     //154, 206, 223
     render() {
         return (
-            <div className="bar" ref={this._bar} style={{
-                transform: `translate(${this.pixelPosition}px)`,
-                width: `${this.pixelDuration}px`,
-                background: `linear-gradient(90deg, rgba(154, 206, 223, 0) 0%, rgba(154, 206, 223, 1) ${this.pixelFadeIn / this.pixelDuration * 100}%, rgba(154, 206, 223, 1) ${(this.pixelDuration - this.pixelFadeOut) / this.pixelDuration * 100}%, rgba(154, 206, 223, 0) 100% )`
-            }}
+            <div
+                className="bar"
+                ref={this._bar}
+                style={{
+                    transform: `translate(${this.pixelPosition}px)`,
+                    width: `${this.pixelDuration}px`,
+                    background: `linear-gradient(90deg, rgba(154, 206, 223, 0) 0%, rgba(154, 206, 223, 1) ${(this.pixelFadeIn / this.pixelDuration) * 100}%, rgba(154, 206, 223, 1) ${
+                        ((this.pixelDuration - this.pixelFadeOut) / this.pixelDuration) * 100
+                    }%, rgba(154, 206, 223, 0) 100% )`,
+                }}
                 onPointerDown={this.onBarPointerDown}>
-                <div className="leftResize keyframe-indicator" onPointerDown={this.onResizeLeft} ></div>
+                <div className="leftResize keyframe-indicator" onPointerDown={this.onResizeLeft}></div>
                 {/* <div className="keyframe-information"></div> */}
                 <div className="rightResize keyframe-indicator" onPointerDown={this.onResizeRight}></div>
                 {/* <div className="keyframe-information"></div> */}
@@ -542,4 +596,4 @@ export class Keyframe extends React.Component<IProps> {
             </div>
         );
     }
-}
\ No newline at end of file
+}
-- 
cgit v1.2.3-70-g09d2


From d5f60e34e8ac9f99c0b442346db5386068af5de2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 23 Mar 2023 10:06:39 -0400
Subject: changed showing keyvalue panes to not create a document, but to use
 the LayoutTemplateString.

---
 src/client/documents/Documents.ts                    |  8 ++++----
 src/client/util/DictationManager.ts                  | 10 ----------
 src/client/views/StyleProvider.tsx                   |  5 ++---
 src/client/views/animationtimeline/Keyframe.tsx      |  8 --------
 .../views/collections/CollectionDockingView.tsx      | 20 ++++++++++----------
 src/client/views/collections/TabDocView.tsx          | 12 ++++++++----
 src/client/views/nodes/DocumentView.tsx              |  6 ++++--
 src/client/views/nodes/KeyValueBox.tsx               |  2 +-
 src/client/views/nodes/KeyValuePair.tsx              |  2 +-
 9 files changed, 30 insertions(+), 43 deletions(-)

(limited to 'src/client/views/animationtimeline/Keyframe.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3e89c8347..cb429d436 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -762,7 +762,6 @@ export namespace Docs {
                 ...(template.options || {}),
                 layout: layout.view?.LayoutString(layout.dataField),
                 data: template.data,
-                layout_keyValue: KeyValueBox.LayoutString(''),
             };
             Object.entries(options).map(pair => {
                 if (typeof pair[1] === 'string' && pair[1].startsWith('@')) {
@@ -1021,9 +1020,10 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.MARKER), new List(documents), { lat, lng, infoWindowOpen, ...options }, id);
         }
 
-        export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
-        }
+        // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView)
+        // export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
+        //     return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options });
+        // }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id);
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 203d4ad62..e116ae14b 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -323,16 +323,6 @@ export namespace DictationManager {
                 },
             ],
 
-            [
-                'open fields',
-                {
-                    action: (target: DocumentView) => {
-                        const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 });
-                        target.props.addDocTab(kvp, OpenWhere.addRight);
-                    },
-                },
-            ],
-
             [
                 'new outline',
                 {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 096396a41..d1e85a65b 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -21,6 +21,7 @@ import { FieldViewProps } from './nodes/FieldView';
 import { SliderBox } from './nodes/SliderBox';
 import './StyleProvider.scss';
 import React = require('react');
+import { KeyValueBox } from './nodes/KeyValueBox';
 
 export enum StyleProp {
     TreeViewIcon = 'treeViewIcon',
@@ -131,10 +132,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             return undefined;
         case StyleProp.WidgetColor:
             return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? 'lightgrey' : 'dimgrey';
-        // return doc = dragManager.dragDocument ? props.dragEffects.opacity??CastofOpacityonline94 : Cast())
-        // same idea for background Color
         case StyleProp.Opacity:
-            return Cast(doc?._opacity, 'number', Cast(doc?.opacity, 'number', null));
+            return props?.LayoutTemplateString?.includes(KeyValueBox.name) ? 1 : Cast(doc?._opacity, 'number', Cast(doc?.opacity, 'number', null));
         case StyleProp.HideLinkButton:
             return props?.hideLinkButton || (!selected && doc?.hideLinkButton);
         case StyleProp.FontSize:
diff --git a/src/client/views/animationtimeline/Keyframe.tsx b/src/client/views/animationtimeline/Keyframe.tsx
index 21a5af83f..3465a5283 100644
--- a/src/client/views/animationtimeline/Keyframe.tsx
+++ b/src/client/views/animationtimeline/Keyframe.tsx
@@ -343,14 +343,6 @@ export class Keyframe extends React.Component<IProps> {
         TimelineMenu.Instance.addItem('button', 'Toggle Fade Only', () => {
             kf.type = kf.type === KeyframeFunc.KeyframeType.fade ? KeyframeFunc.KeyframeType.default : KeyframeFunc.KeyframeType.fade;
         }),
-            TimelineMenu.Instance.addItem(
-                'button',
-                'Show Data',
-                action(() => {
-                    const kvp = Docs.Create.KVPDocument(kf, { _width: 300, _height: 300 });
-                    CollectionDockingView.AddSplit(kvp, OpenWhereMod.right);
-                })
-            ),
             TimelineMenu.Instance.addItem(
                 'button',
                 'Delete',
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 057c1e30f..e38905cbc 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -28,13 +28,12 @@ import React = require('react');
 import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView';
 import { OverlayView } from '../OverlayView';
 import { ScriptingRepl } from '../ScriptingRepl';
-import { ScriptField } from '../../../fields/ScriptField';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
 export class CollectionDockingView extends CollectionSubView() {
     @observable public static Instance: CollectionDockingView | undefined;
-    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number) {
+    public static makeDocumentConfig(document: Doc, panelName?: string, width?: number, keyValue?: boolean) {
         return {
             type: 'react-component',
             component: 'DocumentFrameRenderer',
@@ -42,6 +41,7 @@ export class CollectionDockingView extends CollectionSubView() {
             width: width,
             props: {
                 documentId: document[Id],
+                keyValue,
                 panelName, // name of tab that can be used to close or replace its contents
             },
         };
@@ -146,10 +146,10 @@ export class CollectionDockingView extends CollectionSubView() {
 
     @undoBatch
     @action
-    public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean): boolean {
+    public static ReplaceTab(document: Doc, panelName: OpenWhereMod, stack: any, addToSplit?: boolean, keyValue?: boolean): boolean {
         const instance = CollectionDockingView.Instance;
         if (!instance) return false;
-        const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+        const newConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
         if (!panelName && stack) {
             const activeContentItemIndex = stack.contentItems.findIndex((item: any) => item.config === stack._activeContentItem.config);
             const newContentItem = stack.layoutManager.createContentItem(newConfig, instance._goldenLayout);
@@ -171,10 +171,10 @@ export class CollectionDockingView extends CollectionSubView() {
     }
 
     @undoBatch
-    public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string) {
+    public static ToggleSplit(doc: Doc, location: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
         return CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap.keys()).findIndex(tab => tab.DashDoc === doc) !== -1
             ? CollectionDockingView.CloseSplit(doc)
-            : CollectionDockingView.AddSplit(doc, location, stack, panelName);
+            : CollectionDockingView.AddSplit(doc, location, stack, panelName, keyValue);
     }
 
     //
@@ -182,7 +182,7 @@ export class CollectionDockingView extends CollectionSubView() {
     //
     @undoBatch
     @action
-    public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string) {
+    public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) {
         if (document?._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document);
         if (!CollectionDockingView.Instance) return false;
         const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document);
@@ -193,7 +193,7 @@ export class CollectionDockingView extends CollectionSubView() {
         const instance = CollectionDockingView.Instance;
         const glayRoot = instance._goldenLayout.root;
         if (!instance) return false;
-        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName);
+        const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue);
 
         if (!pullSide && stack) {
             stack.addChild(docContentConfig, undefined);
@@ -601,6 +601,6 @@ ScriptingGlobals.add(
     'opens up document in screen overlay layer',
     '(doc: any)'
 );
-ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) {
-    CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, shiftKey);
+ScriptingGlobals.add(function useRightSplit(doc: any, addToRightSplit?: boolean) {
+    CollectionDockingView.ReplaceTab(doc, OpenWhereMod.right, undefined, addToRightSplit);
 });
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 0ab94e2e3..631b9add5 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -35,10 +35,12 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV
 import { CollectionView } from './CollectionView';
 import './TabDocView.scss';
 import React = require('react');
+import { KeyValueBox } from '../nodes/KeyValueBox';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 interface TabDocViewProps {
     documentId: FieldId;
+    keyValue?: boolean;
     glContainer: any;
 }
 @observer
@@ -348,7 +350,8 @@ export class TabDocView extends React.Component<TabDocViewProps> {
     addDocTab = (doc: Doc, location: OpenWhere) => {
         SelectionManager.DeselectAll();
         const whereFields = doc._viewType === CollectionViewType.Docking ? [OpenWhere.dashboard] : location.split(':');
-        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none;
+        const keyValue = whereFields[1]?.includes('KeyValue');
+        const whereMods: OpenWhereMod = whereFields.length > 1 ? (whereFields[1].replace('KeyValue', '') as OpenWhereMod) : OpenWhereMod.none;
         if (doc.dockingConfig) return DashboardView.openDashboard(doc);
         // prettier-ignore
         switch (whereFields[0]) {
@@ -365,9 +368,9 @@ export class TabDocView extends React.Component<TabDocViewProps> {
             case OpenWhere.dashboard:  return DashboardView.openDashboard(doc);
             case OpenWhere.fullScreen: return CollectionDockingView.OpenFullScreen(doc);
             case OpenWhere.close:      return CollectionDockingView.CloseSplit(doc, whereMods);
-            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack);
-            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack);
-            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack);
+            case OpenWhere.replace:    return CollectionDockingView.ReplaceTab(doc, whereMods, this.stack, undefined, keyValue);
+            case OpenWhere.toggle:     return CollectionDockingView.ToggleSplit(doc, whereMods, this.stack, undefined, keyValue);
+            case OpenWhere.add:default:return CollectionDockingView.AddSplit(doc, whereMods, this.stack, undefined, keyValue);
         }
     };
     remDocTab = (doc: Doc | Doc[]) => {
@@ -419,6 +422,7 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                         this._lastView = this._view;
                     })}
                     renderDepth={0}
+                    LayoutTemplateString={this.props.keyValue ? KeyValueBox.LayoutString('data') : undefined}
                     Document={this._document}
                     DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined}
                     ContainingCollectionView={undefined}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 796d2b3ca..9e4c21a29 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -54,6 +54,7 @@ import { ScriptingBox } from './ScriptingBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
+import { KeyValueBox } from './KeyValueBox';
 const { Howl } = require('howler');
 
 interface Window {
@@ -88,6 +89,7 @@ export enum OpenWhereMod {
     right = 'right',
     top = 'top',
     bottom = 'bottom',
+    rightKeyValue = 'rightKeyValue',
 }
 
 export interface DocFocusOptions {
@@ -1000,7 +1002,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         const help = cm.findByDescription('Help...');
         const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : [];
-        helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' });
+        helpItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
         !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' });
         !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' });
         !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' });
@@ -1836,7 +1838,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                             transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: isButton ? '100%' : xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`,
                             height:
-                                isButton || this.props.forceAutoHeight
+                                isButton || this.props.LayoutTemplateString?.includes(KeyValueBox.name) || this.props.forceAutoHeight
                                     ? undefined
                                     : yshift ?? (this.fitWidth ? `${this.panelHeight}px` : `${(((100 * this.effectiveNativeHeight) / this.effectiveNativeWidth) * this.props.PanelWidth()) / this.props.PanelHeight()}%`),
                         }}>
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 60417430f..9f9d93a82 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -45,7 +45,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return NumCast(this.props.Document.schemaSplitPercentage, 50);
     }
     get fieldDocToLayout() {
-        return this.props.fieldKey ? Cast(this.props.Document[this.props.fieldKey], Doc, null) : this.props.Document;
+        return this.props.fieldKey ? DocCast(this.props.Document[this.props.fieldKey], DocCast(this.props.Document)) : this.props.Document;
     }
 
     @action
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index e74ef4a39..c4adc7f1a 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
         if (value instanceof Doc) {
             e.stopPropagation();
             e.preventDefault();
-            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(Docs.Create.KVPDocument(value, { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'layer-group' });
+            ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, ((OpenWhere.addRight as string) + 'KeyValue') as OpenWhere), icon: 'layer-group' });
             ContextMenu.Instance.displayMenu(e.clientX, e.clientY);
         }
     };
-- 
cgit v1.2.3-70-g09d2

