control/entityEditor/EntityEditorView.tstypescript
import { Entity } from "../../entity/Entity";
import { DEGREES, RADIANS } from "../../math";
import { Vec3 } from "../../math/Vec3";
import { Checkbox } from "../../ui/Checkbox";
import { Input } from "../../ui/Input";
import { Slider } from "../../ui/Slider";
import { TitleBarView } from "../../ui/TitleBarView";
import { View, type IViewParams } from "../../ui/View";
import { EntityEditorScene, NATIVE_MODE, YAW_MODE } from "./EntityEditorScene";

interface IEntityEditorViewParams extends IViewParams {
    model: EntityEditorScene;
    entity?: Entity;
}

const TEMPLATE = `<div class="og-editor-panel"></div>`;

export class EntityEditorView extends View<EntityEditorScene> {
    protected _entity: Entity | null;
    protected _titleBarView: TitleBarView;
    protected _bodyEl: HTMLElement | null;

    protected _relativePositionView: Checkbox;
    protected _yawModeView: Checkbox;

    protected _lonView: Input;
    protected _latView: Input;
    protected _heightView: Input;

    protected _xView: Input;
    protected _yView: Input;
    protected _zView: Input;

    protected _absXView: Input;
    protected _absYView: Input;
    protected _absZView: Input;

    protected _pitchView: Input;
    protected _yawView: Input;
    protected _rollView: Input;

    protected _absolutePitchView: Input;
    protected _absoluteYawView: Input;
    protected _absoluteRollView: Input;

    protected _scaleView: Input;
    protected _scaleXView: Input;
    protected _scaleYView: Input;
    protected _scaleZView: Input;
    protected _opacityView: Slider;

    constructor(params: IEntityEditorViewParams) {
        super({
            template: TEMPLATE,
            model: params.model
        });

        this._entity = params.entity || null;
        this._titleBarView = new TitleBarView({
            title: "Entity"
        });
        this._bodyEl = null;

        this._relativePositionView = new Checkbox({
            label: "Relative position"
        });

        this._yawModeView = new Checkbox({
            label: "Yaw mode"
        });

        this._lonView = new Input({
            label: "Lon",
            type: "number",
            min: -180,
            max: 180,
            maxFixed: 10
        });

        this._latView = new Input({
            label: "Lat",
            type: "number",
            min: -90,
            max: 90,
            maxFixed: 10
        });

        this._heightView = new Input({
            label: "Height",
            type: "number",
            maxFixed: 4
        });

        this._xView = new Input({
            label: "X",
            type: "number",
            maxFixed: 10
        });

        this._yView = new Input({
            label: "Y",
            type: "number"
        });

        this._zView = new Input({
            label: "Z",
            type: "number"
        });

        this._absXView = new Input({
            label: "Absolute X",
            type: "number",
            maxFixed: 10
        });

        this._absYView = new Input({
            label: "Absolute Y",
            type: "number"
        });

        this._absZView = new Input({
            label: "Absolute Z",
            type: "number"
        });

        this._pitchView = new Input({
            label: "Pitch",
            type: "number",
            maxFixed: 2
        });

        this._yawView = new Input({
            label: "Yaw",
            type: "number",
            maxFixed: 2
        });

        this._rollView = new Input({
            label: "Roll",
            type: "number",
            maxFixed: 2
        });

        this._absolutePitchView = new Input({
            label: "Absolute pitch",
            type: "number",
            maxFixed: 2
        });

        this._absoluteYawView = new Input({
            label: "Absolute yaw",
            type: "number",
            maxFixed: 2
        });

        this._absoluteRollView = new Input({
            label: "Absolute roll",
            type: "number",
            maxFixed: 2
        });

        this._scaleView = new Input({
            label: "Scale",
            type: "number",
            maxFixed: 2
        });

        this._scaleXView = new Input({
            label: "Scale X",
            type: "number",
            maxFixed: 2
        });

        this._scaleYView = new Input({
            label: "Scale Y",
            type: "number",
            maxFixed: 2
        });

        this._scaleZView = new Input({
            label: "Scale Z",
            type: "number",
            maxFixed: 2
        });

        this._opacityView = new Slider({
            label: "Opacity",
            min: 0,
            max: 1,
            value: 1
        });
    }

    public bindEntity(entity: Entity): void {
        this._entity = entity;
        this.refresh(entity);
    }

    public override render(params?: unknown): this {
        super.render(params);

        this._titleBarView.appendTo(this.el!);
        this._titleBarView.events.on("change", this._onTitleBarChange);

        this._bodyEl = document.createElement("div");
        this._bodyEl.classList.add("og-editor-panel__body");
        this.el!.appendChild(this._bodyEl);

        this._relativePositionView.appendTo(this._bodyEl);
        this._yawModeView.appendTo(this._bodyEl);

        if (this.model.planet) {
            this._lonView.appendTo(this._bodyEl);
            this._latView.appendTo(this._bodyEl);
            this._heightView.appendTo(this._bodyEl);
        }
        this._xView.appendTo(this._bodyEl);
        this._yView.appendTo(this._bodyEl);
        this._zView.appendTo(this._bodyEl);

        this._absXView.appendTo(this._bodyEl);
        this._absYView.appendTo(this._bodyEl);
        this._absZView.appendTo(this._bodyEl);

        this._pitchView.appendTo(this._bodyEl);
        this._yawView.appendTo(this._bodyEl);
        this._rollView.appendTo(this._bodyEl);

        this._absolutePitchView.appendTo(this._bodyEl);
        this._absoluteYawView.appendTo(this._bodyEl);
        this._absoluteRollView.appendTo(this._bodyEl);

        this._scaleView.appendTo(this._bodyEl);
        this._scaleXView.appendTo(this._bodyEl);
        this._scaleYView.appendTo(this._bodyEl);
        this._scaleZView.appendTo(this._bodyEl);
        this._opacityView.appendTo(this._bodyEl);

        this._relativePositionView.events.on("change", this._onChangeRelativePosition);
        this._yawModeView.events.on("change", this._onChangeYawMode);

        this._lonView.events.on("change", this._onChangeLon);
        this._latView.events.on("change", this._onChangeLat);
        this._heightView.events.on("change", this._onChangeHeight);

        this._xView.events.on("change", this._onChangeX);
        this._yView.events.on("change", this._onChangeY);
        this._zView.events.on("change", this._onChangeZ);

        this._absXView.events.on("change", this._onChangeAbsoluteX);
        this._absYView.events.on("change", this._onChangeAbsoluteY);
        this._absZView.events.on("change", this._onChangeAbsoluteZ);

        this._pitchView.events.on("change", this._onChangePitch);
        this._yawView.events.on("change", this._onChangeYaw);
        this._rollView.events.on("change", this._onChangeRoll);

        this._absolutePitchView.events.on("change", this._onChangeAbsolutePitch);
        this._absoluteYawView.events.on("change", this._onChangeAbsoluteYaw);
        this._absoluteRollView.events.on("change", this._onChangeAbsoluteRoll);

        this._scaleView.events.on("change", this._onChangeScale);
        this._scaleXView.events.on("change", this._onChangeScaleX);
        this._scaleYView.events.on("change", this._onChangeScaleY);
        this._scaleZView.events.on("change", this._onChangeScaleZ);
        this._opacityView.events.on("change", this._onChangeOpacity);

        this.refresh();

        return this;
    }

    public refresh(entity: Entity | null = this._entity): void {
        if (!entity) return;

        this._entity = entity;
        this._relativePositionView.disabled = !entity.parent;
        //this._relativePositionView.stopPropagation();
        this._relativePositionView.checked = entity.relativePosition;
        const isYawMode = this.model.editMode === YAW_MODE;
        if (this._yawModeView.checked !== isYawMode) {
            this._yawModeView.events.stopPropagation();
            this._yawModeView.checked = isYawMode;
        }

        let ll = entity.getLonLat();
        this._lonView.stopPropagation();
        this._latView.stopPropagation();
        this._heightView.stopPropagation();
        this._lonView.value = ll.lon;
        this._latView.value = ll.lat;
        this._heightView.value = ll.height;

        let cart = entity.getCartesian();
        this._xView.stopPropagation();
        this._yView.stopPropagation();
        this._zView.stopPropagation();
        this._xView.value = cart.x;
        this._yView.value = cart.y;
        this._zView.value = cart.z;

        cart = entity.getAbsoluteCartesian();
        this._absXView.stopPropagation();
        this._absYView.stopPropagation();
        this._absZView.stopPropagation();
        this._absXView.value = cart.x;
        this._absYView.value = cart.y;
        this._absZView.value = cart.z;

        this.refreshRotation(entity);

        this._scaleView.stopPropagation();
        let scl = entity.getScale();
        if (scl.x === scl.y && scl.y === scl.z) {
            this._scaleView.value = scl.x;
        } else {
            this._scaleView.value = 1;
        }

        this._scaleXView.stopPropagation();
        this._scaleYView.stopPropagation();
        this._scaleZView.stopPropagation();
        this._scaleXView.value = scl.x;
        this._scaleYView.value = scl.y;
        this._scaleZView.value = scl.z;

        this._opacityView.events.stopPropagation();
        this._opacityView.value = entity.getOpacity();
    }

    public refreshRotation(entity: Entity | null = this._entity): void {
        if (!entity) return;

        this._pitchView.stopPropagation();
        this._pitchView.value = entity.getPitch() * DEGREES;
        this._yawView.stopPropagation();
        this._yawView.value = entity.getYaw() * DEGREES;
        this._rollView.stopPropagation();
        this._rollView.value = entity.getRoll() * DEGREES;

        this._absolutePitchView.stopPropagation();
        this._absolutePitchView.value = entity.getAbsolutePitch() * DEGREES;
        this._absoluteYawView.stopPropagation();
        this._absoluteYawView.value = entity.getAbsoluteYaw() * DEGREES;
        this._absoluteRollView.stopPropagation();
        this._absoluteRollView.value = entity.getAbsoluteRoll() * DEGREES;
    }

    public override remove(): void {
        this._relativePositionView.remove();
        this._yawModeView.remove();
        this._lonView.remove();
        this._latView.remove();
        this._heightView.remove();
        this._xView.remove();
        this._yView.remove();
        this._zView.remove();
        this._absXView.remove();
        this._absYView.remove();
        this._absZView.remove();
        this._pitchView.remove();
        this._yawView.remove();
        this._rollView.remove();
        this._absolutePitchView.remove();
        this._absoluteYawView.remove();
        this._absoluteRollView.remove();
        this._scaleView.remove();
        this._scaleXView.remove();
        this._scaleYView.remove();
        this._scaleZView.remove();
        this._opacityView.remove();
        this._titleBarView.events.off("change", this._onTitleBarChange);
        this._titleBarView.remove();
        this._bodyEl = null;
        super.remove();
    }

    protected _onTitleBarChange = (isCollapsed: boolean): void => {
        this._bodyEl?.classList.toggle("og-editor-panel__body_collapsed", isCollapsed);
    };

    protected _onChangeRelativePosition = (checked: boolean) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.relativePosition = checked;
            this.refresh(entity);
        }
    };

    protected _onChangeYawMode = (checked: boolean) => {
        this.model.editMode = checked ? YAW_MODE : NATIVE_MODE;
    };

    protected _onChangeLon = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let ll = entity.getLonLat();
            entity.setLonLat2(parseFloat(val), ll.lat, ll.height);
            this.refresh(entity);
        }
    };

    protected _onChangeLat = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let ll = entity.getLonLat();
            entity.setLonLat2(ll.lon, parseFloat(val), ll.height);
            this.refresh(entity);
        }
    };

    protected _onChangeHeight = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let ll = entity.getLonLat();
            entity.setLonLat2(ll.lon, ll.lat, parseFloat(val));
            this.refresh(entity);
        }
    };

    protected _onChangeX = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getCartesian();
            entity.setCartesian(parseFloat(val), cart.y, cart.z);
            this.refresh(entity);
        }
    };

    protected _onChangeY = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getCartesian();
            entity.setCartesian(cart.x, parseFloat(val), cart.z);
            this.refresh(entity);
        }
    };

    protected _onChangeZ = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getCartesian();
            entity.setCartesian(cart.x, cart.y, parseFloat(val));
            this.refresh(entity);
        }
    };

    protected _onChangeAbsoluteX = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getAbsoluteCartesian();
            entity.setAbsoluteCartesian(parseFloat(val), cart.y, cart.z);
            this.refresh(entity);
        }
    };

    protected _onChangeAbsoluteY = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getAbsoluteCartesian();
            entity.setAbsoluteCartesian(cart.x, parseFloat(val), cart.z);
            this.refresh(entity);
        }
    };

    protected _onChangeAbsoluteZ = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let cart = entity.getAbsoluteCartesian();
            entity.setAbsoluteCartesian(cart.x, cart.y, parseFloat(val));
            this.refresh(entity);
        }
    };

    protected _onChangePitch = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setPitch(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeYaw = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setYaw(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeRoll = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setRoll(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeAbsolutePitch = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setAbsolutePitch(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeAbsoluteYaw = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setAbsoluteYaw(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeAbsoluteRoll = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setAbsoluteRoll(parseFloat(val) * RADIANS);
            this.refresh(entity);
        }
    };

    protected _onChangeScale = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let s = parseFloat(val);
            entity.setScale(s);
            this._scaleXView.stopPropagation();
            this._scaleYView.stopPropagation();
            this._scaleZView.stopPropagation();
            this._scaleXView.value = s;
            this._scaleYView.value = s;
            this._scaleZView.value = s;
        }
    };

    protected _onChangeScaleX = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let s = entity.getScale();
            entity.setScale3v(new Vec3(parseFloat(val), s.y, s.z));
        }
    };

    protected _onChangeScaleY = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let s = entity.getScale();
            entity.setScale3v(new Vec3(s.x, parseFloat(val), s.z));
        }
    };

    protected _onChangeScaleZ = (val: string) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            let s = entity.getScale();
            entity.setScale3v(new Vec3(s.x, s.y, parseFloat(val)));
        }
    };

    protected _onChangeOpacity = (val: number) => {
        let entity = this.model.getSelectedEntity();
        if (entity) {
            entity.setOpacity(val);
        }
    };
}