Skip to content

EA Plugin Example — Music Player

A complete example of a Music Player plugin for the Enterprise Application (EA) platform. This plugin adds a music widget to the dashboard and a settings page for managing playlists.

File Structure

music/
├── 1.0.0/
│   ├── setting.json
│   ├── script.js
│   ├── widget.vue
│   ├── setting.vue
│   ├── README-AR.md
│   ├── README-EN.md
│   └── images/
└── database/
    └── music/
        └── data.json

setting.json

The plugin manifest. Defines the plugin identity, version, and entry point.

json
{
    "versionNumber": "0",
    "version": "1.0.0",
    "name": "music",
    "init": true,
    "type": "EA",
    "script": "script.js",
    "id": "_957744244"
}

script.js

The main registration script. This runs when the plugin loads and registers all its parts into the EA platform.

EA-specific APIs used:

  • addLanguage() — registers i18n translation keys
  • addRole() — defines permission roles (create, save, delete)
  • addTask() — mounts a widget component onto the dashboard
  • addToSetting() — adds an entry to the Settings sidebar
  • addPage() — registers a full settings page route
js
plugins["music"] = function () {
    this.addLanguage("ar", "music", {
        name: "الموسيقى",
        playlist: "قائمة التشغيل"
    });
    this.addLanguage("en", "music", {
        name: "Music",
        playlist: "Playlist"
    });

    this.addRole({
        id: "music",
        text: "Music",
        key: "music.name",
        roles: {
            delete: { text: "Delete", key: "button.delete", value: false },
            create: { text: "Create", key: "button.create", value: false },
            save:   { text: "Save",   key: "button.save",   value: false }
        }
    });

    this.addTask({
        name: "/[music]widget.vue",
        id: "init-music",
        file: ["music", "widget.vue"]
    });

    this.addToSetting({
        label: "Music",
        key: "music.name",
        icon: "mdi mdi-book-music",
        to: "/app/setting/music"
    });

    this.addPage({
        name: "/[music]setting.vue",
        path: "/app/setting/music",
        file: ["music", "setting.vue"]
    }, "setting");
}

widget.vue

The dashboard music player widget. Displays the player, playlist, and playback controls.

vue
<template>
    <div id="box" class="col-12 lg:col-6 xl:col-6">
        <div class="audioPlayer" dir="ltr">
            <a class="nav-icon" @click="isPlaylistActive = !isPlaylistActive"
               :class="{ 'isActive': isPlaylistActive }">
                <span></span><span></span><span></span>
            </a>

            <div class="audioPlayerList" :class="{ 'isActive': isPlaylistActive }">
                <div class="item"
                     v-for="(item, index) in musicPlaylist"
                     :class="{ 'isActive': currentSong == index }"
                     @click="changeSong(index), isPlaylistActive = !isPlaylistActive"
                     :key="index">
                    <p class="title">{{ item.name }}</p>
                </div>
            </div>

            <div class="audioPlayerUI" :class="[{ 'isDisabled': isPlaylistActive }]">
                <div class="albumImage">
                    <transition name="fade" mode="out-in" appear>
                        <div :class="['disc-back', currentlyPlaying ? '' : 'paused']" :key="currentSong">
                            <img :src="img()" class="disc">
                            <img v-if="posterLoad" :src="musicPlaylist[currentSong].image" class="poster">
                        </div>
                    </transition>
                </div>

                <div class="albumDetails">
                    <transition name="slide-fade" mode="out-in" appear>
                        <p class="title" :key="currentSong">{{ musicPlaylist[currentSong]?.name }}</p>
                    </transition>
                    <div :class="['wave-container', currentlyPlaying ? '' : 'paused']">
                        <div v-for="index in 20" :key="index" class="wave-bar"></div>
                    </div>
                </div>

                <div class="playerButtons w-full">
                    <a class="button" @click="prevSong()">
                        <i class="mdi mdi-skip-previous icon text-4xl" />
                    </a>
                    <a class="button play" @click="playPauseAudio()">
                        <i :class="currentlyPlaying ? 'mdi mdi-pause' : 'mdi mdi-play'" class="icon text-4xl" />
                    </a>
                    <a class="button" @click="nextSong()">
                        <i class="icon mdi mdi-skip-next text-4xl" />
                    </a>
                </div>

                <div class="timeAndProgress">
                    <div class="currentTimeContainer">
                        <span class="currentTime">{{ currentTimeShow }}</span>
                        <span class="totalTime">{{ trackDurationShow }}</span>
                    </div>
                    <div class="currentProgressBar" ref="progress" @click="clickProgress">
                        <div class="currentProgress" :style="{ width: currentProgressBar + '%' }"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
import axios from "axios";

export default {
    // ... component logic
}
</script>

setting.vue

The settings page where users manage playlists. Uses role-based button visibility (role()).

vue
<template>
  <div>
    <div class="card flex align-items-center justify-content-between">
      <h5 class="m-0">{{ $t("music.name") }}</h5>
      <div>
        <Button icon="pi pi-save"    class="p-button-text" @click="save()"
                v-if="role('music', 'create')" />
        <Button icon="pi pi-plus"    class="p-button-text"
                @click="products.playlist.push([])"
                v-if="role('music', 'save')" />
        <Button icon="pi pi-refresh" class="p-button-text" @click="init()" />
      </div>
    </div>

    <Accordion :activeIndex="0">
      <AccordionTab v-for="(item, index) in products.playlist" :key="index">
        <template #header>
          <RadioButton v-model="products.select" :value="index" />
          <span>{{ $t("music.playlist") }} {{ index + 1 }}</span>
        </template>

        <div>
          <Button icon="pi pi-plus"  class="p-button-text" @click="item.push({})" />
          <Button icon="pi pi-trash" class="p-button-text p-button-danger"
                  @click="deleteElemet(index)" />
        </div>

        <DataTable :value="item" editMode="row" @row-edit-save="onRowEditSave($event, index)"
                   v-model:editingRows="editingRows">
          <Column field="name" :header="$t('form.name')" :sortable="true">
            <template #editor="{ data, field }">
              <InputText v-model="data[field]" class="w-full" />
            </template>
          </Column>
          <Column field="path" :header="$t('form.path')" :sortable="true">
            <template #editor="{ data, field }">
              <div class="p-inputgroup flex-1">
                <InputText v-model="data[field]" readonly />
                <Button icon="pi pi-search" @click="openToGetFile(data, field)" />
              </div>
            </template>
          </Column>
          <Column v-if="role('music', 'delete')" headerStyle="width:0px;" bodyStyle="text-align:end">
            <template #body="{ index }">
              <Button icon="pi pi-trash" class="p-button-text p-button-danger"
                      @click="item.splice(index, 1)" />
            </template>
          </Column>
          <Column :rowEditor="true" headerStyle="width: 110px;" bodyStyle="text-align:end" />
        </DataTable>
      </AccordionTab>
    </Accordion>
  </div>
</template>

database/music/data.json

Initial data structure for the plugin's database. Loaded via the platform database API.

json
{
  "playlist": [],
  "select": 0
}