From cf5e232488dc1389ca2ab45d1870bd0387aa3b7b Mon Sep 17 00:00:00 2001
From: Konrad Mohrfeldt <konrad.mohrfeldt@farbdev.org>
Date: Tue, 28 May 2024 11:51:11 +0200
Subject: [PATCH] feat: add m3u playlist entry type

refs #291
---
 src/components/playlist/AM3uUrlDialog.vue     | 37 +++++++++++++++++++
 src/components/playlist/APlaylistEditor.vue   | 20 ++++++++++
 .../playlist/APlaylistEntryEditor.vue         |  6 +++
 src/i18n/de.js                                |  6 +++
 src/i18n/en.js                                |  6 +++
 5 files changed, 75 insertions(+)
 create mode 100644 src/components/playlist/AM3uUrlDialog.vue

diff --git a/src/components/playlist/AM3uUrlDialog.vue b/src/components/playlist/AM3uUrlDialog.vue
new file mode 100644
index 00000000..d92fc94a
--- /dev/null
+++ b/src/components/playlist/AM3uUrlDialog.vue
@@ -0,0 +1,37 @@
+<template>
+  <AEditDialog
+    ref="dialog"
+    :title="t('playlist.editor.addM3uDialog.title')"
+    :save-label="t('playlist.editor.addM3uDialog.saveLabel')"
+    :can-save="canSave"
+    :save="() => emit('save', url)"
+    class="md:tw-w-[600px]"
+  >
+    <FormTable>
+      <FormGroup v-slot="attrs" :label="t('file.name')" center>
+        <input v-bind="attrs" v-model="name" type="text" />
+      </FormGroup>
+    </FormTable>
+  </AEditDialog>
+</template>
+
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue'
+
+import { useI18n } from '@/i18n'
+import FormGroup from '@/components/generic/FormGroup.vue'
+import FormTable from '@/components/generic/FormTable.vue'
+import AEditDialog from '@/components/generic/AEditDialog.vue'
+
+const emit = defineEmits<{
+  save: [string]
+}>()
+
+const { t } = useI18n()
+const dialog = ref()
+const name = ref('')
+const url = computed(() => 'm3u://' + name.value.trim().replace(/^m3u:\/\//i, ''))
+const canSave = computed(() => /\.m3u$/i.test(name.value))
+
+onMounted(() => dialog.value.open())
+</script>
diff --git a/src/components/playlist/APlaylistEditor.vue b/src/components/playlist/APlaylistEditor.vue
index 23e711ab..7657f926 100644
--- a/src/components/playlist/APlaylistEditor.vue
+++ b/src/components/playlist/APlaylistEditor.vue
@@ -132,6 +132,12 @@
               {{ t('playlist.editor.control.addInput') }}
             </button>
           </APermissionGuard>
+          <APermissionGuard show-permissions="program.add__m3ufile">
+            <button type="button" class="btn btn-default" @click="addM3utoPlaylist">
+              <icon-ph-playlist-light class="tw-flex-none" />
+              {{ t('playlist.editor.control.addM3u') }}
+            </button>
+          </APermissionGuard>
         </div>
       </div>
     </fieldset>
@@ -154,6 +160,12 @@
       <AInputUrlDialog @save="resolve($event)" @close="resolve(null)" />
     </GetInputUrl>
   </APermissionGuard>
+
+  <APermissionGuard show-permissions="programm.add__m3ufile">
+    <GetM3uUrl v-slot="{ resolve }">
+      <AM3uUrlDialog @save="resolve($event)" @close="resolve(null)" />
+    </GetM3uUrl>
+  </APermissionGuard>
 </template>
 
 <script lang="ts" setup>
@@ -170,6 +182,7 @@ import { ensureError, getFilenameFromURL, useAsyncFunction } from '@/util'
 import AStreamURLDialog from '@/components/playlist/AStreamURLDialog.vue'
 import AFileUrlDialog from '@/components/playlist/AFileUrlDialog.vue'
 import AInputUrlDialog from '@/components/playlist/AInputUrlDialog.vue'
+import AM3uUrlDialog from '@/components/playlist/AM3uUrlDialog.vue'
 import Loading from '@/components/generic/Loading.vue'
 import AUploadProgress from '@/components/playlist/AUploadProgress.vue'
 import SaveIndicator from '@/components/generic/SaveIndicator.vue'
@@ -201,6 +214,7 @@ const playlistStore = usePlaylistStore()
 const GetStreamUrl = createTemplatePromise<string | null>()
 const GetFileImportUrl = createTemplatePromise<string | null>()
 const GetInputUrl = createTemplatePromise<string | null>()
+const GetM3uUrl = createTemplatePromise<string | null>()
 
 const entries = useCopy(() => props.playlist?.entries ?? [], {
   save: () => updatePlaylistEntries(),
@@ -324,6 +338,12 @@ async function addInputToPlaylist() {
   await updatePlaylistEntries({ uri: inputUrl })
 }
 
+async function addM3utoPlaylist() {
+  const m3uUrl = await GetM3uUrl.start()
+  if (!m3uUrl) return
+  await updatePlaylistEntries({ uri: m3uUrl })
+}
+
 const { fn: updatePlaylistEntries, isProcessing: isUpdatingPlaylist } = useAsyncFunction(
   async function (...newEntries: Partial<PlaylistEntry>[]) {
     try {
diff --git a/src/components/playlist/APlaylistEntryEditor.vue b/src/components/playlist/APlaylistEntryEditor.vue
index ce2f57fe..a0766df3 100644
--- a/src/components/playlist/APlaylistEntryEditor.vue
+++ b/src/components/playlist/APlaylistEntryEditor.vue
@@ -44,6 +44,12 @@
           {{ entry.uri }}
         </span>
       </template>
+      <template v-else-if="type === 'm3u'">
+        <icon-ph-playlist-light class="tw-flex-none" />
+        <span class="tw-truncate">
+          {{ uri.pathname.replace(/^\/*/, '') }}
+        </span>
+      </template>
     </span>
 
     <span class="tw-ml-auto">
diff --git a/src/i18n/de.js b/src/i18n/de.js
index 68a9759f..975c8bdc 100644
--- a/src/i18n/de.js
+++ b/src/i18n/de.js
@@ -238,6 +238,7 @@ export default {
 
   file: {
     url: 'URL',
+    name: 'Dateiname',
     unnamed: 'Unbenannte Datei',
     duration: 'Dauer',
     durationUnknown: 'Unbekannte Dauer',
@@ -291,6 +292,7 @@ export default {
         importFile: 'Datei von URL importieren',
         addStream: 'Stream hinzufügen',
         addInput: 'Eingang hinzufügen',
+        addM3u: 'M3U hinzufügen',
       },
       importFileDialog: {
         title: 'Datei von URL importieren',
@@ -304,6 +306,10 @@ export default {
         title: 'Stream als Medienquelle hinzufügen',
         saveLabel: 'Stream hinzufügen',
       },
+      addM3uDialog: {
+        title: 'M3U als Medienquelle hinzufügen',
+        saveLabel: 'M3U hinzufügen',
+      },
     },
     state: {
       ok: { title: 'Perfekt' },
diff --git a/src/i18n/en.js b/src/i18n/en.js
index e4ac7217..921cb608 100644
--- a/src/i18n/en.js
+++ b/src/i18n/en.js
@@ -239,6 +239,7 @@ export default {
 
   file: {
     url: 'URL',
+    name: 'Filename',
     unnamed: 'Unnamed file',
     duration: 'Duration',
     durationUnknown: 'Unknown duration',
@@ -292,6 +293,7 @@ export default {
         importFile: 'Import file from URL',
         addStream: 'Add stream',
         addInput: 'Add input',
+        addM3u: 'Add M3U',
       },
       importFileDialog: {
         title: 'Import file from URL',
@@ -305,6 +307,10 @@ export default {
         title: 'Add stream as media source',
         saveLabel: 'Add stream',
       },
+      addM3uDialog: {
+        title: 'Add M3U as media source',
+        saveLabel: 'Add M3U',
+      },
     },
     state: {
       ok: { title: 'Perfect' },
-- 
GitLab