@itgenio/edik-core
TypeScript icon, indicating that this package has built-in type declarations

0.0.136 • Public • Published

Ядро проигрывателя

  • содержит встроенные компоненты и системы, такие как Текст, Изображение или Объект сцены и т.д.
  • содержит Player, который умеет проигрывать сцену
  • реализует Сериализатор, который может сериализовать/десериализовать объекты, необходимые ядру.
  • строит IMaterial - подобие AST материала, чтобы проигрыватель мог проигрывать.
  • генерирует события, чтобы потребитель смог получать оповещения и что-то делать.

Publishing

npm i - ставим зависимости

npm version patch - изменяем версию

npm publish - публикуем

Концепции

Независим от ui библиотеки

Ядро не знает про UI и как строится интерфейс: React, Svelte или чистый JS.

Ядро предоставляет состояние и логику по его изменению, а так же позволяет подписываться на события.

Чтобы UI "реагировал" на изменения в состоянии, необходимы специальные обертки:

  • React - mobx-react
  • Vue - mobx-vue
  • Svelte - svelte-mobx
  • Vanilla JS mobx (так как достаточно autorun)

Расширяемый

Функционал ядра должен быть расширяемый с помощью плагинов/скриптов.

Как пример, CodeComponent может быть вынесен в отдельный плагин и подключаться только в случае, когда на слайде нужен такой компонент. Это снизит размер ядра и позволит подключать различные реализации компонента для работы с кодом.

Зависимости

  • mobx@5 - необходим для реализации паттерна observable value
  • tslib - необходимы для работы декораторов и рефлексии (получение типов переменных)
  • typescript - само собой разумеющееся
  • nanoid для генерации id

Сохранение состояния

Существует два вида состояния: статическое и динамическое.

Статическое состояние

Необходимо для описания первоначального состояния материала:

  • список слайдов;
  • список объектов и их компонентов;
  • начальные значения переменных компонентов и объектов.

Для сохранения состояния поля нужно использовать декоратор @gserializable.

Поддерживаются следующие типы:

  • string, number, boolean
  • SlideObject
  • наследники CBaseComponent
  • наследники AbstractResource
  • массив вышеперечисленных типов

Важно 1

  1. Cтатически сохраняются только поля, помеченные атрибутом gserializable. Если логика использует поле, которое не имеет данного атрибута, то его значение не будет сохранено.
  2. Если логика создает объекты/компоненты, они будут сохранены. Поэтому, перед созданием, стоит проверять, что объекты/компоненты еще не созданы.
//Плохо, т.к. будет создаваться компонент CButton при каждом вызове onStart.
class SomeComponent {
  onStart() {
    super.onStart();

    const button = this.slideObject.addComponent(CButton);
  }
}

//Хорошо, т.к. если уже есть компонент, то он не будет создаваться.
class SomeComponent {
  onStart() {
    super.onStart();

    const button = this.slideObject.getComponent(CButton) ?? this.slideObject.addComponent(CButton);
  }
}

Важно 2

ISlideObject может не находиться на сцене, а просто быть в памяти вместе со своими компонентами, их связями.

Важно, что такие объекты и их компоненты не могут ссылаться на объекты/компоненты не из иерархии объекта!

Динамическое состояние

Хранит данные, которые необходимо сохранять в процессе прохождения материала: какие варианты в заданиях были выбраны, какой ответ был введен и т.д. Обычно такое состояние в играх называют прогрессом, сейвом и т.п.

Внутри компонента/плагина доступны функции-фабрики createOwnedValue / createSharedValue / createSecuredValue, которые создают переменные для хранения данных. Их можно типизировать, а так же задать ключ, по которому будут храниться значения в таблице.

Тогда ключ доступа будет иметь вид: ${componentId}${key}

Важно

IStorage (IStorableValue в том числе, т.к. это, по сути, обертка над ним) рассчитан на пользовательскую сессию и в дальнейшем будет использован для синхронизации состояния как между сессиями пользователя-владельца материала, так и синхронизации этого состояния другим наблюдателям.

Этот интерфейс не рассчитан на синхронизацию от наблюдателей к владельцу.

Важно 2

Внутри SyncStorage, AsyncStorage, поле cache помечено атрибутом @observable.struct. Это означает, что если происходит изменение поля, то его нужно меня полностью на новое значение, иначе не сработает реакция. Т.е. если там объект или массив, то нужно создать новый.

class SomeComponent extends CBaseComponent {
  //удобное свойство для работы со значением
  get finished() {
    //создаем API для работы со значением, указываем ключ
    return this.createOwnedValue<boolean>('finished');
  }

  onStart() {
    super.onStart();

    //какая-то логика...

    if (!this.finished.get()) {
      //что-то делаем ...

      // и ставим в true
      this.finished.set(true);
    }
  }
}

Примеры

Несколько примеров для закрепления.

Проигрывание анимации при нажатии на кнопку

Достаточно иметь статическое состояние(компонент кнопки и анимации), которое запустить анимацию при нажатии на кнопку.

class SomeComponent extends CBaseComponent {
  onStart() {
    super.onStart();

    const btn = this.slideObject.getComponent(CButton);
    const animation = this.slideObject.getComponent(CAnimation);

    this.registerDisposer(
      btn.addHandler(() => {
        animation.play();
      })
    );
  }
}

Проигрывание анимации при нажатии на кнопку единожды

Для этого нужно дописать сохранение динамического состояния. И перед тем, как проиграть анимацию нужно проверить, не была ли она уже проиграна:

class SomeComponent extends CBaseComponent {
  //создаем переменную-хранилище
  get played() {
    return this.createOwnedValue<boolean>('played');
  }

  onStart() {
    super.onStart();

    const btn = this.slideObject.getComponent(CButton);
    const animation = this.slideObject.getComponent(CAnimation);
    this.registerDisposer(
      btn.addHandler(() => {
        //если есть такой ключ - уже проиграли
        if (this.played.has()) {
          return;
        }

        animation.play();

        //запоминаем
        this.played.set(true);
      })
    );
  }
}

Счетчик кликов с сохранением состояния

class ClickCounterComponent extends CBaseComponent {
  //создаем переменную-хранилище
  get counter() {
    return this.createOwnedValue<number>('counter');
  }

  onStart() {
    super.onStart();

    const btn = this.slideObject.getComponent(CButton);

    this.registerDisposer(
      btn.addHandler(() => {
        this.counter.inc();
      })
    );
  }
}

Счетчик кликов наблюдателей

Вначале нужно "аутентифицировать" пользователей, получив какой-то userId.

Далее, нужно понять, совершал ли пользователь какое-то действие или нет.

Результат можно хранить как локально, так и сохранив в БД.

class SpectatorsClickCounterComponent extends CBaseComponent {
  //создаем переменную-хранилище
  get counter() {
    return this.createSharedValue<number>('counter');
  }

  onStart() {
    super.onStart();

    const btn = this.slideObject.getComponent(CButton);

    this.registerDisposer(btn.addHandler(() => this.counter.inc()));
  }
}

Readme

Keywords

none

Package Sidebar

Install

npm i @itgenio/edik-core

Weekly Downloads

29

Version

0.0.136

License

ISC

Unpacked Size

817 kB

Total Files

202

Last publish

Collaborators

  • pavel-r
  • brondinar
  • asosnovskiy
  • nkroe