Eulergraph
    Preparing search index...

    typeName и сериализация cell-классов

    При сохранении сцены каждый элемент записывается в JSON с полем className — идентификатором типа. При загрузке движок ищет конструктор в elementsLib[className], создаёт экземпляр и вызывает fromJSON().

    Раньше в className попадало constructor.name. После минификации production-сборки имена классов в бандле становятся короткими (Hj, kj и т.п.), JSON перестаёт загружаться, а внешним модулям приходилось настраивать сборщик, чтобы сохранять имена.

    Сейчас используется явное поле static readonly typeName — стабильный ID, не зависящий от минификации и от любого bundler.


    На каждом конкретном cell-классе, который сериализуется или регистрируется в движке:

    import { CellParametric } from "@engine/entities"

    export class MyShape extends CellParametric {
    static readonly typeName: string = "MyShape"

    constructor(options?: CellOptions) {
    super(options)
    // ...
    }
    }

    Требования:

    Правило Пояснение
    Уникальность Два разных класса не могут иметь один typeName
    Стабильность Значение не меняют после релиза — иначе старые файлы не откроются
    Явное объявление Поле задаётся на самом классе, а не берётся из Function.name
    Тип : string Нужен для корректного переопределения в TypeScript у наследников

    Значение совпадает с тем, что попадает в JSON как className. Для совместимости с существующими файлами в движке используются имена вроде "Spline", "Link", "CellParametric".

    На абстрактном GraphicElement typeName не задаётся — только на классах, экземпляры которых реально создаются.


    В JavaScript статическое поле наследуется. Если у наследника нет своего typeName, используется значение родителя:

    // ❌ Ошибка при registerElement и при new в dev
    export class MyWire extends Segment {}

    // MyWire.typeName === "Segment" — тот же ID, что у Segment

    Правильно:

    // ✅
    export class MyWire extends Segment {
    static readonly typeName: string = "MyWire"
    }

    Проверка выполняется функцией assertCellTypeName (см. @helpers/cellTypeName):

    1. typeName не задан — ошибка с просьбой объявить поле.
    2. typeName совпадает с предком — ошибка: нужно переопределить.
    3. Дубликат в elementsLib — ошибка при registerElement.

    Проверка срабатывает при eulerGraph.registerElement(MyClass) и при new MyClass() в dev (в конструкторе GraphicElement).


    import { MyShape } from "./MyShape"

    // Вариант 1 — вручную
    eulerGraph.registerElement(MyShape)

    // Вариант 2 — через библиотеку Stencil (Library.register → registerElement для каждого shape)
    const myLib = new Library("Моя библиотека", [MyShape])
    eulerEngine.services.Stencil?.register(myLib)

    После регистрации класс доступен по ключу elementsLib["MyShape"]. Сериализация (toJSON) записывает в JSON "className": "MyShape".


    Минимальный контракт — три шага:

    import { CellParametric, Library } from "@engine/index"
    import type { CellOptions } from "@etypes/Cell"

    export class GaugeShape extends CellParametric {
    static readonly typeName: string = "GaugeShape"

    constructor(options: CellOptions = {}) {
    super({ name: "Датчик", ...options })
    }
    }

    export const gaugeLib = new Library("Датчики", [GaugeShape])

    Подключение при старте (см. Руководство пользователя):

    eulerEngine.registerStandardLibs()
    eulerEngine.services.Stencil?.register(gaugeLib)
    eulerEngine.services.Stencil?.loadLibs()

    Сборка модуля — любым инструментом. Главное — явный typeName на каждом классе фигуры.


    «объявите static readonly typeName…»

    На классе нет поля typeName. Добавьте строку из раздела «Объявление typeName».

    «переопределите static readonly typeName — сейчас унаследовано "Segment" от Segment»

    Класс наследует typeName от родителя. Задайте своё значение на дочернем классе.

    «typeName "MyShape" уже зарегистрирован другим классом»

    Такой ID уже занят в elementsLib. Выберите другое имя или не регистрируйте класс повторно.

    Сцена сохранилась, но не открывается после production-сборки

    Убедитесь, что класс зарегистрирован через registerElement / Library.register до загрузки JSON и что typeName в коде совпадает с className в файле.