|
@@ -1,2067 +1,2014 @@
|
|
|
-<template>
|
|
|
- <div>
|
|
|
- <modal
|
|
|
- :title="`${newSong ? 'Create' : 'Edit'} Song`"
|
|
|
- class="song-modal"
|
|
|
- :size="'wide'"
|
|
|
- :split="true"
|
|
|
- :intercept-close="true"
|
|
|
- @close="onCloseModal"
|
|
|
- >
|
|
|
- <template #toggleMobileSidebar>
|
|
|
- <slot name="toggleMobileSidebar" />
|
|
|
- </template>
|
|
|
- <template #sidebar>
|
|
|
- <slot name="sidebar" />
|
|
|
- </template>
|
|
|
- <template #body>
|
|
|
- <div v-if="!youtubeId && !newSong" class="notice-container">
|
|
|
- <h4>No song has been selected</h4>
|
|
|
- </div>
|
|
|
- <div v-if="songDeleted" class="notice-container">
|
|
|
- <h4>The song you were editing has been deleted</h4>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- v-if="
|
|
|
- youtubeId &&
|
|
|
- !songDataLoaded &&
|
|
|
- !songNotFound &&
|
|
|
- !newSong
|
|
|
- "
|
|
|
- class="notice-container"
|
|
|
- >
|
|
|
- <h4>Song hasn't loaded yet</h4>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- v-if="youtubeId && songNotFound && !newSong"
|
|
|
- class="notice-container"
|
|
|
- >
|
|
|
- <h4>Song was not found</h4>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="left-section"
|
|
|
- v-show="songDataLoaded && !songDeleted"
|
|
|
- >
|
|
|
- <div class="top-section">
|
|
|
- <div class="player-section">
|
|
|
- <div :id="`editSongPlayer-${modalUuid}`" />
|
|
|
+<script setup lang="ts">
|
|
|
+import { useStore } from "vuex";
|
|
|
+import {
|
|
|
+ defineAsyncComponent,
|
|
|
+ ref,
|
|
|
+ computed,
|
|
|
+ watch,
|
|
|
+ onMounted,
|
|
|
+ onBeforeUnmount
|
|
|
+} from "vue";
|
|
|
+import Toast from "toasters";
|
|
|
+import { useModalState, useModalActions } from "@/vuex_helpers";
|
|
|
+import aw from "@/aw";
|
|
|
+import ws from "@/ws";
|
|
|
+import validation from "@/validation";
|
|
|
+import keyboardShortcuts from "@/keyboardShortcuts";
|
|
|
|
|
|
- <div v-show="youtubeError" class="player-error">
|
|
|
- <h2>{{ youtubeErrorMessage }}</h2>
|
|
|
- </div>
|
|
|
+const FloatingBox = defineAsyncComponent(
|
|
|
+ () => import("@/components/FloatingBox.vue")
|
|
|
+);
|
|
|
+const SaveButton = defineAsyncComponent(
|
|
|
+ () => import("@/components/SaveButton.vue")
|
|
|
+);
|
|
|
+const AutoSuggest = defineAsyncComponent(
|
|
|
+ () => import("@/components/AutoSuggest.vue")
|
|
|
+);
|
|
|
+const Discogs = defineAsyncComponent(() => import("./Tabs/Discogs.vue"));
|
|
|
+const ReportsTab = defineAsyncComponent(() => import("./Tabs/Reports.vue"));
|
|
|
+const Youtube = defineAsyncComponent(() => import("./Tabs/Youtube.vue"));
|
|
|
+const MusareSongs = defineAsyncComponent(() => import("./Tabs/Songs.vue"));
|
|
|
+
|
|
|
+const props = defineProps({
|
|
|
+ modalUuid: { type: String, default: "" },
|
|
|
+ modalModulePath: {
|
|
|
+ type: String,
|
|
|
+ default: "modals/editSong/MODAL_UUID"
|
|
|
+ },
|
|
|
+ discogsAlbum: { type: Object, default: null },
|
|
|
+ bulk: { type: Boolean, default: false },
|
|
|
+ flagged: { type: Boolean, default: false }
|
|
|
+});
|
|
|
+
|
|
|
+const emit = defineEmits([
|
|
|
+ "error",
|
|
|
+ "savedSuccess",
|
|
|
+ "savedError",
|
|
|
+ "flagSong",
|
|
|
+ "nextSong",
|
|
|
+ "close"
|
|
|
+]);
|
|
|
+
|
|
|
+const store = useStore();
|
|
|
+
|
|
|
+const { socket } = store.state.websockets;
|
|
|
+
|
|
|
+const modals = computed(() => store.state.modalVisibility.modals);
|
|
|
+const activeModals = computed(() => store.state.modalVisibility.activeModals);
|
|
|
+
|
|
|
+const modalState = useModalState(props.modalModulePath, {
|
|
|
+ modalUuid: props.modalUuid
|
|
|
+});
|
|
|
+const tab = computed(() => modalState.tab);
|
|
|
+const video = computed(() => modalState.video);
|
|
|
+const song = computed(() => modalState.song);
|
|
|
+const youtubeId = computed(() => modalState.youtubeId);
|
|
|
+const prefillData = computed(() => modalState.prefillData);
|
|
|
+const originalSong = computed(() => modalState.originalSong);
|
|
|
+const reports = computed(() => modalState.reports);
|
|
|
+const newSong = computed(() => modalState.newSong);
|
|
|
+
|
|
|
+const songDataLoaded = ref(false);
|
|
|
+const songDeleted = ref(false);
|
|
|
+const youtubeError = ref(false);
|
|
|
+const youtubeErrorMessage = ref("");
|
|
|
+const youtubeVideoDuration = ref("0.000");
|
|
|
+const youtubeVideoCurrentTime = ref(0);
|
|
|
+const youtubeVideoNote = ref("");
|
|
|
+const useHTTPS = ref(false);
|
|
|
+const muted = ref(false);
|
|
|
+const volumeSliderValue = ref(0);
|
|
|
+const artistInputValue = ref("");
|
|
|
+const genreInputValue = ref("");
|
|
|
+const tagInputValue = ref("");
|
|
|
+const activityWatchVideoDataInterval = ref(null);
|
|
|
+const activityWatchVideoLastStatus = ref("");
|
|
|
+const activityWatchVideoLastStartDuration = ref("");
|
|
|
+const recommendedGenres = ref([
|
|
|
+ "Blues",
|
|
|
+ "Country",
|
|
|
+ "Disco",
|
|
|
+ "Funk",
|
|
|
+ "Hip-Hop",
|
|
|
+ "Jazz",
|
|
|
+ "Metal",
|
|
|
+ "Oldies",
|
|
|
+ "Other",
|
|
|
+ "Pop",
|
|
|
+ "Rap",
|
|
|
+ "Reggae",
|
|
|
+ "Rock",
|
|
|
+ "Techno",
|
|
|
+ "Trance",
|
|
|
+ "Classical",
|
|
|
+ "Instrumental",
|
|
|
+ "House",
|
|
|
+ "Electronic",
|
|
|
+ "Christian Rap",
|
|
|
+ "Lo-Fi",
|
|
|
+ "Musical",
|
|
|
+ "Rock 'n' Roll",
|
|
|
+ "Opera",
|
|
|
+ "Drum & Bass",
|
|
|
+ "Club-House",
|
|
|
+ "Indie",
|
|
|
+ "Heavy Metal",
|
|
|
+ "Christian rock",
|
|
|
+ "Dubstep"
|
|
|
+]);
|
|
|
+const autosuggest = ref({
|
|
|
+ allItems: {
|
|
|
+ artists: [],
|
|
|
+ genres: [],
|
|
|
+ tags: []
|
|
|
+ }
|
|
|
+});
|
|
|
+const songNotFound = ref(false);
|
|
|
+const showRateDropdown = ref(false);
|
|
|
+const thumbnailElement = ref();
|
|
|
+const thumbnailNotSquare = ref(false);
|
|
|
+const thumbnailWidth = ref(null);
|
|
|
+const thumbnailHeight = ref(null);
|
|
|
+const thumbnailLoadError = ref(false);
|
|
|
+const tabs = ref([]);
|
|
|
+const inputs = ref([]);
|
|
|
+const playerReady = ref(true);
|
|
|
+const interval = ref();
|
|
|
+const saveButtonRefs = ref([]);
|
|
|
+const canvasElement = ref();
|
|
|
+const genreHelper = ref();
|
|
|
+
|
|
|
+const isYoutubeThumbnail = computed(
|
|
|
+ () =>
|
|
|
+ songDataLoaded.value &&
|
|
|
+ song.value.youtubeId &&
|
|
|
+ song.value.thumbnail &&
|
|
|
+ (song.value.thumbnail.lastIndexOf("i.ytimg.com") !== -1 ||
|
|
|
+ song.value.thumbnail.lastIndexOf("img.youtube.com") !== -1)
|
|
|
+);
|
|
|
+
|
|
|
+const {
|
|
|
+ stopVideo,
|
|
|
+ hardStopVideo,
|
|
|
+ loadVideoById,
|
|
|
+ pauseVideo,
|
|
|
+ setSong,
|
|
|
+ resetSong,
|
|
|
+ updateOriginalSong,
|
|
|
+ updateSongField,
|
|
|
+ updateReports,
|
|
|
+ setPlaybackRate
|
|
|
+} = useModalActions(
|
|
|
+ props.modalModulePath,
|
|
|
+ [
|
|
|
+ "stopVideo",
|
|
|
+ "hardStopVideo",
|
|
|
+ "loadVideoById",
|
|
|
+ "pauseVideo",
|
|
|
+ "setSong",
|
|
|
+ "resetSong",
|
|
|
+ "updateOriginalSong",
|
|
|
+ "updateSongField",
|
|
|
+ "updateReports",
|
|
|
+ "setPlaybackRate"
|
|
|
+ ],
|
|
|
+ {
|
|
|
+ modalUuid: props.modalUuid
|
|
|
+ }
|
|
|
+);
|
|
|
|
|
|
- <canvas
|
|
|
- :ref="`durationCanvas-${modalUuid}`"
|
|
|
- class="duration-canvas"
|
|
|
- v-show="!youtubeError"
|
|
|
- height="20"
|
|
|
- width="530"
|
|
|
- @click="setTrackPosition($event)"
|
|
|
- />
|
|
|
- <div class="player-footer">
|
|
|
- <div class="player-footer-left">
|
|
|
- <button
|
|
|
- class="button is-primary"
|
|
|
- @click="play()"
|
|
|
- @keyup.enter="play()"
|
|
|
- v-if="video.paused"
|
|
|
- content="Resume Playback"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- <i class="material-icons">play_arrow</i>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-primary"
|
|
|
- @click="settings('pause')"
|
|
|
- @keyup.enter="settings('pause')"
|
|
|
- v-else
|
|
|
- content="Pause Playback"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- <i class="material-icons">pause</i>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-danger"
|
|
|
- @click.exact="settings('stop')"
|
|
|
- @click.shift="settings('hardStop')"
|
|
|
- @keyup.enter.exact="settings('stop')"
|
|
|
- @keyup.shift.enter="
|
|
|
- settings('hardStop')
|
|
|
- "
|
|
|
- content="Stop Playback"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- <i class="material-icons">stop</i>
|
|
|
- </button>
|
|
|
- <tippy
|
|
|
- class="playerRateDropdown"
|
|
|
- :touch="true"
|
|
|
- :interactive="true"
|
|
|
- placement="bottom"
|
|
|
- theme="dropdown"
|
|
|
- ref="dropdown"
|
|
|
- trigger="click"
|
|
|
- append-to="parent"
|
|
|
- @show="
|
|
|
- () => {
|
|
|
- showRateDropdown = true;
|
|
|
- }
|
|
|
- "
|
|
|
- @hide="
|
|
|
- () => {
|
|
|
- showRateDropdown = false;
|
|
|
- }
|
|
|
- "
|
|
|
- >
|
|
|
- <div
|
|
|
- ref="trigger"
|
|
|
- class="control has-addons"
|
|
|
- content="Set Playback Rate"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- <button class="button is-primary">
|
|
|
- <i class="material-icons"
|
|
|
- >fast_forward</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button dropdown-toggle"
|
|
|
- >
|
|
|
- <i class="material-icons">
|
|
|
- {{
|
|
|
- showRateDropdown
|
|
|
- ? "expand_more"
|
|
|
- : "expand_less"
|
|
|
- }}
|
|
|
- </i>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
+const openModal = payload =>
|
|
|
+ store.dispatch("modalVisibility/openModal", payload);
|
|
|
|
|
|
- <template #content>
|
|
|
- <div class="nav-dropdown-items">
|
|
|
- <button
|
|
|
- class="nav-item button"
|
|
|
- :class="{
|
|
|
- active:
|
|
|
- video.playbackRate ===
|
|
|
- 0.5
|
|
|
- }"
|
|
|
- title="0.5x"
|
|
|
- @click="
|
|
|
- setPlaybackRate(0.5)
|
|
|
- "
|
|
|
- >
|
|
|
- <p>0.5x</p>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="nav-item button"
|
|
|
- :class="{
|
|
|
- active:
|
|
|
- video.playbackRate ===
|
|
|
- 1
|
|
|
- }"
|
|
|
- title="1x"
|
|
|
- @click="setPlaybackRate(1)"
|
|
|
- >
|
|
|
- <p>1x</p>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="nav-item button"
|
|
|
- :class="{
|
|
|
- active:
|
|
|
- video.playbackRate ===
|
|
|
- 2
|
|
|
- }"
|
|
|
- title="2x"
|
|
|
- @click="setPlaybackRate(2)"
|
|
|
- >
|
|
|
- <p>2x</p>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </tippy>
|
|
|
- </div>
|
|
|
- <div class="player-footer-center">
|
|
|
- <span>
|
|
|
- <span>
|
|
|
- {{ youtubeVideoCurrentTime }}
|
|
|
- </span>
|
|
|
- /
|
|
|
- <span>
|
|
|
- {{ youtubeVideoDuration }}
|
|
|
- {{ youtubeVideoNote }}
|
|
|
- </span>
|
|
|
- </span>
|
|
|
- </div>
|
|
|
- <div class="player-footer-right">
|
|
|
- <p id="volume-control">
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- @click="toggleMute()"
|
|
|
- :content="`${
|
|
|
- muted ? 'Unmute' : 'Mute'
|
|
|
- }`"
|
|
|
- v-tippy
|
|
|
- >{{
|
|
|
- muted
|
|
|
- ? "volume_mute"
|
|
|
- : volumeSliderValue >= 50
|
|
|
- ? "volume_up"
|
|
|
- : "volume_down"
|
|
|
- }}</i
|
|
|
- >
|
|
|
- <input
|
|
|
- v-model="volumeSliderValue"
|
|
|
- type="range"
|
|
|
- min="0"
|
|
|
- max="100"
|
|
|
- class="volume-slider active"
|
|
|
- @change="changeVolume()"
|
|
|
- @input="changeVolume()"
|
|
|
- />
|
|
|
- </p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <song-thumbnail
|
|
|
- v-if="songDataLoaded && !songDeleted"
|
|
|
- :song="song"
|
|
|
- :fallback="false"
|
|
|
- class="thumbnail-preview"
|
|
|
- @loadError="onThumbnailLoadError"
|
|
|
- />
|
|
|
- <img
|
|
|
- v-if="
|
|
|
- !isYoutubeThumbnail &&
|
|
|
- songDataLoaded &&
|
|
|
- !songDeleted
|
|
|
- "
|
|
|
- class="thumbnail-dummy"
|
|
|
- :src="song.thumbnail"
|
|
|
- ref="thumbnailElement"
|
|
|
- @load="onThumbnailLoad"
|
|
|
- />
|
|
|
- </div>
|
|
|
+const closeCurrentModal = () => {
|
|
|
+ if (props.bulk) emit("close");
|
|
|
+ else store.dispatch("modalVisibility/closeCurrentModal");
|
|
|
+};
|
|
|
|
|
|
- <div
|
|
|
- class="edit-section"
|
|
|
- v-if="songDataLoaded && !songDeleted"
|
|
|
- >
|
|
|
- <div class="control is-grouped">
|
|
|
- <div class="title-container">
|
|
|
- <label class="label">Title</label>
|
|
|
- <p class="control has-addons">
|
|
|
- <input
|
|
|
- class="input"
|
|
|
- type="text"
|
|
|
- ref="title-input"
|
|
|
- v-model="song.title"
|
|
|
- placeholder="Enter song title..."
|
|
|
- @keyup.shift.enter="
|
|
|
- getAlbumData('title')
|
|
|
- "
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button youtube-get-button"
|
|
|
- @click="getYouTubeData('title')"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="youtube-icon"
|
|
|
- v-tippy
|
|
|
- content="Fill from YouTube"
|
|
|
- ></div>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button album-get-button"
|
|
|
- @click="getAlbumData('title')"
|
|
|
- >
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- v-tippy
|
|
|
- content="Fill from Discogs"
|
|
|
- >album</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- </div>
|
|
|
+const showTab = payload => {
|
|
|
+ if (tabs.value[`${payload}-tab`])
|
|
|
+ tabs.value[`${payload}-tab`].scrollIntoView({ block: "nearest" });
|
|
|
+ store.dispatch(
|
|
|
+ `${props.modalModulePath.replace(
|
|
|
+ "MODAL_UUID",
|
|
|
+ props.modalUuid
|
|
|
+ )}/showTab`,
|
|
|
+ payload
|
|
|
+ );
|
|
|
+};
|
|
|
|
|
|
- <div class="duration-container">
|
|
|
- <label class="label">Duration</label>
|
|
|
- <p class="control has-addons">
|
|
|
- <input
|
|
|
- class="input"
|
|
|
- type="text"
|
|
|
- placeholder="Enter song duration..."
|
|
|
- v-model.number="song.duration"
|
|
|
- @keyup.shift.enter="fillDuration()"
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button duration-fill-button"
|
|
|
- @click="fillDuration()"
|
|
|
- >
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- v-tippy
|
|
|
- content="Sync duration with YouTube"
|
|
|
- >sync</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- </div>
|
|
|
+const onThumbnailLoad = () => {
|
|
|
+ if (thumbnailElement.value) {
|
|
|
+ const height = thumbnailElement.value.naturalHeight;
|
|
|
+ const width = thumbnailElement.value.naturalWidth;
|
|
|
+
|
|
|
+ thumbnailNotSquare.value = height !== width;
|
|
|
+ thumbnailHeight.value = height;
|
|
|
+ thumbnailWidth.value = width;
|
|
|
+ } else {
|
|
|
+ thumbnailNotSquare.value = false;
|
|
|
+ thumbnailHeight.value = null;
|
|
|
+ thumbnailWidth.value = null;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- <div class="skip-duration-container">
|
|
|
- <label class="label">Skip duration</label>
|
|
|
- <p class="control">
|
|
|
- <input
|
|
|
- class="input"
|
|
|
- type="text"
|
|
|
- placeholder="Enter skip duration..."
|
|
|
- v-model.number="song.skipDuration"
|
|
|
- />
|
|
|
- </p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+const onThumbnailLoadError = error => {
|
|
|
+ thumbnailLoadError.value = error !== 0;
|
|
|
+};
|
|
|
|
|
|
- <div class="control is-grouped">
|
|
|
- <div class="album-art-container">
|
|
|
- <label class="label">
|
|
|
- Thumbnail
|
|
|
- <i
|
|
|
- v-if="
|
|
|
- thumbnailNotSquare &&
|
|
|
- !isYoutubeThumbnail
|
|
|
- "
|
|
|
- class="material-icons thumbnail-warning"
|
|
|
- content="Thumbnail not square, it will be stretched"
|
|
|
- v-tippy="{ theme: 'info' }"
|
|
|
- >
|
|
|
- warning
|
|
|
- </i>
|
|
|
- <i
|
|
|
- v-if="
|
|
|
- thumbnailLoadError &&
|
|
|
- !isYoutubeThumbnail
|
|
|
- "
|
|
|
- class="material-icons thumbnail-warning"
|
|
|
- content="Error loading thumbnail"
|
|
|
- v-tippy="{ theme: 'info' }"
|
|
|
- >
|
|
|
- warning
|
|
|
- </i>
|
|
|
- </label>
|
|
|
+const unloadSong = (_youtubeId, songId) => {
|
|
|
+ songDataLoaded.value = false;
|
|
|
+ songDeleted.value = false;
|
|
|
+ stopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ resetSong(_youtubeId);
|
|
|
+ thumbnailNotSquare.value = false;
|
|
|
+ thumbnailWidth.value = null;
|
|
|
+ thumbnailHeight.value = null;
|
|
|
+ youtubeVideoCurrentTime.value = "0.000";
|
|
|
+ youtubeVideoDuration.value = "0.000";
|
|
|
+ youtubeVideoNote.value = "";
|
|
|
+ if (songId) socket.dispatch("apis.leaveRoom", `edit-song.${songId}`);
|
|
|
+ if (saveButtonRefs.value.saveButton)
|
|
|
+ saveButtonRefs.value.saveButton.status = "default";
|
|
|
+};
|
|
|
|
|
|
- <p class="control has-addons">
|
|
|
- <input
|
|
|
- class="input"
|
|
|
- type="text"
|
|
|
- v-model="song.thumbnail"
|
|
|
- placeholder="Enter link to thumbnail..."
|
|
|
- @keyup.shift.enter="
|
|
|
- getAlbumData('albumArt')
|
|
|
- "
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button youtube-get-button"
|
|
|
- @click="getYouTubeData('thumbnail')"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="youtube-icon"
|
|
|
- v-tippy
|
|
|
- content="Fill from YouTube"
|
|
|
- ></div>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button album-get-button"
|
|
|
- @click="getAlbumData('albumArt')"
|
|
|
- >
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- v-tippy
|
|
|
- content="Fill from Discogs"
|
|
|
- >album</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- </div>
|
|
|
- <div class="youtube-id-container">
|
|
|
- <label class="label">YouTube ID</label>
|
|
|
- <p class="control">
|
|
|
- <input
|
|
|
- class="input"
|
|
|
- type="text"
|
|
|
- placeholder="Enter YouTube ID..."
|
|
|
- v-model="song.youtubeId"
|
|
|
- />
|
|
|
- </p>
|
|
|
- </div>
|
|
|
- <div class="verified-container">
|
|
|
- <label class="label">Verified</label>
|
|
|
- <p class="is-expanded checkbox-control">
|
|
|
- <label class="switch">
|
|
|
- <input
|
|
|
- type="checkbox"
|
|
|
- id="verified"
|
|
|
- v-model="song.verified"
|
|
|
- />
|
|
|
- <span class="slider round"></span>
|
|
|
- </label>
|
|
|
- </p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
+const loadSong = _youtubeId => {
|
|
|
+ console.log(`LOAD SONG ${_youtubeId}`);
|
|
|
+ songNotFound.value = false;
|
|
|
+ socket.dispatch(`songs.getSongsFromYoutubeIds`, [_youtubeId], res => {
|
|
|
+ const { songs } = res.data;
|
|
|
+ if (res.status === "success" && songs.length > 0) {
|
|
|
+ let _song = songs[0];
|
|
|
+ _song = Object.assign(_song, prefillData.value);
|
|
|
|
|
|
- <div class="control is-grouped">
|
|
|
- <div class="artists-container">
|
|
|
- <label class="label">Artists</label>
|
|
|
- <p class="control has-addons">
|
|
|
- <auto-suggest
|
|
|
- v-model="artistInputValue"
|
|
|
- ref="new-artist"
|
|
|
- placeholder="Add artist..."
|
|
|
- :all-items="
|
|
|
- autosuggest.allItems.artists
|
|
|
- "
|
|
|
- @submitted="addTag('artists')"
|
|
|
- @keyup.shift.enter="
|
|
|
- getAlbumData('artists')
|
|
|
- "
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button youtube-get-button"
|
|
|
- @click="getYouTubeData('author')"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="youtube-icon"
|
|
|
- v-tippy
|
|
|
- content="Fill from YouTube"
|
|
|
- ></div>
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button album-get-button"
|
|
|
- @click="getAlbumData('artists')"
|
|
|
- >
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- v-tippy
|
|
|
- content="Fill from Discogs"
|
|
|
- >album</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-info add-button"
|
|
|
- @click="addTag('artists')"
|
|
|
- >
|
|
|
- <i class="material-icons">add</i>
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- <div class="list-container">
|
|
|
- <div
|
|
|
- class="list-item"
|
|
|
- v-for="artist in song.artists"
|
|
|
- :key="artist"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="list-item-circle"
|
|
|
- @click="
|
|
|
- removeTag('artists', artist)
|
|
|
- "
|
|
|
- >
|
|
|
- <i class="material-icons">close</i>
|
|
|
- </div>
|
|
|
- <p>{{ artist }}</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="genres-container">
|
|
|
- <label class="label">
|
|
|
- <span>Genres</span>
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- @click="toggleGenreHelper"
|
|
|
- @dblclick="resetGenreHelper"
|
|
|
- v-tippy
|
|
|
- content="View list of genres"
|
|
|
- >info</i
|
|
|
- >
|
|
|
- </label>
|
|
|
- <p class="control has-addons">
|
|
|
- <auto-suggest
|
|
|
- v-model="genreInputValue"
|
|
|
- ref="new-genre"
|
|
|
- placeholder="Add genre..."
|
|
|
- :all-items="autosuggest.allItems.genres"
|
|
|
- @submitted="addTag('genres')"
|
|
|
- @keyup.shift.enter="
|
|
|
- getAlbumData('genres')
|
|
|
- "
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button album-get-button"
|
|
|
- @click="getAlbumData('genres')"
|
|
|
- >
|
|
|
- <i
|
|
|
- class="material-icons"
|
|
|
- v-tippy
|
|
|
- content="Fill from Discogs"
|
|
|
- >album</i
|
|
|
- >
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-info add-button"
|
|
|
- @click="addTag('genres')"
|
|
|
- >
|
|
|
- <i class="material-icons">add</i>
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- <div class="list-container">
|
|
|
- <div
|
|
|
- class="list-item"
|
|
|
- v-for="genre in song.genres"
|
|
|
- :key="genre"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="list-item-circle"
|
|
|
- @click="removeTag('genres', genre)"
|
|
|
- >
|
|
|
- <i class="material-icons">close</i>
|
|
|
- </div>
|
|
|
- <p>{{ genre }}</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="tags-container">
|
|
|
- <label class="label">Tags</label>
|
|
|
- <p class="control has-addons">
|
|
|
- <auto-suggest
|
|
|
- v-model="tagInputValue"
|
|
|
- ref="new-tag"
|
|
|
- placeholder="Add tag..."
|
|
|
- :all-items="autosuggest.allItems.tags"
|
|
|
- @submitted="addTag('tags')"
|
|
|
- />
|
|
|
- <button
|
|
|
- class="button is-info add-button"
|
|
|
- @click="addTag('tags')"
|
|
|
- >
|
|
|
- <i class="material-icons">add</i>
|
|
|
- </button>
|
|
|
- </p>
|
|
|
- <div class="list-container">
|
|
|
- <div
|
|
|
- class="list-item"
|
|
|
- v-for="tag in song.tags"
|
|
|
- :key="tag"
|
|
|
- >
|
|
|
- <div
|
|
|
- class="list-item-circle"
|
|
|
- @click="removeTag('tags', tag)"
|
|
|
- >
|
|
|
- <i class="material-icons">close</i>
|
|
|
- </div>
|
|
|
- <p>{{ tag }}</p>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div
|
|
|
- class="right-section"
|
|
|
- v-if="songDataLoaded && !songDeleted"
|
|
|
- >
|
|
|
- <div id="tabs-container">
|
|
|
- <div id="tab-selection">
|
|
|
- <button
|
|
|
- class="button is-default"
|
|
|
- :class="{ selected: tab === 'discogs' }"
|
|
|
- ref="discogs-tab"
|
|
|
- @click="showTab('discogs')"
|
|
|
- >
|
|
|
- Discogs
|
|
|
- </button>
|
|
|
- <button
|
|
|
- v-if="!newSong"
|
|
|
- class="button is-default"
|
|
|
- :class="{ selected: tab === 'reports' }"
|
|
|
- ref="reports-tab"
|
|
|
- @click="showTab('reports')"
|
|
|
- >
|
|
|
- Reports ({{ reports.length }})
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-default"
|
|
|
- :class="{ selected: tab === 'youtube' }"
|
|
|
- ref="youtube-tab"
|
|
|
- @click="showTab('youtube')"
|
|
|
- >
|
|
|
- YouTube
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-default"
|
|
|
- :class="{ selected: tab === 'musare-songs' }"
|
|
|
- ref="musare-songs-tab"
|
|
|
- @click="showTab('musare-songs')"
|
|
|
- >
|
|
|
- Songs
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <discogs
|
|
|
- class="tab"
|
|
|
- v-show="tab === 'discogs'"
|
|
|
- :bulk="bulk"
|
|
|
- :modal-uuid="modalUuid"
|
|
|
- :modal-module-path="modalModulePath"
|
|
|
- />
|
|
|
- <reports
|
|
|
- v-if="!newSong"
|
|
|
- class="tab"
|
|
|
- v-show="tab === 'reports'"
|
|
|
- :modal-uuid="modalUuid"
|
|
|
- :modal-module-path="modalModulePath"
|
|
|
- />
|
|
|
- <youtube
|
|
|
- class="tab"
|
|
|
- v-show="tab === 'youtube'"
|
|
|
- :modal-uuid="modalUuid"
|
|
|
- :modal-module-path="modalModulePath"
|
|
|
- />
|
|
|
- <musare-songs
|
|
|
- class="tab"
|
|
|
- v-show="tab === 'musare-songs'"
|
|
|
- :modal-uuid="modalUuid"
|
|
|
- :modal-module-path="modalModulePath"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- <template #footer>
|
|
|
- <div v-if="bulk">
|
|
|
- <button class="button is-primary" @click="editNextSong()">
|
|
|
- Next
|
|
|
- </button>
|
|
|
- <button
|
|
|
- class="button is-primary"
|
|
|
- @click="toggleFlag()"
|
|
|
- v-if="youtubeId && !songDeleted"
|
|
|
- >
|
|
|
- {{ flagged ? "Unflag" : "Flag" }}
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- <div v-if="!newSong && !songDeleted">
|
|
|
- <save-button
|
|
|
- ref="saveButton"
|
|
|
- @clicked="save(song, false, 'saveButton')"
|
|
|
- />
|
|
|
- <save-button
|
|
|
- ref="saveAndCloseButton"
|
|
|
- :default-message="
|
|
|
- bulk ? `Save and next` : `Save and close`
|
|
|
- "
|
|
|
- @clicked="save(song, true, 'saveAndCloseButton')"
|
|
|
- />
|
|
|
+ setSong(_song);
|
|
|
|
|
|
- <div class="right">
|
|
|
- <button
|
|
|
- class="button is-danger icon-with-button material-icons"
|
|
|
- @click.prevent="
|
|
|
- confirmAction({
|
|
|
- message:
|
|
|
- 'Removing this song will remove it from all playlists and cause a ratings recalculation.',
|
|
|
- action: 'remove',
|
|
|
- params: song._id
|
|
|
- })
|
|
|
- "
|
|
|
- content="Delete Song"
|
|
|
- v-tippy
|
|
|
- >
|
|
|
- delete_forever
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div v-else-if="newSong">
|
|
|
- <save-button
|
|
|
- ref="createButton"
|
|
|
- default-message="Create Song"
|
|
|
- @clicked="save(song, false, 'createButton', true)"
|
|
|
- />
|
|
|
- <save-button
|
|
|
- ref="createAndCloseButton"
|
|
|
- :default-message="
|
|
|
- bulk ? `Create and next` : `Create and close`
|
|
|
- "
|
|
|
- @clicked="
|
|
|
- save(song, true, 'createAndCloseButton', true)
|
|
|
- "
|
|
|
- />
|
|
|
- </div>
|
|
|
- </template>
|
|
|
- </modal>
|
|
|
- <floating-box
|
|
|
- id="genreHelper"
|
|
|
- ref="genreHelper"
|
|
|
- :column="false"
|
|
|
- title="Song Genres List"
|
|
|
- >
|
|
|
- <template #body>
|
|
|
- <span
|
|
|
- v-for="item in autosuggest.allItems.genres"
|
|
|
- :key="`genre-helper-${item}`"
|
|
|
- >
|
|
|
- {{ item }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- </floating-box>
|
|
|
- </div>
|
|
|
-</template>
|
|
|
+ songDataLoaded.value = true;
|
|
|
|
|
|
-<script>
|
|
|
-import { mapState, mapGetters, mapActions } from "vuex";
|
|
|
-import Toast from "toasters";
|
|
|
+ if (_song._id)
|
|
|
+ socket.dispatch("apis.joinRoom", `edit-song.${_song._id}`);
|
|
|
|
|
|
-import { mapModalState, mapModalActions } from "@/vuex_helpers";
|
|
|
+ if (video.value.player && video.value.player.cueVideoById) {
|
|
|
+ video.value.player.cueVideoById(_youtubeId, _song.skipDuration);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ new Toast("Song with that ID not found");
|
|
|
+ if (props.bulk) songNotFound.value = true;
|
|
|
+ if (!props.bulk) closeCurrentModal();
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
-import aw from "@/aw";
|
|
|
-import ws from "@/ws";
|
|
|
-import validation from "@/validation";
|
|
|
-import keyboardShortcuts from "@/keyboardShortcuts";
|
|
|
+ if (!newSong.value)
|
|
|
+ socket.dispatch("reports.getReportsForSong", song.value._id, res => {
|
|
|
+ updateReports(res.data.reports);
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
-import FloatingBox from "../../FloatingBox.vue";
|
|
|
-import SaveButton from "../../SaveButton.vue";
|
|
|
-import AutoSuggest from "@/components/AutoSuggest.vue";
|
|
|
-
|
|
|
-import Discogs from "./Tabs/Discogs.vue";
|
|
|
-import Reports from "./Tabs/Reports.vue";
|
|
|
-import Youtube from "./Tabs/Youtube.vue";
|
|
|
-import MusareSongs from "./Tabs/Songs.vue";
|
|
|
-
|
|
|
-export default {
|
|
|
- components: {
|
|
|
- FloatingBox,
|
|
|
- SaveButton,
|
|
|
- AutoSuggest,
|
|
|
- Discogs,
|
|
|
- Reports,
|
|
|
- Youtube,
|
|
|
- MusareSongs
|
|
|
- },
|
|
|
- props: {
|
|
|
- // songId: { type: String, default: null },
|
|
|
- modalUuid: { type: String, default: "" },
|
|
|
- modalModulePath: {
|
|
|
- type: String,
|
|
|
- default: "modals/editSong/MODAL_UUID"
|
|
|
- },
|
|
|
- discogsAlbum: { type: Object, default: null },
|
|
|
- bulk: { type: Boolean, default: false },
|
|
|
- flagged: { type: Boolean, default: false }
|
|
|
- },
|
|
|
- emits: [
|
|
|
- "error",
|
|
|
- "savedSuccess",
|
|
|
- "savedError",
|
|
|
- "flagSong",
|
|
|
- "nextSong",
|
|
|
- "close"
|
|
|
- ],
|
|
|
- data() {
|
|
|
- return {
|
|
|
- songDataLoaded: false,
|
|
|
- songDeleted: false,
|
|
|
- youtubeError: false,
|
|
|
- youtubeErrorMessage: "",
|
|
|
- focusedElementBefore: null,
|
|
|
- youtubeVideoDuration: "0.000",
|
|
|
- youtubeVideoCurrentTime: 0,
|
|
|
- youtubeVideoNote: "",
|
|
|
- useHTTPS: false,
|
|
|
- muted: false,
|
|
|
- volumeSliderValue: 0,
|
|
|
- artistInputValue: "",
|
|
|
- genreInputValue: "",
|
|
|
- tagInputValue: "",
|
|
|
- activityWatchVideoDataInterval: null,
|
|
|
- activityWatchVideoLastStatus: "",
|
|
|
- activityWatchVideoLastStartDuration: "",
|
|
|
- recommendedGenres: [
|
|
|
- "Blues",
|
|
|
- "Country",
|
|
|
- "Disco",
|
|
|
- "Funk",
|
|
|
- "Hip-Hop",
|
|
|
- "Jazz",
|
|
|
- "Metal",
|
|
|
- "Oldies",
|
|
|
- "Other",
|
|
|
- "Pop",
|
|
|
- "Rap",
|
|
|
- "Reggae",
|
|
|
- "Rock",
|
|
|
- "Techno",
|
|
|
- "Trance",
|
|
|
- "Classical",
|
|
|
- "Instrumental",
|
|
|
- "House",
|
|
|
- "Electronic",
|
|
|
- "Christian Rap",
|
|
|
- "Lo-Fi",
|
|
|
- "Musical",
|
|
|
- "Rock 'n' Roll",
|
|
|
- "Opera",
|
|
|
- "Drum & Bass",
|
|
|
- "Club-House",
|
|
|
- "Indie",
|
|
|
- "Heavy Metal",
|
|
|
- "Christian rock",
|
|
|
- "Dubstep"
|
|
|
- ],
|
|
|
- autosuggest: {
|
|
|
- allItems: {
|
|
|
- artists: [],
|
|
|
- genres: [],
|
|
|
- tags: []
|
|
|
- }
|
|
|
- },
|
|
|
- songNotFound: false,
|
|
|
- showRateDropdown: false,
|
|
|
- thumbnailNotSquare: false,
|
|
|
- thumbnailWidth: null,
|
|
|
- thumbnailHeight: null,
|
|
|
- thumbnailLoadError: false
|
|
|
- };
|
|
|
- },
|
|
|
- computed: {
|
|
|
- isYoutubeThumbnail() {
|
|
|
- return (
|
|
|
- this.songDataLoaded &&
|
|
|
- this.song.youtubeId &&
|
|
|
- this.song.thumbnail &&
|
|
|
- (this.song.thumbnail.lastIndexOf("i.ytimg.com") !== -1 ||
|
|
|
- this.song.thumbnail.lastIndexOf("img.youtube.com") !== -1)
|
|
|
- );
|
|
|
- },
|
|
|
- ...mapModalState("MODAL_MODULE_PATH", {
|
|
|
- tab: state => state.tab,
|
|
|
- video: state => state.video,
|
|
|
- song: state => state.song,
|
|
|
- youtubeId: state => state.youtubeId,
|
|
|
- prefillData: state => state.prefillData,
|
|
|
- originalSong: state => state.originalSong,
|
|
|
- reports: state => state.reports,
|
|
|
- newSong: state => state.newSong
|
|
|
- }),
|
|
|
- ...mapState("modalVisibility", {
|
|
|
- activeModals: state => state.activeModals
|
|
|
- }),
|
|
|
- ...mapGetters({
|
|
|
- socket: "websockets/getSocket"
|
|
|
- })
|
|
|
- },
|
|
|
- watch: {
|
|
|
- /* eslint-disable */
|
|
|
- "song.duration": function () {
|
|
|
- this.drawCanvas();
|
|
|
- },
|
|
|
- "song.skipDuration": function () {
|
|
|
- this.drawCanvas();
|
|
|
- },
|
|
|
- /* eslint-enable */
|
|
|
- youtubeId(youtubeId, oldYoutubeId) {
|
|
|
- console.log("NEW YOUTUBE ID", youtubeId);
|
|
|
- this.unloadSong(oldYoutubeId);
|
|
|
- this.loadSong(youtubeId);
|
|
|
- }
|
|
|
- },
|
|
|
- beforeMount() {
|
|
|
- console.log("EDITSONG BEFOREMOUNT");
|
|
|
- },
|
|
|
- async mounted() {
|
|
|
- console.log("EDITSONG MOUNTED");
|
|
|
- this.activityWatchVideoDataInterval = setInterval(() => {
|
|
|
- this.sendActivityWatchVideoData();
|
|
|
- }, 1000);
|
|
|
+const drawCanvas = () => {
|
|
|
+ if (!songDataLoaded.value || !canvasElement.value) return;
|
|
|
+ console.log(555, canvasElement.value);
|
|
|
+ const ctx = canvasElement.value.getContext("2d");
|
|
|
|
|
|
- this.useHTTPS = await lofig.get("cookie.secure");
|
|
|
+ const videoDuration = Number(youtubeVideoDuration.value);
|
|
|
|
|
|
- ws.onConnect(this.init);
|
|
|
+ const skipDuration = Number(song.value.skipDuration.value);
|
|
|
+ const duration = Number(song.value.duration.value);
|
|
|
+ const afterDuration = videoDuration - (skipDuration + duration);
|
|
|
|
|
|
- let volume = parseFloat(localStorage.getItem("volume"));
|
|
|
- volume =
|
|
|
- typeof volume === "number" && !Number.isNaN(volume) ? volume : 20;
|
|
|
- localStorage.setItem("volume", volume);
|
|
|
- this.volumeSliderValue = volume;
|
|
|
+ const width = 530;
|
|
|
|
|
|
- this.socket.on(
|
|
|
- "event:admin.song.removed",
|
|
|
- res => {
|
|
|
- if (res.data.songId === this.song._id) {
|
|
|
- this.songDeleted = true;
|
|
|
- }
|
|
|
- },
|
|
|
- { modalUuid: this.modalUuid }
|
|
|
- );
|
|
|
+ const currentTime =
|
|
|
+ video.value.player && video.value.player.getCurrentTime
|
|
|
+ ? video.value.player.getCurrentTime()
|
|
|
+ : 0;
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
|
|
|
- keyCode: 101,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- if (this.video.paused) this.play();
|
|
|
- else this.settings("pause");
|
|
|
- }
|
|
|
- });
|
|
|
+ const widthSkipDuration = (skipDuration / videoDuration) * width;
|
|
|
+ const widthDuration = (duration / videoDuration) * width;
|
|
|
+ const widthAfterDuration = (afterDuration / videoDuration) * width;
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.stopVideo", {
|
|
|
- keyCode: 101,
|
|
|
- ctrl: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.settings("stop");
|
|
|
- }
|
|
|
- });
|
|
|
+ const widthCurrentTime = (currentTime / videoDuration) * width;
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.hardStopVideo", {
|
|
|
- keyCode: 101,
|
|
|
- ctrl: true,
|
|
|
- shift: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.settings("hardStop");
|
|
|
- }
|
|
|
- });
|
|
|
+ const skipDurationColor = "#F42003";
|
|
|
+ const durationColor = "#03A9F4";
|
|
|
+ const afterDurationColor = "#41E841";
|
|
|
+ const currentDurationColor = "#3b25e8";
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.skipToLast10Secs", {
|
|
|
- keyCode: 102,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.settings("skipToLast10Secs");
|
|
|
- }
|
|
|
- });
|
|
|
+ ctx.fillStyle = skipDurationColor;
|
|
|
+ ctx.fillRect(0, 0, widthSkipDuration, 20);
|
|
|
+ ctx.fillStyle = durationColor;
|
|
|
+ ctx.fillRect(widthSkipDuration, 0, widthDuration, 20);
|
|
|
+ ctx.fillStyle = afterDurationColor;
|
|
|
+ ctx.fillRect(widthSkipDuration + widthDuration, 0, widthAfterDuration, 20);
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.lowerVolumeLarge", {
|
|
|
- keyCode: 98,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.volumeSliderValue = Math.max(
|
|
|
- 0,
|
|
|
- this.volumeSliderValue - 10
|
|
|
- );
|
|
|
- this.changeVolume();
|
|
|
- }
|
|
|
- });
|
|
|
+ ctx.fillStyle = currentDurationColor;
|
|
|
+ ctx.fillRect(widthCurrentTime, 0, 1, 20);
|
|
|
+};
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.lowerVolumeSmall", {
|
|
|
- keyCode: 98,
|
|
|
- ctrl: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.volumeSliderValue = Math.max(
|
|
|
- 0,
|
|
|
- this.volumeSliderValue - 1
|
|
|
- );
|
|
|
- this.changeVolume();
|
|
|
- }
|
|
|
- });
|
|
|
+const seekTo = position => {
|
|
|
+ pauseVideo(false);
|
|
|
+ video.value.player.seekTo(position);
|
|
|
+};
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.increaseVolumeLarge", {
|
|
|
- keyCode: 104,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.volumeSliderValue = Math.min(
|
|
|
- 100,
|
|
|
- this.volumeSliderValue + 10
|
|
|
- );
|
|
|
- this.changeVolume();
|
|
|
- }
|
|
|
+const init = () => {
|
|
|
+ if (newSong.value && !youtubeId.value && !props.bulk) {
|
|
|
+ setSong({
|
|
|
+ youtubeId: "",
|
|
|
+ title: "",
|
|
|
+ artists: [],
|
|
|
+ genres: [],
|
|
|
+ tags: [],
|
|
|
+ duration: 0,
|
|
|
+ skipDuration: 0,
|
|
|
+ thumbnail: "",
|
|
|
+ verified: false
|
|
|
});
|
|
|
+ songDataLoaded.value = true;
|
|
|
+ showTab("youtube");
|
|
|
+ } else if (youtubeId.value) loadSong(youtubeId.value);
|
|
|
+ else if (!props.bulk) {
|
|
|
+ new Toast("You can't open EditSong without editing a song");
|
|
|
+ return closeCurrentModal();
|
|
|
+ }
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.increaseVolumeSmall", {
|
|
|
- keyCode: 104,
|
|
|
- ctrl: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.volumeSliderValue = Math.min(
|
|
|
- 100,
|
|
|
- this.volumeSliderValue + 1
|
|
|
- );
|
|
|
- this.changeVolume();
|
|
|
- }
|
|
|
- });
|
|
|
+ interval.value = setInterval(() => {
|
|
|
+ if (
|
|
|
+ song.value.duration !== -1 &&
|
|
|
+ video.value.paused === false &&
|
|
|
+ playerReady.value &&
|
|
|
+ (video.value.player.getCurrentTime() - song.value.skipDuration >
|
|
|
+ song.value.duration ||
|
|
|
+ (video.value.player.getCurrentTime() > 0 &&
|
|
|
+ video.value.player.getCurrentTime() >=
|
|
|
+ video.value.player.getDuration()))
|
|
|
+ ) {
|
|
|
+ stopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ drawCanvas();
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ playerReady.value &&
|
|
|
+ video.value.player.getVideoData &&
|
|
|
+ video.value.player.getVideoData() &&
|
|
|
+ video.value.player.getVideoData().video_id === song.value.youtubeId
|
|
|
+ ) {
|
|
|
+ const currentTime = video.value.player.getCurrentTime();
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.save", {
|
|
|
- keyCode: 83,
|
|
|
- ctrl: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.save(this.song, false, "saveButton");
|
|
|
- }
|
|
|
- });
|
|
|
+ if (currentTime !== undefined)
|
|
|
+ youtubeVideoCurrentTime.value = currentTime.toFixed(3);
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.saveClose", {
|
|
|
- keyCode: 83,
|
|
|
- ctrl: true,
|
|
|
- alt: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.save(this.song, true, "saveAndCloseButton");
|
|
|
- }
|
|
|
- });
|
|
|
+ if (youtubeVideoDuration.value.indexOf(".000") !== -1) {
|
|
|
+ const duration = video.value.player.getDuration();
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.focusTitle", {
|
|
|
- keyCode: 36,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.$refs["title-input"].focus();
|
|
|
- }
|
|
|
- });
|
|
|
+ if (duration !== undefined) {
|
|
|
+ if (
|
|
|
+ `${youtubeVideoDuration.value}` ===
|
|
|
+ `${Number(song.value.duration).toFixed(3)}`
|
|
|
+ )
|
|
|
+ song.value.duration = duration.toFixed(3);
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.useAllDiscogs", {
|
|
|
- keyCode: 68,
|
|
|
- alt: true,
|
|
|
- ctrl: true,
|
|
|
- preventDefault: true,
|
|
|
- handler: () => {
|
|
|
- this.getAlbumData("title");
|
|
|
- this.getAlbumData("albumArt");
|
|
|
- this.getAlbumData("artists");
|
|
|
- this.getAlbumData("genres");
|
|
|
- }
|
|
|
- });
|
|
|
+ youtubeVideoDuration.value = duration.toFixed(3);
|
|
|
+ if (youtubeVideoDuration.value.indexOf(".000") !== -1)
|
|
|
+ youtubeVideoNote.value = "(~)";
|
|
|
+ else youtubeVideoNote.value = "";
|
|
|
|
|
|
- keyboardShortcuts.registerShortcut("editSong.closeModal", {
|
|
|
- keyCode: 27,
|
|
|
- handler: () => {
|
|
|
- if (
|
|
|
- this.modals[
|
|
|
- this.activeModals[this.activeModals.length - 1]
|
|
|
- ] === "editSong" ||
|
|
|
- this.modals[
|
|
|
- this.activeModals[this.activeModals.length - 1]
|
|
|
- ] === "editSongs"
|
|
|
- ) {
|
|
|
- this.onCloseModal();
|
|
|
+ drawCanvas();
|
|
|
}
|
|
|
}
|
|
|
- });
|
|
|
-
|
|
|
- /*
|
|
|
-
|
|
|
- editSong.pauseResume - Num 5 - Pause/resume song
|
|
|
- editSong.stopVideo - Ctrl - Num 5 - Stop
|
|
|
- editSong.hardStopVideo - Shift - Ctrl - Num 5 - Stop
|
|
|
- editSong.skipToLast10Secs - Num 6 - Skip to last 10 seconds
|
|
|
-
|
|
|
- editSong.lowerVolumeLarge - Num 2 - Volume down by 10
|
|
|
- editSong.lowerVolumeSmall - Ctrl - Num 2 - Volume down by 1
|
|
|
- editSong.increaseVolumeLarge - Num 8 - Volume up by 10
|
|
|
- editSong.increaseVolumeSmall - Ctrl - Num 8 - Volume up by 1
|
|
|
+ }
|
|
|
|
|
|
- editSong.focusTitle - Home - Focus the title input
|
|
|
- editSong.focusDicogs - End - Focus the discogs input
|
|
|
+ if (video.value.paused === false) drawCanvas();
|
|
|
+ }, 200);
|
|
|
+
|
|
|
+ if (window.YT && window.YT.Player) {
|
|
|
+ video.value.player = new window.YT.Player(
|
|
|
+ `editSongPlayer-${props.modalUuid}`,
|
|
|
+ {
|
|
|
+ height: 298,
|
|
|
+ width: 530,
|
|
|
+ videoId: null,
|
|
|
+ host: "https://www.youtube-nocookie.com",
|
|
|
+ playerVars: {
|
|
|
+ controls: 0,
|
|
|
+ iv_load_policy: 3,
|
|
|
+ rel: 0,
|
|
|
+ showinfo: 0,
|
|
|
+ autoplay: 0
|
|
|
+ },
|
|
|
+ startSeconds: song.value.skipDuration,
|
|
|
+ events: {
|
|
|
+ onReady: () => {
|
|
|
+ let volume = parseFloat(localStorage.getItem("volume"));
|
|
|
+ volume = typeof volume === "number" ? volume : 20;
|
|
|
+ video.value.player.setVolume(volume);
|
|
|
+ if (volume > 0) video.value.player.unMute();
|
|
|
+
|
|
|
+ playerReady.value = true;
|
|
|
+
|
|
|
+ if (song.value && song.value.youtubeId)
|
|
|
+ video.value.player.cueVideoById(
|
|
|
+ song.value.youtubeId,
|
|
|
+ song.value.skipDuration
|
|
|
+ );
|
|
|
|
|
|
- editSong.save - Ctrl - S - Saves song
|
|
|
- editSong.save - Ctrl - Alt - S - Saves song and closes the modal
|
|
|
- editSong.save - Ctrl - Alt - V - Saves song, verifies songs and then closes the modal
|
|
|
- editSong.close - F4 - Closes modal without saving
|
|
|
+ setPlaybackRate(null);
|
|
|
|
|
|
- editSong.useAllDiscogs - Ctrl - Alt - D - Sets all fields to the Discogs data
|
|
|
+ drawCanvas();
|
|
|
+ },
|
|
|
+ onStateChange: event => {
|
|
|
+ drawCanvas();
|
|
|
|
|
|
- Inside Discogs inputs: Ctrl - D - Sets this field to the Discogs data
|
|
|
+ if (event.data === 1) {
|
|
|
+ video.value.paused = false;
|
|
|
+ let youtubeDuration =
|
|
|
+ video.value.player.getDuration();
|
|
|
+ const newYoutubeVideoDuration =
|
|
|
+ youtubeDuration.toFixed(3);
|
|
|
|
|
|
- */
|
|
|
- },
|
|
|
- beforeUnmount() {
|
|
|
- console.log("EDITSONG BEFOREUNMOUNT");
|
|
|
- this.unloadSong(this.youtubeId, this.song._id);
|
|
|
-
|
|
|
- this.playerReady = false;
|
|
|
- clearInterval(this.interval);
|
|
|
- clearInterval(this.activityWatchVideoDataInterval);
|
|
|
-
|
|
|
- const shortcutNames = [
|
|
|
- "editSong.pauseResume",
|
|
|
- "editSong.stopVideo",
|
|
|
- "editSong.hardStopVideo",
|
|
|
- "editSong.skipToLast10Secs",
|
|
|
- "editSong.lowerVolumeLarge",
|
|
|
- "editSong.lowerVolumeSmall",
|
|
|
- "editSong.increaseVolumeLarge",
|
|
|
- "editSong.increaseVolumeSmall",
|
|
|
- "editSong.focusTitle",
|
|
|
- "editSong.focusDicogs",
|
|
|
- "editSong.save",
|
|
|
- "editSong.saveClose",
|
|
|
- "editSong.useAllDiscogs",
|
|
|
- "editSong.closeModal"
|
|
|
- ];
|
|
|
-
|
|
|
- shortcutNames.forEach(shortcutName => {
|
|
|
- keyboardShortcuts.unregisterShortcut(shortcutName);
|
|
|
- });
|
|
|
+ if (
|
|
|
+ youtubeVideoDuration.value.indexOf(".000") !==
|
|
|
+ -1 &&
|
|
|
+ `${youtubeVideoDuration.value}` !==
|
|
|
+ `${newYoutubeVideoDuration}`
|
|
|
+ ) {
|
|
|
+ const songDurationNumber = Number(
|
|
|
+ song.value.duration
|
|
|
+ );
|
|
|
+ const songDurationNumber2 =
|
|
|
+ Number(song.value.duration) + 1;
|
|
|
+ const songDurationNumber3 =
|
|
|
+ Number(song.value.duration) - 1;
|
|
|
+ const fixedSongDuration =
|
|
|
+ songDurationNumber.toFixed(3);
|
|
|
+ const fixedSongDuration2 =
|
|
|
+ songDurationNumber2.toFixed(3);
|
|
|
+ const fixedSongDuration3 =
|
|
|
+ songDurationNumber3.toFixed(3);
|
|
|
+
|
|
|
+ if (
|
|
|
+ `${youtubeVideoDuration.value}` ===
|
|
|
+ `${Number(song.value.duration).toFixed(
|
|
|
+ 3
|
|
|
+ )}` &&
|
|
|
+ (fixedSongDuration ===
|
|
|
+ youtubeVideoDuration.value ||
|
|
|
+ fixedSongDuration2 ===
|
|
|
+ youtubeVideoDuration.value ||
|
|
|
+ fixedSongDuration3 ===
|
|
|
+ youtubeVideoDuration.value)
|
|
|
+ )
|
|
|
+ song.value.duration =
|
|
|
+ newYoutubeVideoDuration;
|
|
|
+
|
|
|
+ youtubeVideoDuration.value =
|
|
|
+ newYoutubeVideoDuration;
|
|
|
+ if (
|
|
|
+ youtubeVideoDuration.value.indexOf(
|
|
|
+ ".000"
|
|
|
+ ) !== -1
|
|
|
+ )
|
|
|
+ youtubeVideoNote.value = "(~)";
|
|
|
+ else youtubeVideoNote.value = "";
|
|
|
+ }
|
|
|
|
|
|
- if (!this.bulk) {
|
|
|
- // Delete the VueX module that was created for this modal, after all other cleanup tasks are performed
|
|
|
- this.$store.unregisterModule([
|
|
|
- "modals",
|
|
|
- "editSong",
|
|
|
- this.modalUuid
|
|
|
- ]);
|
|
|
- } else {
|
|
|
- console.log("UNREGISTER EDITSONG");
|
|
|
- this.$store.unregisterModule([
|
|
|
- "modals",
|
|
|
- "editSongs",
|
|
|
- this.modalUuid,
|
|
|
- "editSong"
|
|
|
- ]);
|
|
|
- }
|
|
|
- },
|
|
|
- unmounted() {
|
|
|
- console.log("EDITSONG UNMOUNTED");
|
|
|
- },
|
|
|
- methods: {
|
|
|
- onThumbnailLoad() {
|
|
|
- if (this.$refs.thumbnailElement) {
|
|
|
- const thumbnailHeight =
|
|
|
- this.$refs.thumbnailElement.naturalHeight;
|
|
|
- const thumbnailWidth = this.$refs.thumbnailElement.naturalWidth;
|
|
|
-
|
|
|
- this.thumbnailNotSquare = thumbnailHeight !== thumbnailWidth;
|
|
|
- this.thumbnailHeight = thumbnailHeight;
|
|
|
- this.thumbnailWidth = thumbnailWidth;
|
|
|
- } else {
|
|
|
- this.thumbnailNotSquare = false;
|
|
|
- this.thumbnailHeight = null;
|
|
|
- this.thumbnailWidth = null;
|
|
|
- }
|
|
|
- },
|
|
|
- onThumbnailLoadError(error) {
|
|
|
- this.thumbnailLoadError = error !== 0;
|
|
|
- },
|
|
|
- init() {
|
|
|
- if (this.newSong && !this.youtubeId && !this.bulk) {
|
|
|
- this.setSong({
|
|
|
- youtubeId: "",
|
|
|
- title: "",
|
|
|
- artists: [],
|
|
|
- genres: [],
|
|
|
- tags: [],
|
|
|
- duration: 0,
|
|
|
- skipDuration: 0,
|
|
|
- thumbnail: "",
|
|
|
- verified: false
|
|
|
- });
|
|
|
- this.songDataLoaded = true;
|
|
|
- this.showTab("youtube");
|
|
|
- } else if (this.youtubeId) this.loadSong(this.youtubeId);
|
|
|
- else if (!this.bulk) {
|
|
|
- new Toast("You can't open EditSong without editing a song");
|
|
|
- return this.closeModal("editSong");
|
|
|
- }
|
|
|
+ if (song.value.duration === -1)
|
|
|
+ song.value.duration =
|
|
|
+ youtubeVideoDuration.value;
|
|
|
|
|
|
- this.interval = setInterval(() => {
|
|
|
- if (
|
|
|
- this.song.duration !== -1 &&
|
|
|
- this.video.paused === false &&
|
|
|
- this.playerReady &&
|
|
|
- (this.video.player.getCurrentTime() -
|
|
|
- this.song.skipDuration >
|
|
|
- this.song.duration ||
|
|
|
- (this.video.player.getCurrentTime() > 0 &&
|
|
|
- this.video.player.getCurrentTime() >=
|
|
|
- this.video.player.getDuration()))
|
|
|
- ) {
|
|
|
- this.stopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- this.drawCanvas();
|
|
|
- }
|
|
|
- if (
|
|
|
- this.playerReady &&
|
|
|
- this.video.player.getVideoData &&
|
|
|
- this.video.player.getVideoData() &&
|
|
|
- this.video.player.getVideoData().video_id ===
|
|
|
- this.song.youtubeId
|
|
|
- ) {
|
|
|
- const currentTime = this.video.player.getCurrentTime();
|
|
|
-
|
|
|
- if (currentTime !== undefined)
|
|
|
- this.youtubeVideoCurrentTime = currentTime.toFixed(3);
|
|
|
-
|
|
|
- if (this.youtubeVideoDuration.indexOf(".000") !== -1) {
|
|
|
- const duration = this.video.player.getDuration();
|
|
|
-
|
|
|
- if (duration !== undefined) {
|
|
|
- if (
|
|
|
- `${this.youtubeVideoDuration}` ===
|
|
|
- `${Number(this.song.duration).toFixed(3)}`
|
|
|
- )
|
|
|
- this.song.duration = duration.toFixed(3);
|
|
|
+ youtubeDuration -= song.value.skipDuration;
|
|
|
+ if (song.value.duration > youtubeDuration + 1) {
|
|
|
+ stopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ return new Toast(
|
|
|
+ "Video can't play. Specified duration is bigger than the YouTube song duration."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (song.value.duration <= 0) {
|
|
|
+ stopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ return new Toast(
|
|
|
+ "Video can't play. Specified duration has to be more than 0 seconds."
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- this.youtubeVideoDuration = duration.toFixed(3);
|
|
|
if (
|
|
|
- this.youtubeVideoDuration.indexOf(".000") !== -1
|
|
|
- )
|
|
|
- this.youtubeVideoNote = "(~)";
|
|
|
- else this.youtubeVideoNote = "";
|
|
|
+ video.value.player.getCurrentTime() <
|
|
|
+ song.value.skipDuration
|
|
|
+ ) {
|
|
|
+ return seekTo(song.value.skipDuration);
|
|
|
+ }
|
|
|
|
|
|
- this.drawCanvas();
|
|
|
+ setPlaybackRate(null);
|
|
|
+ } else if (event.data === 2) {
|
|
|
+ video.value.paused = true;
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
|
|
|
- if (this.video.paused === false) this.drawCanvas();
|
|
|
- }, 200);
|
|
|
-
|
|
|
- if (window.YT && window.YT.Player) {
|
|
|
- this.video.player = new window.YT.Player(
|
|
|
- `editSongPlayer-${this.modalUuid}`,
|
|
|
- {
|
|
|
- height: 298,
|
|
|
- width: 530,
|
|
|
- videoId: null,
|
|
|
- host: "https://www.youtube-nocookie.com",
|
|
|
- playerVars: {
|
|
|
- controls: 0,
|
|
|
- iv_load_policy: 3,
|
|
|
- rel: 0,
|
|
|
- showinfo: 0,
|
|
|
- autoplay: 0
|
|
|
- },
|
|
|
- startSeconds: this.song.skipDuration,
|
|
|
- events: {
|
|
|
- onReady: () => {
|
|
|
- let volume = parseFloat(
|
|
|
- localStorage.getItem("volume")
|
|
|
- );
|
|
|
- volume =
|
|
|
- typeof volume === "number" ? volume : 20;
|
|
|
- this.video.player.setVolume(volume);
|
|
|
- if (volume > 0) this.video.player.unMute();
|
|
|
-
|
|
|
- this.playerReady = true;
|
|
|
-
|
|
|
- if (this.song && this.song.youtubeId)
|
|
|
- this.video.player.cueVideoById(
|
|
|
- this.song.youtubeId,
|
|
|
- this.song.skipDuration
|
|
|
- );
|
|
|
-
|
|
|
- this.setPlaybackRate(null);
|
|
|
-
|
|
|
- this.drawCanvas();
|
|
|
- },
|
|
|
- onStateChange: event => {
|
|
|
- this.drawCanvas();
|
|
|
-
|
|
|
- if (event.data === 1) {
|
|
|
- this.video.paused = false;
|
|
|
- let youtubeDuration =
|
|
|
- this.video.player.getDuration();
|
|
|
- const newYoutubeVideoDuration =
|
|
|
- youtubeDuration.toFixed(3);
|
|
|
-
|
|
|
- if (
|
|
|
- this.youtubeVideoDuration.indexOf(
|
|
|
- ".000"
|
|
|
- ) !== -1 &&
|
|
|
- `${this.youtubeVideoDuration}` !==
|
|
|
- `${newYoutubeVideoDuration}`
|
|
|
- ) {
|
|
|
- const songDurationNumber = Number(
|
|
|
- this.song.duration
|
|
|
- );
|
|
|
- const songDurationNumber2 =
|
|
|
- Number(this.song.duration) + 1;
|
|
|
- const songDurationNumber3 =
|
|
|
- Number(this.song.duration) - 1;
|
|
|
- const fixedSongDuration =
|
|
|
- songDurationNumber.toFixed(3);
|
|
|
- const fixedSongDuration2 =
|
|
|
- songDurationNumber2.toFixed(3);
|
|
|
- const fixedSongDuration3 =
|
|
|
- songDurationNumber3.toFixed(3);
|
|
|
-
|
|
|
- if (
|
|
|
- `${this.youtubeVideoDuration}` ===
|
|
|
- `${Number(
|
|
|
- this.song.duration
|
|
|
- ).toFixed(3)}` &&
|
|
|
- (fixedSongDuration ===
|
|
|
- this.youtubeVideoDuration ||
|
|
|
- fixedSongDuration2 ===
|
|
|
- this.youtubeVideoDuration ||
|
|
|
- fixedSongDuration3 ===
|
|
|
- this.youtubeVideoDuration)
|
|
|
- )
|
|
|
- this.song.duration =
|
|
|
- newYoutubeVideoDuration;
|
|
|
-
|
|
|
- this.youtubeVideoDuration =
|
|
|
- newYoutubeVideoDuration;
|
|
|
- if (
|
|
|
- this.youtubeVideoDuration.indexOf(
|
|
|
- ".000"
|
|
|
- ) !== -1
|
|
|
- )
|
|
|
- this.youtubeVideoNote = "(~)";
|
|
|
- else this.youtubeVideoNote = "";
|
|
|
- }
|
|
|
-
|
|
|
- if (this.song.duration === -1)
|
|
|
- this.song.duration =
|
|
|
- this.youtubeVideoDuration;
|
|
|
-
|
|
|
- youtubeDuration -= this.song.skipDuration;
|
|
|
- if (
|
|
|
- this.song.duration >
|
|
|
- youtubeDuration + 1
|
|
|
- ) {
|
|
|
- this.stopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- return new Toast(
|
|
|
- "Video can't play. Specified duration is bigger than the YouTube song duration."
|
|
|
- );
|
|
|
- }
|
|
|
- if (this.song.duration <= 0) {
|
|
|
- this.stopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- return new Toast(
|
|
|
- "Video can't play. Specified duration has to be more than 0 seconds."
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- if (
|
|
|
- this.video.player.getCurrentTime() <
|
|
|
- this.song.skipDuration
|
|
|
- ) {
|
|
|
- return this.seekTo(
|
|
|
- this.song.skipDuration
|
|
|
- );
|
|
|
- }
|
|
|
-
|
|
|
- this.setPlaybackRate(null);
|
|
|
- } else if (event.data === 2) {
|
|
|
- this.video.paused = true;
|
|
|
- }
|
|
|
-
|
|
|
- return false;
|
|
|
- }
|
|
|
- }
|
|
|
+ return false;
|
|
|
}
|
|
|
- );
|
|
|
- } else {
|
|
|
- this.youtubeError = true;
|
|
|
- this.youtubeErrorMessage = "Player could not be loaded.";
|
|
|
+ }
|
|
|
}
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ youtubeError.value = true;
|
|
|
+ youtubeErrorMessage.value = "Player could not be loaded.";
|
|
|
+ }
|
|
|
|
|
|
- ["artists", "genres", "tags"].forEach(type => {
|
|
|
- this.socket.dispatch(
|
|
|
- `songs.get${type.charAt(0).toUpperCase()}${type.slice(1)}`,
|
|
|
- res => {
|
|
|
- if (res.status === "success") {
|
|
|
- const { items } = res.data;
|
|
|
- if (type === "genres")
|
|
|
- this.autosuggest.allItems[type] = Array.from(
|
|
|
- new Set([
|
|
|
- ...this.recommendedGenres,
|
|
|
- ...items
|
|
|
- ])
|
|
|
- );
|
|
|
- else this.autosuggest.allItems[type] = items;
|
|
|
- } else {
|
|
|
- new Toast(res.message);
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
- });
|
|
|
+ ["artists", "genres", "tags"].forEach(type => {
|
|
|
+ socket.dispatch(
|
|
|
+ `songs.get${type.charAt(0).toUpperCase()}${type.slice(1)}`,
|
|
|
+ res => {
|
|
|
+ if (res.status === "success") {
|
|
|
+ const { items } = res.data;
|
|
|
+ if (type === "genres")
|
|
|
+ autosuggest.value.allItems[type] = Array.from(
|
|
|
+ new Set([...recommendedGenres.value, ...items])
|
|
|
+ );
|
|
|
+ else autosuggest.value.allItems[type] = items;
|
|
|
+ } else {
|
|
|
+ new Toast(res.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+ });
|
|
|
|
|
|
- return null;
|
|
|
- },
|
|
|
- unloadSong(youtubeId, songId) {
|
|
|
- this.songDataLoaded = false;
|
|
|
- this.songDeleted = false;
|
|
|
- this.stopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- this.resetSong(youtubeId);
|
|
|
- this.thumbnailNotSquare = false;
|
|
|
- this.thumbnailWidth = null;
|
|
|
- this.thumbnailHeight = null;
|
|
|
- this.youtubeVideoCurrentTime = "0.000";
|
|
|
- this.youtubeVideoDuration = "0.000";
|
|
|
- this.youtubeVideoNote = "";
|
|
|
- if (songId)
|
|
|
- this.socket.dispatch("apis.leaveRoom", `edit-song.${songId}`);
|
|
|
- if (this.$refs.saveButton) this.$refs.saveButton.status = "default";
|
|
|
- },
|
|
|
- loadSong(youtubeId) {
|
|
|
- console.log(`LOAD SONG ${youtubeId}`);
|
|
|
- this.songNotFound = false;
|
|
|
- this.socket.dispatch(
|
|
|
- `songs.getSongsFromYoutubeIds`,
|
|
|
- [youtubeId],
|
|
|
- res => {
|
|
|
- const { songs } = res.data;
|
|
|
- if (res.status === "success" && songs.length > 0) {
|
|
|
- let song = songs[0];
|
|
|
- song = Object.assign(song, this.prefillData);
|
|
|
-
|
|
|
- this.setSong(song);
|
|
|
-
|
|
|
- this.songDataLoaded = true;
|
|
|
-
|
|
|
- if (song._id)
|
|
|
- this.socket.dispatch(
|
|
|
- "apis.joinRoom",
|
|
|
- `edit-song.${song._id}`
|
|
|
- );
|
|
|
+ return null;
|
|
|
+};
|
|
|
|
|
|
- if (
|
|
|
- this.video.player &&
|
|
|
- this.video.player.cueVideoById
|
|
|
- ) {
|
|
|
- this.video.player.cueVideoById(
|
|
|
- youtubeId,
|
|
|
- song.skipDuration
|
|
|
- );
|
|
|
- }
|
|
|
- } else {
|
|
|
- new Toast("Song with that ID not found");
|
|
|
- if (this.bulk) this.songNotFound = true;
|
|
|
- if (!this.bulk) this.closeModal("editSong");
|
|
|
- }
|
|
|
- }
|
|
|
- );
|
|
|
+const save = (songToCopy, closeOrNext, saveButtonRefName, _newSong = false) => {
|
|
|
+ const _song = JSON.parse(JSON.stringify(songToCopy));
|
|
|
|
|
|
- if (!this.newSong)
|
|
|
- this.socket.dispatch(
|
|
|
- "reports.getReportsForSong",
|
|
|
- this.song._id,
|
|
|
- res => {
|
|
|
- this.updateReports(res.data.reports);
|
|
|
- }
|
|
|
- );
|
|
|
- },
|
|
|
- importAlbum(result) {
|
|
|
- this.selectDiscogsAlbum(result);
|
|
|
- this.openModal("importAlbum");
|
|
|
- this.closeModal("editSong");
|
|
|
- },
|
|
|
- save(songToCopy, closeOrNext, saveButtonRefName, newSong = false) {
|
|
|
- const song = JSON.parse(JSON.stringify(songToCopy));
|
|
|
+ if (!newSong.value || props.bulk) emit("saving", _song.youtubeId);
|
|
|
|
|
|
- if (!newSong || this.bulk) this.$emit("saving", song.youtubeId);
|
|
|
+ const saveButtonRef = saveButtonRefs.value[saveButtonRefName];
|
|
|
|
|
|
- const saveButtonRef = this.$refs[saveButtonRefName];
|
|
|
+ if (!youtubeError.value && youtubeVideoDuration.value === "0.000") {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast("The video appears to not be working.");
|
|
|
+ }
|
|
|
|
|
|
- if (!this.youtubeError && this.youtubeVideoDuration === "0.000") {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong) this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast("The video appears to not be working.");
|
|
|
- }
|
|
|
+ if (!_song.title) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast("Please fill in all fields");
|
|
|
+ }
|
|
|
|
|
|
- if (!song.title) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast("Please fill in all fields");
|
|
|
- }
|
|
|
+ if (!_song.thumbnail) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast("Please fill in all fields");
|
|
|
+ }
|
|
|
|
|
|
- if (!song.thumbnail) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast("Please fill in all fields");
|
|
|
- }
|
|
|
+ // const thumbnailHeight = thumbnailElement.value.naturalHeight;
|
|
|
+ // const thumbnailWidth = thumbnailElement.value.naturalWidth;
|
|
|
+
|
|
|
+ // if (thumbnailHeight < 80 || thumbnailWidth < 80) {
|
|
|
+ // saveButtonRef.handleFailedSave();
|
|
|
+ // return new Toast(
|
|
|
+ // "Thumbnail width and height must be at least 80px."
|
|
|
+ // );
|
|
|
+ // }
|
|
|
+
|
|
|
+ // if (thumbnailHeight > 4000 || thumbnailWidth > 4000) {
|
|
|
+ // saveButtonRef.handleFailedSave();
|
|
|
+ // return new Toast(
|
|
|
+ // "Thumbnail width and height must be less than 4000px."
|
|
|
+ // );
|
|
|
+ // }
|
|
|
+
|
|
|
+ // if (thumbnailHeight - thumbnailWidth > 5) {
|
|
|
+ // saveButtonRef.handleFailedSave();
|
|
|
+ // return new Toast("Thumbnail cannot be taller than it is wide.");
|
|
|
+ // }
|
|
|
+
|
|
|
+ // Youtube Id
|
|
|
+ if (
|
|
|
+ !_newSong &&
|
|
|
+ youtubeError.value &&
|
|
|
+ originalSong.value.youtubeId !== _song.youtubeId
|
|
|
+ ) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(
|
|
|
+ "You're not allowed to change the YouTube id while the player is not working"
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- // const thumbnailHeight = this.$refs.thumbnailElement.naturalHeight;
|
|
|
- // const thumbnailWidth = this.$refs.thumbnailElement.naturalWidth;
|
|
|
-
|
|
|
- // if (thumbnailHeight < 80 || thumbnailWidth < 80) {
|
|
|
- // saveButtonRef.handleFailedSave();
|
|
|
- // return new Toast(
|
|
|
- // "Thumbnail width and height must be at least 80px."
|
|
|
- // );
|
|
|
- // }
|
|
|
-
|
|
|
- // if (thumbnailHeight > 4000 || thumbnailWidth > 4000) {
|
|
|
- // saveButtonRef.handleFailedSave();
|
|
|
- // return new Toast(
|
|
|
- // "Thumbnail width and height must be less than 4000px."
|
|
|
- // );
|
|
|
- // }
|
|
|
-
|
|
|
- // if (thumbnailHeight - thumbnailWidth > 5) {
|
|
|
- // saveButtonRef.handleFailedSave();
|
|
|
- // return new Toast("Thumbnail cannot be taller than it is wide.");
|
|
|
- // }
|
|
|
-
|
|
|
- // Youtube Id
|
|
|
- if (
|
|
|
- !newSong &&
|
|
|
- this.youtubeError &&
|
|
|
- this.originalSong.youtubeId !== song.youtubeId
|
|
|
- ) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(
|
|
|
- "You're not allowed to change the YouTube id while the player is not working"
|
|
|
- );
|
|
|
- }
|
|
|
+ // Duration
|
|
|
+ if (
|
|
|
+ Number(_song.skipDuration) + Number(_song.duration) >
|
|
|
+ youtubeVideoDuration.value &&
|
|
|
+ (((!_newSong || props.bulk) && !youtubeError.value) ||
|
|
|
+ originalSong.value.duration !== _song.duration)
|
|
|
+ ) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(
|
|
|
+ "Duration can't be higher than the length of the video"
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- // Duration
|
|
|
- if (
|
|
|
- Number(song.skipDuration) + Number(song.duration) >
|
|
|
- this.youtubeVideoDuration &&
|
|
|
- (((!newSong || this.bulk) && !this.youtubeError) ||
|
|
|
- this.originalSong.duration !== song.duration)
|
|
|
- ) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(
|
|
|
- "Duration can't be higher than the length of the video"
|
|
|
- );
|
|
|
- }
|
|
|
+ // Title
|
|
|
+ if (!validation.isLength(_song.title, 1, 100)) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast("Title must have between 1 and 100 characters.");
|
|
|
+ }
|
|
|
|
|
|
- // Title
|
|
|
- if (!validation.isLength(song.title, 1, 100)) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(
|
|
|
- "Title must have between 1 and 100 characters."
|
|
|
- );
|
|
|
- }
|
|
|
+ // Artists
|
|
|
+ if (
|
|
|
+ (_song.verified && _song.artists.length < 1) ||
|
|
|
+ _song.artists.length > 10
|
|
|
+ ) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(
|
|
|
+ "Invalid artists. You must have at least 1 artist and a maximum of 10 artists."
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- // Artists
|
|
|
- if (
|
|
|
- (song.verified && song.artists.length < 1) ||
|
|
|
- song.artists.length > 10
|
|
|
- ) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(
|
|
|
- "Invalid artists. You must have at least 1 artist and a maximum of 10 artists."
|
|
|
- );
|
|
|
- }
|
|
|
+ let error;
|
|
|
+ _song.artists.forEach(artist => {
|
|
|
+ if (!validation.isLength(artist, 1, 64)) {
|
|
|
+ error = "Artist must have between 1 and 64 characters.";
|
|
|
+ return error;
|
|
|
+ }
|
|
|
+ if (artist === "NONE") {
|
|
|
+ error =
|
|
|
+ 'Invalid artist format. Artists are not allowed to be named "NONE".';
|
|
|
+ return error;
|
|
|
+ }
|
|
|
|
|
|
- let error;
|
|
|
- song.artists.forEach(artist => {
|
|
|
- if (!validation.isLength(artist, 1, 64)) {
|
|
|
- error = "Artist must have between 1 and 64 characters.";
|
|
|
- return error;
|
|
|
- }
|
|
|
- if (artist === "NONE") {
|
|
|
- error =
|
|
|
- 'Invalid artist format. Artists are not allowed to be named "NONE".';
|
|
|
- return error;
|
|
|
- }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
|
|
|
- return false;
|
|
|
- });
|
|
|
+ if (error) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(error);
|
|
|
+ }
|
|
|
|
|
|
- if (error) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(error);
|
|
|
+ // Genres
|
|
|
+ error = undefined;
|
|
|
+ if (_song.verified && _song.genres.length < 1)
|
|
|
+ _song.genres.forEach(genre => {
|
|
|
+ if (!validation.isLength(genre, 1, 32)) {
|
|
|
+ error = "Genre must have between 1 and 32 characters.";
|
|
|
+ return error;
|
|
|
+ }
|
|
|
+ if (!validation.regex.ascii.test(genre)) {
|
|
|
+ error =
|
|
|
+ "Invalid genre format. Only ascii characters are allowed.";
|
|
|
+ return error;
|
|
|
}
|
|
|
|
|
|
- // Genres
|
|
|
- error = undefined;
|
|
|
- if (song.verified && song.genres.length < 1)
|
|
|
- song.genres.forEach(genre => {
|
|
|
- if (!validation.isLength(genre, 1, 32)) {
|
|
|
- error = "Genre must have between 1 and 32 characters.";
|
|
|
- return error;
|
|
|
- }
|
|
|
- if (!validation.regex.ascii.test(genre)) {
|
|
|
- error =
|
|
|
- "Invalid genre format. Only ascii characters are allowed.";
|
|
|
- return error;
|
|
|
- }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
|
|
|
- return false;
|
|
|
- });
|
|
|
+ if ((_song.verified && _song.genres.length < 1) || _song.genres.length > 16)
|
|
|
+ error = "You must have between 1 and 16 genres.";
|
|
|
|
|
|
- if (
|
|
|
- (song.verified && song.genres.length < 1) ||
|
|
|
- song.genres.length > 16
|
|
|
+ if (error) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(error);
|
|
|
+ }
|
|
|
+
|
|
|
+ error = undefined;
|
|
|
+ _song.tags.forEach(tag => {
|
|
|
+ if (
|
|
|
+ !/^[a-zA-Z0-9_]{1,64}$|^[a-zA-Z0-9_]{1,64}\[[a-zA-Z0-9_]{1,64}\]$/.test(
|
|
|
+ tag
|
|
|
)
|
|
|
- error = "You must have between 1 and 16 genres.";
|
|
|
+ ) {
|
|
|
+ error = "Invalid tag format.";
|
|
|
+ return error;
|
|
|
+ }
|
|
|
|
|
|
- if (error) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(error);
|
|
|
- }
|
|
|
+ return false;
|
|
|
+ });
|
|
|
|
|
|
- error = undefined;
|
|
|
- song.tags.forEach(tag => {
|
|
|
- if (
|
|
|
- !/^[a-zA-Z0-9_]{1,64}$|^[a-zA-Z0-9_]{1,64}\[[a-zA-Z0-9_]{1,64}\]$/.test(
|
|
|
- tag
|
|
|
- )
|
|
|
- ) {
|
|
|
- error = "Invalid tag format.";
|
|
|
- return error;
|
|
|
- }
|
|
|
+ if (error) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast(error);
|
|
|
+ }
|
|
|
|
|
|
- return false;
|
|
|
- });
|
|
|
+ // Thumbnail
|
|
|
+ if (!validation.isLength(_song.thumbnail, 1, 256)) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast("Thumbnail must have between 8 and 256 characters.");
|
|
|
+ }
|
|
|
+ if (useHTTPS.value && _song.thumbnail.indexOf("https://") !== 0) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast('Thumbnail must start with "https://".');
|
|
|
+ }
|
|
|
|
|
|
- if (error) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(error);
|
|
|
- }
|
|
|
+ if (
|
|
|
+ !useHTTPS.value &&
|
|
|
+ _song.thumbnail.indexOf("http://") !== 0 &&
|
|
|
+ _song.thumbnail.indexOf("https://") !== 0
|
|
|
+ ) {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ if (!_newSong || props.bulk) emit("savedError", _song.youtubeId);
|
|
|
+ return new Toast('Thumbnail must start with "http://".');
|
|
|
+ }
|
|
|
|
|
|
- // Thumbnail
|
|
|
- if (!validation.isLength(song.thumbnail, 1, 256)) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast(
|
|
|
- "Thumbnail must have between 8 and 256 characters."
|
|
|
- );
|
|
|
- }
|
|
|
- if (this.useHTTPS && song.thumbnail.indexOf("https://") !== 0) {
|
|
|
+ saveButtonRef.status = "saving";
|
|
|
+
|
|
|
+ if (_newSong)
|
|
|
+ return socket.dispatch(`songs.create`, _song, res => {
|
|
|
+ new Toast(res.message);
|
|
|
+
|
|
|
+ if (res.status === "error") {
|
|
|
saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast('Thumbnail must start with "https://".');
|
|
|
+ emit("savedError", _song.youtubeId);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- if (
|
|
|
- !this.useHTTPS &&
|
|
|
- song.thumbnail.indexOf("http://") !== 0 &&
|
|
|
- song.thumbnail.indexOf("https://") !== 0
|
|
|
- ) {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- if (!newSong || this.bulk)
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return new Toast('Thumbnail must start with "http://".');
|
|
|
+ saveButtonRef.handleSuccessfulSave();
|
|
|
+ emit("savedSuccess", _song.youtubeId);
|
|
|
+
|
|
|
+ if (!closeOrNext) {
|
|
|
+ loadSong(_song.youtubeId);
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- saveButtonRef.status = "saving";
|
|
|
+ if (props.bulk) emit("nextSong");
|
|
|
+ else closeCurrentModal();
|
|
|
+ });
|
|
|
+ return socket.dispatch(`songs.update`, _song._id, _song, res => {
|
|
|
+ new Toast(res.message);
|
|
|
|
|
|
- if (newSong)
|
|
|
- return this.socket.dispatch(`songs.create`, song, res => {
|
|
|
- new Toast(res.message);
|
|
|
+ if (res.status === "error") {
|
|
|
+ saveButtonRef.handleFailedSave();
|
|
|
+ emit("savedError", _song.youtubeId);
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if (res.status === "error") {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return;
|
|
|
- }
|
|
|
+ updateOriginalSong(_song);
|
|
|
|
|
|
- saveButtonRef.handleSuccessfulSave();
|
|
|
- this.$emit("savedSuccess", song.youtubeId);
|
|
|
+ saveButtonRef.handleSuccessfulSave();
|
|
|
+ emit("savedSuccess", _song.youtubeId);
|
|
|
|
|
|
- if (!closeOrNext) {
|
|
|
- this.loadSong(song.youtubeId);
|
|
|
- return;
|
|
|
- }
|
|
|
+ if (!closeOrNext) return;
|
|
|
|
|
|
- if (this.bulk) this.$emit("nextSong");
|
|
|
- else this.closeModal("editSong");
|
|
|
- });
|
|
|
- return this.socket.dispatch(`songs.update`, song._id, song, res => {
|
|
|
- new Toast(res.message);
|
|
|
+ if (props.bulk) emit("nextSong");
|
|
|
+ else closeCurrentModal();
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- if (res.status === "error") {
|
|
|
- saveButtonRef.handleFailedSave();
|
|
|
- this.$emit("savedError", song.youtubeId);
|
|
|
- return;
|
|
|
- }
|
|
|
+const editNextSong = () => {
|
|
|
+ emit("nextSong");
|
|
|
+};
|
|
|
|
|
|
- this.updateOriginalSong(song);
|
|
|
+const toggleFlag = () => {
|
|
|
+ emit("toggleFlag");
|
|
|
+};
|
|
|
|
|
|
- saveButtonRef.handleSuccessfulSave();
|
|
|
- this.$emit("savedSuccess", song.youtubeId);
|
|
|
+const getAlbumData = type => {
|
|
|
+ if (!song.value.discogs) return;
|
|
|
+ if (type === "title")
|
|
|
+ updateSongField({
|
|
|
+ field: "title",
|
|
|
+ value: song.value.discogs.track.title
|
|
|
+ });
|
|
|
+ if (type === "albumArt")
|
|
|
+ updateSongField({
|
|
|
+ field: "thumbnail",
|
|
|
+ value: song.value.discogs.album.albumArt
|
|
|
+ });
|
|
|
+ if (type === "genres")
|
|
|
+ updateSongField({
|
|
|
+ field: "genres",
|
|
|
+ value: JSON.parse(JSON.stringify(song.value.discogs.album.genres))
|
|
|
+ });
|
|
|
+ if (type === "artists")
|
|
|
+ updateSongField({
|
|
|
+ field: "artists",
|
|
|
+ value: JSON.parse(JSON.stringify(song.value.discogs.album.artists))
|
|
|
+ });
|
|
|
+};
|
|
|
|
|
|
- if (!closeOrNext) return;
|
|
|
+const getYouTubeData = type => {
|
|
|
+ if (type === "title") {
|
|
|
+ try {
|
|
|
+ const { title } = video.value.player.getVideoData();
|
|
|
|
|
|
- if (this.bulk) this.$emit("nextSong");
|
|
|
- else this.closeModal("editSong");
|
|
|
- });
|
|
|
- },
|
|
|
- editNextSong() {
|
|
|
- this.$emit("nextSong");
|
|
|
- },
|
|
|
- toggleFlag() {
|
|
|
- this.$emit("toggleFlag");
|
|
|
- },
|
|
|
- getAlbumData(type) {
|
|
|
- if (!this.song.discogs) return;
|
|
|
- if (type === "title")
|
|
|
- this.updateSongField({
|
|
|
+ if (title)
|
|
|
+ updateSongField({
|
|
|
field: "title",
|
|
|
- value: this.song.discogs.track.title
|
|
|
- });
|
|
|
- if (type === "albumArt")
|
|
|
- this.updateSongField({
|
|
|
- field: "thumbnail",
|
|
|
- value: this.song.discogs.album.albumArt
|
|
|
- });
|
|
|
- if (type === "genres")
|
|
|
- this.updateSongField({
|
|
|
- field: "genres",
|
|
|
- value: JSON.parse(
|
|
|
- JSON.stringify(this.song.discogs.album.genres)
|
|
|
- )
|
|
|
- });
|
|
|
- if (type === "artists")
|
|
|
- this.updateSongField({
|
|
|
- field: "artists",
|
|
|
- value: JSON.parse(
|
|
|
- JSON.stringify(this.song.discogs.album.artists)
|
|
|
- )
|
|
|
- });
|
|
|
- },
|
|
|
- getYouTubeData(type) {
|
|
|
- if (type === "title") {
|
|
|
- try {
|
|
|
- const { title } = this.video.player.getVideoData();
|
|
|
-
|
|
|
- if (title)
|
|
|
- this.updateSongField({
|
|
|
- field: "title",
|
|
|
- value: title
|
|
|
- });
|
|
|
- else throw new Error("No title found");
|
|
|
- } catch (e) {
|
|
|
- new Toast(
|
|
|
- "Unable to fetch YouTube video title. Try starting the video."
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- if (type === "thumbnail")
|
|
|
- this.updateSongField({
|
|
|
- field: "thumbnail",
|
|
|
- value: `https://img.youtube.com/vi/${this.song.youtubeId}/mqdefault.jpg`
|
|
|
+ value: title
|
|
|
});
|
|
|
- if (type === "author") {
|
|
|
- try {
|
|
|
- const { author } = this.video.player.getVideoData();
|
|
|
-
|
|
|
- if (author) this.artistInputValue = author;
|
|
|
- else throw new Error("No video author found");
|
|
|
- } catch (e) {
|
|
|
- new Toast(
|
|
|
- "Unable to fetch YouTube video author. Try starting the video."
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
- fillDuration() {
|
|
|
- this.song.duration =
|
|
|
- this.youtubeVideoDuration - this.song.skipDuration;
|
|
|
- },
|
|
|
- settings(type) {
|
|
|
- switch (type) {
|
|
|
- case "stop":
|
|
|
- this.stopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- break;
|
|
|
- case "hardStop":
|
|
|
- this.hardStopVideo();
|
|
|
- this.pauseVideo(true);
|
|
|
- break;
|
|
|
- case "pause":
|
|
|
- this.pauseVideo(true);
|
|
|
- break;
|
|
|
- case "play":
|
|
|
- this.pauseVideo(false);
|
|
|
- break;
|
|
|
- case "skipToLast10Secs":
|
|
|
- this.seekTo(
|
|
|
- this.song.duration - 10 + this.song.skipDuration
|
|
|
- );
|
|
|
- break;
|
|
|
- default:
|
|
|
- break;
|
|
|
- }
|
|
|
- },
|
|
|
- play() {
|
|
|
- if (
|
|
|
- this.video.player.getVideoData().video_id !==
|
|
|
- this.song.youtubeId
|
|
|
- ) {
|
|
|
- this.song.duration = -1;
|
|
|
- this.loadVideoById(this.song.youtubeId, this.song.skipDuration);
|
|
|
- }
|
|
|
- this.settings("play");
|
|
|
- },
|
|
|
- seekTo(position) {
|
|
|
- this.settings("play");
|
|
|
- this.video.player.seekTo(position);
|
|
|
- },
|
|
|
- changeVolume() {
|
|
|
- const volume = this.volumeSliderValue;
|
|
|
- localStorage.setItem("volume", volume);
|
|
|
- this.video.player.setVolume(volume);
|
|
|
- if (volume > 0) {
|
|
|
- this.video.player.unMute();
|
|
|
- this.muted = false;
|
|
|
- }
|
|
|
- },
|
|
|
- toggleMute() {
|
|
|
- const previousVolume = parseFloat(localStorage.getItem("volume"));
|
|
|
- const volume =
|
|
|
- this.video.player.getVolume() <= 0 ? previousVolume : 0;
|
|
|
- this.muted = !this.muted;
|
|
|
- this.volumeSliderValue = volume;
|
|
|
- this.video.player.setVolume(volume);
|
|
|
- if (!this.muted) localStorage.setItem("volume", volume);
|
|
|
- },
|
|
|
- increaseVolume() {
|
|
|
- const previousVolume = parseFloat(localStorage.getItem("volume"));
|
|
|
- let volume = previousVolume + 5;
|
|
|
- this.muted = false;
|
|
|
- if (volume > 100) volume = 100;
|
|
|
- this.volumeSliderValue = volume;
|
|
|
- this.video.player.setVolume(volume);
|
|
|
- localStorage.setItem("volume", volume);
|
|
|
- },
|
|
|
- addTag(type, value) {
|
|
|
- if (type === "genres") {
|
|
|
- const genre = value || this.genreInputValue.trim();
|
|
|
-
|
|
|
- if (
|
|
|
- this.song.genres
|
|
|
- .map(genre => genre.toLowerCase())
|
|
|
- .indexOf(genre.toLowerCase()) !== -1
|
|
|
- )
|
|
|
- return new Toast("Genre already exists");
|
|
|
- if (genre) {
|
|
|
- this.song.genres.push(genre);
|
|
|
- this.genreInputValue = "";
|
|
|
- return false;
|
|
|
- }
|
|
|
+ else throw new Error("No title found");
|
|
|
+ } catch (e) {
|
|
|
+ new Toast(
|
|
|
+ "Unable to fetch YouTube video title. Try starting the video."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (type === "thumbnail")
|
|
|
+ updateSongField({
|
|
|
+ field: "thumbnail",
|
|
|
+ value: `https://img.youtube.com/vi/${song.value.youtubeId}/mqdefault.jpg`
|
|
|
+ });
|
|
|
+ if (type === "author") {
|
|
|
+ try {
|
|
|
+ const { author } = video.value.player.getVideoData();
|
|
|
+
|
|
|
+ if (author) artistInputValue.value = author;
|
|
|
+ else throw new Error("No video author found");
|
|
|
+ } catch (e) {
|
|
|
+ new Toast(
|
|
|
+ "Unable to fetch YouTube video author. Try starting the video."
|
|
|
+ );
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
- return new Toast("Genre cannot be empty");
|
|
|
- }
|
|
|
- if (type === "artists") {
|
|
|
- const artist = value || this.artistInputValue;
|
|
|
- if (this.song.artists.indexOf(artist) !== -1)
|
|
|
- return new Toast("Artist already exists");
|
|
|
- if (artist !== "") {
|
|
|
- this.song.artists.push(artist);
|
|
|
- this.artistInputValue = "";
|
|
|
- return false;
|
|
|
- }
|
|
|
- return new Toast("Artist cannot be empty");
|
|
|
- }
|
|
|
- if (type === "tags") {
|
|
|
- const tag = value || this.tagInputValue;
|
|
|
- if (this.song.tags.indexOf(tag) !== -1)
|
|
|
- return new Toast("Tag already exists");
|
|
|
- if (tag !== "") {
|
|
|
- this.song.tags.push(tag);
|
|
|
- this.tagInputValue = "";
|
|
|
- return false;
|
|
|
- }
|
|
|
- return new Toast("Tag cannot be empty");
|
|
|
- }
|
|
|
+const fillDuration = () => {
|
|
|
+ song.value.duration = youtubeVideoDuration.value - song.value.skipDuration;
|
|
|
+};
|
|
|
+
|
|
|
+const settings = type => {
|
|
|
+ switch (type) {
|
|
|
+ case "stop":
|
|
|
+ stopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ break;
|
|
|
+ case "hardStop":
|
|
|
+ hardStopVideo();
|
|
|
+ pauseVideo(true);
|
|
|
+ break;
|
|
|
+ case "pause":
|
|
|
+ pauseVideo(true);
|
|
|
+ break;
|
|
|
+ case "play":
|
|
|
+ pauseVideo(false);
|
|
|
+ break;
|
|
|
+ case "skipToLast10Secs":
|
|
|
+ seekTo(song.value.duration - 10 + song.value.skipDuration);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const play = () => {
|
|
|
+ if (video.value.player.getVideoData().video_id !== song.value.youtubeId) {
|
|
|
+ song.value.duration = -1;
|
|
|
+ loadVideoById(song.value.youtubeId, song.value.skipDuration);
|
|
|
+ }
|
|
|
+ settings("play");
|
|
|
+};
|
|
|
+
|
|
|
+const changeVolume = () => {
|
|
|
+ const volume = volumeSliderValue.value;
|
|
|
+ localStorage.setItem("volume", volume);
|
|
|
+ video.value.player.setVolume(volume);
|
|
|
+ if (volume > 0) {
|
|
|
+ video.value.player.unMute();
|
|
|
+ muted.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
|
|
|
+const toggleMute = () => {
|
|
|
+ const previousVolume = parseFloat(localStorage.getItem("volume"));
|
|
|
+ const volume = video.value.player.getVolume() <= 0 ? previousVolume : 0;
|
|
|
+ muted.value = !muted.value;
|
|
|
+ volumeSliderValue.value = volume;
|
|
|
+ video.value.player.setVolume(volume);
|
|
|
+ if (!muted.value) localStorage.setItem("volume", volume);
|
|
|
+};
|
|
|
+
|
|
|
+const addTag = (type, value) => {
|
|
|
+ if (type === "genres") {
|
|
|
+ const genre = value || genreInputValue.value.trim();
|
|
|
+
|
|
|
+ if (
|
|
|
+ song.value.genres
|
|
|
+ .map(genre => genre.toLowerCase())
|
|
|
+ .indexOf(genre.toLowerCase()) !== -1
|
|
|
+ )
|
|
|
+ return new Toast("Genre already exists");
|
|
|
+ if (genre) {
|
|
|
+ song.value.genres.push(genre);
|
|
|
+ genreInputValue.value = "";
|
|
|
return false;
|
|
|
- },
|
|
|
- removeTag(type, value) {
|
|
|
- if (type === "genres")
|
|
|
- this.song.genres.splice(this.song.genres.indexOf(value), 1);
|
|
|
- else if (type === "artists")
|
|
|
- this.song.artists.splice(this.song.artists.indexOf(value), 1);
|
|
|
- else if (type === "tags")
|
|
|
- this.song.tags.splice(this.song.tags.indexOf(value), 1);
|
|
|
- },
|
|
|
- drawCanvas() {
|
|
|
- const canvasElement =
|
|
|
- this.$refs[`durationCanvas-${this.modalUuid}`];
|
|
|
- if (!this.songDataLoaded || !canvasElement) return;
|
|
|
- const ctx = canvasElement.getContext("2d");
|
|
|
-
|
|
|
- const videoDuration = Number(this.youtubeVideoDuration);
|
|
|
-
|
|
|
- const skipDuration = Number(this.song.skipDuration);
|
|
|
- const duration = Number(this.song.duration);
|
|
|
- const afterDuration = videoDuration - (skipDuration + duration);
|
|
|
-
|
|
|
- const width = 530;
|
|
|
-
|
|
|
- const currentTime =
|
|
|
- this.video.player && this.video.player.getCurrentTime
|
|
|
- ? this.video.player.getCurrentTime()
|
|
|
- : 0;
|
|
|
-
|
|
|
- const widthSkipDuration = (skipDuration / videoDuration) * width;
|
|
|
- const widthDuration = (duration / videoDuration) * width;
|
|
|
- const widthAfterDuration = (afterDuration / videoDuration) * width;
|
|
|
-
|
|
|
- const widthCurrentTime = (currentTime / videoDuration) * width;
|
|
|
-
|
|
|
- const skipDurationColor = "#F42003";
|
|
|
- const durationColor = "#03A9F4";
|
|
|
- const afterDurationColor = "#41E841";
|
|
|
- const currentDurationColor = "#3b25e8";
|
|
|
-
|
|
|
- ctx.fillStyle = skipDurationColor;
|
|
|
- ctx.fillRect(0, 0, widthSkipDuration, 20);
|
|
|
- ctx.fillStyle = durationColor;
|
|
|
- ctx.fillRect(widthSkipDuration, 0, widthDuration, 20);
|
|
|
- ctx.fillStyle = afterDurationColor;
|
|
|
- ctx.fillRect(
|
|
|
- widthSkipDuration + widthDuration,
|
|
|
- 0,
|
|
|
- widthAfterDuration,
|
|
|
- 20
|
|
|
- );
|
|
|
+ }
|
|
|
|
|
|
- ctx.fillStyle = currentDurationColor;
|
|
|
- ctx.fillRect(widthCurrentTime, 0, 1, 20);
|
|
|
- },
|
|
|
- setTrackPosition(event) {
|
|
|
- this.seekTo(
|
|
|
- Number(
|
|
|
- Number(this.video.player.getDuration()) *
|
|
|
- ((event.pageX -
|
|
|
- event.target.getBoundingClientRect().left) /
|
|
|
- 530)
|
|
|
- )
|
|
|
- );
|
|
|
- },
|
|
|
- toggleGenreHelper() {
|
|
|
- this.$refs.genreHelper.toggleBox();
|
|
|
- },
|
|
|
- resetGenreHelper() {
|
|
|
- this.$refs.genreHelper.resetBox();
|
|
|
- },
|
|
|
- sendActivityWatchVideoData() {
|
|
|
- if (!this.video.paused) {
|
|
|
- if (this.activityWatchVideoLastStatus !== "playing") {
|
|
|
- this.activityWatchVideoLastStatus = "playing";
|
|
|
- if (
|
|
|
- this.song.skipDuration > 0 &&
|
|
|
- parseFloat(this.youtubeVideoCurrentTime) === 0
|
|
|
- ) {
|
|
|
- this.activityWatchVideoLastStartDuration = Math.floor(
|
|
|
- this.song.skipDuration +
|
|
|
- parseFloat(this.youtubeVideoCurrentTime)
|
|
|
- );
|
|
|
- } else {
|
|
|
- this.activityWatchVideoLastStartDuration = Math.floor(
|
|
|
- parseFloat(this.youtubeVideoCurrentTime)
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
+ return new Toast("Genre cannot be empty");
|
|
|
+ }
|
|
|
+ if (type === "artists") {
|
|
|
+ const artist = value || artistInputValue.value;
|
|
|
+ if (song.value.artists.indexOf(artist) !== -1)
|
|
|
+ return new Toast("Artist already exists");
|
|
|
+ if (artist !== "") {
|
|
|
+ song.value.artists.push(artist);
|
|
|
+ artistInputValue.value = "";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return new Toast("Artist cannot be empty");
|
|
|
+ }
|
|
|
+ if (type === "tags") {
|
|
|
+ const tag = value || tagInputValue.value;
|
|
|
+ if (song.value.tags.indexOf(tag) !== -1)
|
|
|
+ return new Toast("Tag already exists");
|
|
|
+ if (tag !== "") {
|
|
|
+ song.value.tags.push(tag);
|
|
|
+ tagInputValue.value = "";
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return new Toast("Tag cannot be empty");
|
|
|
+ }
|
|
|
|
|
|
- const videoData = {
|
|
|
- title: this.song.title,
|
|
|
- artists: this.song.artists
|
|
|
- ? this.song.artists.join(", ")
|
|
|
- : null,
|
|
|
- youtubeId: this.song.youtubeId,
|
|
|
- muted: this.muted,
|
|
|
- volume: this.volumeSliderValue,
|
|
|
- startedDuration:
|
|
|
- this.activityWatchVideoLastStartDuration <= 0
|
|
|
- ? 0
|
|
|
- : this.activityWatchVideoLastStartDuration,
|
|
|
- source: `editSong#${this.song.youtubeId}`,
|
|
|
- hostname: window.location.hostname
|
|
|
- };
|
|
|
-
|
|
|
- aw.sendVideoData(videoData);
|
|
|
- } else {
|
|
|
- this.activityWatchVideoLastStatus = "not_playing";
|
|
|
- }
|
|
|
- },
|
|
|
- remove(id) {
|
|
|
- this.socket.dispatch("songs.remove", id, res => {
|
|
|
- new Toast(res.message);
|
|
|
- });
|
|
|
- },
|
|
|
- confirmAction({ message, action, params }) {
|
|
|
- this.openModal({
|
|
|
- modal: "confirm",
|
|
|
- data: {
|
|
|
- message,
|
|
|
- action,
|
|
|
- params,
|
|
|
- onCompleted: this.handleConfirmed
|
|
|
- }
|
|
|
- });
|
|
|
- },
|
|
|
- handleConfirmed({ action, params }) {
|
|
|
- if (typeof this[action] === "function") {
|
|
|
- if (params) this[action](params);
|
|
|
- else this[action]();
|
|
|
- }
|
|
|
- },
|
|
|
- onCloseModal() {
|
|
|
- const songStringified = JSON.stringify({
|
|
|
- ...this.song,
|
|
|
- ...{
|
|
|
- duration: Number(this.song.duration).toFixed(3)
|
|
|
- }
|
|
|
- });
|
|
|
- const originalSongStringified = JSON.stringify({
|
|
|
- ...this.originalSong,
|
|
|
- ...{
|
|
|
- duration: Number(this.originalSong.duration).toFixed(3)
|
|
|
- }
|
|
|
- });
|
|
|
- const unsavedChanges = songStringified !== originalSongStringified;
|
|
|
-
|
|
|
- if (unsavedChanges) {
|
|
|
- return this.confirmAction({
|
|
|
- message:
|
|
|
- "You have unsaved changes. Are you sure you want to discard unsaved changes?",
|
|
|
- action: "closeThisModal",
|
|
|
- params: null
|
|
|
- });
|
|
|
- }
|
|
|
+ return false;
|
|
|
+};
|
|
|
|
|
|
- return this.closeThisModal();
|
|
|
- },
|
|
|
- closeThisModal() {
|
|
|
- if (this.bulk) this.$emit("close");
|
|
|
- else this.closeModal("editSong");
|
|
|
- },
|
|
|
- ...mapActions("modals/importAlbum", ["selectDiscogsAlbum"]),
|
|
|
- ...mapActions({
|
|
|
- showTab(dispatch, payload) {
|
|
|
- if (this.$refs[`${payload}-tab`])
|
|
|
- this.$refs[`${payload}-tab`].scrollIntoView({
|
|
|
- block: "nearest"
|
|
|
- });
|
|
|
- return dispatch(
|
|
|
- `${this.modalModulePath.replace(
|
|
|
- "MODAL_UUID",
|
|
|
- this.modalUuid
|
|
|
- )}/showTab`,
|
|
|
- payload
|
|
|
+const removeTag = (type, value) => {
|
|
|
+ if (type === "genres")
|
|
|
+ song.value.genres.splice(song.value.genres.indexOf(value), 1);
|
|
|
+ else if (type === "artists")
|
|
|
+ song.value.artists.splice(song.value.artists.indexOf(value), 1);
|
|
|
+ else if (type === "tags")
|
|
|
+ song.value.tags.splice(song.value.tags.indexOf(value), 1);
|
|
|
+};
|
|
|
+
|
|
|
+const setTrackPosition = event => {
|
|
|
+ seekTo(
|
|
|
+ Number(
|
|
|
+ Number(video.value.player.getDuration()) *
|
|
|
+ ((event.pageX - event.target.getBoundingClientRect().left) /
|
|
|
+ 530)
|
|
|
+ )
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+const toggleGenreHelper = () => {
|
|
|
+ genreHelper.value.toggleBox();
|
|
|
+};
|
|
|
+
|
|
|
+const resetGenreHelper = () => {
|
|
|
+ genreHelper.value.resetBox();
|
|
|
+};
|
|
|
+
|
|
|
+const sendActivityWatchVideoData = () => {
|
|
|
+ if (!video.value.paused) {
|
|
|
+ if (activityWatchVideoLastStatus.value !== "playing") {
|
|
|
+ activityWatchVideoLastStatus.value = "playing";
|
|
|
+ if (
|
|
|
+ song.value.skipDuration > 0 &&
|
|
|
+ parseFloat(youtubeVideoCurrentTime.value) === 0
|
|
|
+ ) {
|
|
|
+ activityWatchVideoLastStartDuration.value = Math.floor(
|
|
|
+ song.value.skipDuration +
|
|
|
+ parseFloat(youtubeVideoCurrentTime.value)
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ activityWatchVideoLastStartDuration.value = Math.floor(
|
|
|
+ parseFloat(youtubeVideoCurrentTime.value)
|
|
|
);
|
|
|
}
|
|
|
- }),
|
|
|
- ...mapModalActions("MODAL_MODULE_PATH", [
|
|
|
- "stopVideo",
|
|
|
- "hardStopVideo",
|
|
|
- "loadVideoById",
|
|
|
- "pauseVideo",
|
|
|
- "getCurrentTime",
|
|
|
- "setSong",
|
|
|
- "resetSong",
|
|
|
- "updateOriginalSong",
|
|
|
- "updateSongField",
|
|
|
- "updateReports",
|
|
|
- "setPlaybackRate"
|
|
|
- ]),
|
|
|
- ...mapActions("modalVisibility", ["closeModal", "openModal"])
|
|
|
+ }
|
|
|
+
|
|
|
+ const videoData = {
|
|
|
+ title: song.value.title,
|
|
|
+ artists: song.value.artists ? song.value.artists.join(", ") : null,
|
|
|
+ youtubeId: song.value.youtubeId,
|
|
|
+ muted: muted.value,
|
|
|
+ volume: volumeSliderValue.value,
|
|
|
+ startedDuration:
|
|
|
+ activityWatchVideoLastStartDuration.value <= 0
|
|
|
+ ? 0
|
|
|
+ : activityWatchVideoLastStartDuration.value,
|
|
|
+ source: `editSong#${song.value.youtubeId}`,
|
|
|
+ hostname: window.location.hostname
|
|
|
+ };
|
|
|
+
|
|
|
+ aw.sendVideoData(videoData);
|
|
|
+ } else {
|
|
|
+ activityWatchVideoLastStatus.value = "not_playing";
|
|
|
}
|
|
|
};
|
|
|
-</script>
|
|
|
+
|
|
|
+const remove = id => {
|
|
|
+ socket.dispatch("songs.remove", id, res => {
|
|
|
+ new Toast(res.message);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const handleConfirmed = ({ action, params }) => {
|
|
|
+ if (typeof action === "function") {
|
|
|
+ if (params) action(params);
|
|
|
+ else action();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+const confirmAction = ({ message, action, params }) => {
|
|
|
+ openModal({
|
|
|
+ modal: "confirm",
|
|
|
+ data: {
|
|
|
+ message,
|
|
|
+ action,
|
|
|
+ params,
|
|
|
+ onCompleted: handleConfirmed
|
|
|
+ }
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+const onCloseModal = () => {
|
|
|
+ const songStringified = JSON.stringify({
|
|
|
+ ...song.value,
|
|
|
+ ...{
|
|
|
+ duration: Number(song.value.duration).toFixed(3)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const originalSongStringified = JSON.stringify({
|
|
|
+ ...originalSong.value,
|
|
|
+ ...{
|
|
|
+ duration: Number(originalSong.value.duration).toFixed(3)
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const unsavedChanges = songStringified !== originalSongStringified;
|
|
|
+
|
|
|
+ if (unsavedChanges) {
|
|
|
+ return confirmAction({
|
|
|
+ message:
|
|
|
+ "You have unsaved changes. Are you sure you want to discard unsaved changes?",
|
|
|
+ action: closeCurrentModal,
|
|
|
+ params: null
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ return closeCurrentModal();
|
|
|
+};
|
|
|
+
|
|
|
+watch(
|
|
|
+ () => song.value.duration,
|
|
|
+ () => drawCanvas()
|
|
|
+);
|
|
|
+watch(
|
|
|
+ () => song.value.skipDuration,
|
|
|
+ () => drawCanvas()
|
|
|
+);
|
|
|
+watch(
|
|
|
+ () => youtubeId.value,
|
|
|
+ (_youtubeId, _oldYoutubeId) => {
|
|
|
+ console.log("NEW YOUTUBE ID", _youtubeId);
|
|
|
+ unloadSong(_oldYoutubeId);
|
|
|
+ loadSong(_youtubeId);
|
|
|
+ }
|
|
|
+);
|
|
|
+
|
|
|
+onMounted(async () => {
|
|
|
+ activityWatchVideoDataInterval.value = setInterval(() => {
|
|
|
+ sendActivityWatchVideoData();
|
|
|
+ }, 1000);
|
|
|
+
|
|
|
+ useHTTPS.value = await lofig.get("cookie.secure");
|
|
|
+
|
|
|
+ ws.onConnect(init);
|
|
|
+
|
|
|
+ let volume = parseFloat(localStorage.getItem("volume"));
|
|
|
+ volume = typeof volume === "number" && !Number.isNaN(volume) ? volume : 20;
|
|
|
+ localStorage.setItem("volume", volume);
|
|
|
+ volumeSliderValue.value = volume;
|
|
|
+
|
|
|
+ socket.on(
|
|
|
+ "event:admin.song.removed",
|
|
|
+ res => {
|
|
|
+ if (res.data.songId === song.value._id) {
|
|
|
+ songDeleted.value = true;
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { modalUuid: props.modalUuid }
|
|
|
+ );
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.pauseResumeVideo", {
|
|
|
+ keyCode: 101,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ if (video.value.paused) play();
|
|
|
+ else settings("pause");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.stopVideo", {
|
|
|
+ keyCode: 101,
|
|
|
+ ctrl: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ settings("stop");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.hardStopVideo", {
|
|
|
+ keyCode: 101,
|
|
|
+ ctrl: true,
|
|
|
+ shift: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ settings("hardStop");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.skipToLast10Secs", {
|
|
|
+ keyCode: 102,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ settings("skipToLast10Secs");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.lowerVolumeLarge", {
|
|
|
+ keyCode: 98,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ volumeSliderValue.value = Math.max(0, volumeSliderValue.value - 10);
|
|
|
+ changeVolume();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.lowerVolumeSmall", {
|
|
|
+ keyCode: 98,
|
|
|
+ ctrl: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ volumeSliderValue.value = Math.max(0, volumeSliderValue.value - 1);
|
|
|
+ changeVolume();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.increaseVolumeLarge", {
|
|
|
+ keyCode: 104,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ volumeSliderValue.value = Math.min(
|
|
|
+ 100,
|
|
|
+ volumeSliderValue.value + 10
|
|
|
+ );
|
|
|
+ changeVolume();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.increaseVolumeSmall", {
|
|
|
+ keyCode: 104,
|
|
|
+ ctrl: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ volumeSliderValue.value = Math.min(
|
|
|
+ 100,
|
|
|
+ volumeSliderValue.value + 1
|
|
|
+ );
|
|
|
+ changeVolume();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.save", {
|
|
|
+ keyCode: 83,
|
|
|
+ ctrl: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ save(song.value, false, "saveButton");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.saveClose", {
|
|
|
+ keyCode: 83,
|
|
|
+ ctrl: true,
|
|
|
+ alt: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ save(song.value, true, "saveAndCloseButton");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.focusTitle", {
|
|
|
+ keyCode: 36,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ inputs.value["title-input"].focus();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.useAllDiscogs", {
|
|
|
+ keyCode: 68,
|
|
|
+ alt: true,
|
|
|
+ ctrl: true,
|
|
|
+ preventDefault: true,
|
|
|
+ handler: () => {
|
|
|
+ getAlbumData("title");
|
|
|
+ getAlbumData("albumArt");
|
|
|
+ getAlbumData("artists");
|
|
|
+ getAlbumData("genres");
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ keyboardShortcuts.registerShortcut("editSong.closeModal", {
|
|
|
+ keyCode: 27,
|
|
|
+ handler: () => {
|
|
|
+ if (
|
|
|
+ modals.value[
|
|
|
+ activeModals.value[activeModals.value.length - 1]
|
|
|
+ ] === "editSong" ||
|
|
|
+ modals.value[
|
|
|
+ activeModals.value[activeModals.value.length - 1]
|
|
|
+ ] === "editSongs"
|
|
|
+ ) {
|
|
|
+ onCloseModal();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ /*
|
|
|
+
|
|
|
+ editSong.pauseResume - Num 5 - Pause/resume song
|
|
|
+ editSong.stopVideo - Ctrl - Num 5 - Stop
|
|
|
+ editSong.hardStopVideo - Shift - Ctrl - Num 5 - Stop
|
|
|
+ editSong.skipToLast10Secs - Num 6 - Skip to last 10 seconds
|
|
|
+
|
|
|
+ editSong.lowerVolumeLarge - Num 2 - Volume down by 10
|
|
|
+ editSong.lowerVolumeSmall - Ctrl - Num 2 - Volume down by 1
|
|
|
+ editSong.increaseVolumeLarge - Num 8 - Volume up by 10
|
|
|
+ editSong.increaseVolumeSmall - Ctrl - Num 8 - Volume up by 1
|
|
|
+
|
|
|
+ editSong.focusTitle - Home - Focus the title input
|
|
|
+ editSong.focusDicogs - End - Focus the discogs input
|
|
|
+
|
|
|
+ editSong.save - Ctrl - S - Saves song
|
|
|
+ editSong.save - Ctrl - Alt - S - Saves song and closes the modal
|
|
|
+ editSong.save - Ctrl - Alt - V - Saves song, verifies songs and then closes the modal
|
|
|
+ editSong.close - F4 - Closes modal without saving
|
|
|
+
|
|
|
+ editSong.useAllDiscogs - Ctrl - Alt - D - Sets all fields to the Discogs data
|
|
|
+
|
|
|
+ Inside Discogs inputs: Ctrl - D - Sets this field to the Discogs data
|
|
|
+
|
|
|
+ */
|
|
|
+});
|
|
|
+
|
|
|
+onBeforeUnmount(() => {
|
|
|
+ unloadSong(youtubeId.value, song.value._id);
|
|
|
+
|
|
|
+ playerReady.value = false;
|
|
|
+ clearInterval(interval.value);
|
|
|
+ clearInterval(activityWatchVideoDataInterval.value);
|
|
|
+
|
|
|
+ const shortcutNames = [
|
|
|
+ "editSong.pauseResume",
|
|
|
+ "editSong.stopVideo",
|
|
|
+ "editSong.hardStopVideo",
|
|
|
+ "editSong.skipToLast10Secs",
|
|
|
+ "editSong.lowerVolumeLarge",
|
|
|
+ "editSong.lowerVolumeSmall",
|
|
|
+ "editSong.increaseVolumeLarge",
|
|
|
+ "editSong.increaseVolumeSmall",
|
|
|
+ "editSong.focusTitle",
|
|
|
+ "editSong.focusDicogs",
|
|
|
+ "editSong.save",
|
|
|
+ "editSong.saveClose",
|
|
|
+ "editSong.useAllDiscogs",
|
|
|
+ "editSong.closeModal"
|
|
|
+ ];
|
|
|
+
|
|
|
+ shortcutNames.forEach(shortcutName => {
|
|
|
+ keyboardShortcuts.unregisterShortcut(shortcutName);
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!props.bulk) {
|
|
|
+ // Delete the VueX module that was created for this modal, after all other cleanup tasks are performed
|
|
|
+ store.unregisterModule(["modals", "editSong", props.modalUuid]);
|
|
|
+ } else {
|
|
|
+ store.unregisterModule([
|
|
|
+ "modals",
|
|
|
+ "editSongs",
|
|
|
+ props.modalUuid,
|
|
|
+ "editSong"
|
|
|
+ ]);
|
|
|
+ }
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<template>
|
|
|
+ <div>
|
|
|
+ <modal
|
|
|
+ :title="`${newSong ? 'Create' : 'Edit'} Song`"
|
|
|
+ class="song-modal"
|
|
|
+ :size="'wide'"
|
|
|
+ :split="true"
|
|
|
+ :intercept-close="true"
|
|
|
+ @close="onCloseModal"
|
|
|
+ >
|
|
|
+ <template #toggleMobileSidebar>
|
|
|
+ <slot name="toggleMobileSidebar" />
|
|
|
+ </template>
|
|
|
+ <template #sidebar>
|
|
|
+ <slot name="sidebar" />
|
|
|
+ </template>
|
|
|
+ <template #body>
|
|
|
+ <div v-if="!youtubeId && !newSong" class="notice-container">
|
|
|
+ <h4>No song has been selected</h4>
|
|
|
+ </div>
|
|
|
+ <div v-if="songDeleted" class="notice-container">
|
|
|
+ <h4>The song you were editing has been deleted</h4>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="
|
|
|
+ youtubeId &&
|
|
|
+ !songDataLoaded &&
|
|
|
+ !songNotFound &&
|
|
|
+ !newSong
|
|
|
+ "
|
|
|
+ class="notice-container"
|
|
|
+ >
|
|
|
+ <h4>Song hasn't loaded yet</h4>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ v-if="youtubeId && songNotFound && !newSong"
|
|
|
+ class="notice-container"
|
|
|
+ >
|
|
|
+ <h4>Song was not found</h4>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="left-section"
|
|
|
+ v-show="songDataLoaded && !songDeleted"
|
|
|
+ >
|
|
|
+ <div class="top-section">
|
|
|
+ <div class="player-section">
|
|
|
+ <div :id="`editSongPlayer-${modalUuid}`" />
|
|
|
+
|
|
|
+ <div v-show="youtubeError" class="player-error">
|
|
|
+ <h2>{{ youtubeErrorMessage }}</h2>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <canvas
|
|
|
+ ref="canvasElement"
|
|
|
+ class="duration-canvas"
|
|
|
+ v-show="!youtubeError"
|
|
|
+ height="20"
|
|
|
+ width="530"
|
|
|
+ @click="setTrackPosition($event)"
|
|
|
+ />
|
|
|
+ <div class="player-footer">
|
|
|
+ <div class="player-footer-left">
|
|
|
+ <button
|
|
|
+ class="button is-primary"
|
|
|
+ @click="play()"
|
|
|
+ @keyup.enter="play()"
|
|
|
+ v-if="video.paused"
|
|
|
+ content="Resume Playback"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ <i class="material-icons">play_arrow</i>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-primary"
|
|
|
+ @click="settings('pause')"
|
|
|
+ @keyup.enter="settings('pause')"
|
|
|
+ v-else
|
|
|
+ content="Pause Playback"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ <i class="material-icons">pause</i>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-danger"
|
|
|
+ @click.exact="settings('stop')"
|
|
|
+ @click.shift="settings('hardStop')"
|
|
|
+ @keyup.enter.exact="settings('stop')"
|
|
|
+ @keyup.shift.enter="
|
|
|
+ settings('hardStop')
|
|
|
+ "
|
|
|
+ content="Stop Playback"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ <i class="material-icons">stop</i>
|
|
|
+ </button>
|
|
|
+ <tippy
|
|
|
+ class="playerRateDropdown"
|
|
|
+ :touch="true"
|
|
|
+ :interactive="true"
|
|
|
+ placement="bottom"
|
|
|
+ theme="dropdown"
|
|
|
+ ref="dropdown"
|
|
|
+ trigger="click"
|
|
|
+ append-to="parent"
|
|
|
+ @show="
|
|
|
+ () => {
|
|
|
+ showRateDropdown = true;
|
|
|
+ }
|
|
|
+ "
|
|
|
+ @hide="
|
|
|
+ () => {
|
|
|
+ showRateDropdown = false;
|
|
|
+ }
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ ref="trigger"
|
|
|
+ class="control has-addons"
|
|
|
+ content="Set Playback Rate"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ <button class="button is-primary">
|
|
|
+ <i class="material-icons"
|
|
|
+ >fast_forward</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button dropdown-toggle"
|
|
|
+ >
|
|
|
+ <i class="material-icons">
|
|
|
+ {{
|
|
|
+ showRateDropdown
|
|
|
+ ? "expand_more"
|
|
|
+ : "expand_less"
|
|
|
+ }}
|
|
|
+ </i>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <template #content>
|
|
|
+ <div class="nav-dropdown-items">
|
|
|
+ <button
|
|
|
+ class="nav-item button"
|
|
|
+ :class="{
|
|
|
+ active:
|
|
|
+ video.playbackRate ===
|
|
|
+ 0.5
|
|
|
+ }"
|
|
|
+ title="0.5x"
|
|
|
+ @click="
|
|
|
+ setPlaybackRate(0.5)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <p>0.5x</p>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="nav-item button"
|
|
|
+ :class="{
|
|
|
+ active:
|
|
|
+ video.playbackRate ===
|
|
|
+ 1
|
|
|
+ }"
|
|
|
+ title="1x"
|
|
|
+ @click="setPlaybackRate(1)"
|
|
|
+ >
|
|
|
+ <p>1x</p>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="nav-item button"
|
|
|
+ :class="{
|
|
|
+ active:
|
|
|
+ video.playbackRate ===
|
|
|
+ 2
|
|
|
+ }"
|
|
|
+ title="2x"
|
|
|
+ @click="setPlaybackRate(2)"
|
|
|
+ >
|
|
|
+ <p>2x</p>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </tippy>
|
|
|
+ </div>
|
|
|
+ <div class="player-footer-center">
|
|
|
+ <span>
|
|
|
+ <span>
|
|
|
+ {{ youtubeVideoCurrentTime }}
|
|
|
+ </span>
|
|
|
+ /
|
|
|
+ <span>
|
|
|
+ {{ youtubeVideoDuration }}
|
|
|
+ {{ youtubeVideoNote }}
|
|
|
+ </span>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class="player-footer-right">
|
|
|
+ <p id="volume-control">
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ @click="toggleMute()"
|
|
|
+ :content="`${
|
|
|
+ muted ? 'Unmute' : 'Mute'
|
|
|
+ }`"
|
|
|
+ v-tippy
|
|
|
+ >{{
|
|
|
+ muted
|
|
|
+ ? "volume_mute"
|
|
|
+ : volumeSliderValue >= 50
|
|
|
+ ? "volume_up"
|
|
|
+ : "volume_down"
|
|
|
+ }}</i
|
|
|
+ >
|
|
|
+ <input
|
|
|
+ v-model="volumeSliderValue"
|
|
|
+ type="range"
|
|
|
+ min="0"
|
|
|
+ max="100"
|
|
|
+ class="volume-slider active"
|
|
|
+ @change="changeVolume()"
|
|
|
+ @input="changeVolume()"
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <song-thumbnail
|
|
|
+ v-if="songDataLoaded && !songDeleted"
|
|
|
+ :song="song"
|
|
|
+ :fallback="false"
|
|
|
+ class="thumbnail-preview"
|
|
|
+ @loadError="onThumbnailLoadError"
|
|
|
+ />
|
|
|
+ <img
|
|
|
+ v-if="
|
|
|
+ !isYoutubeThumbnail &&
|
|
|
+ songDataLoaded &&
|
|
|
+ !songDeleted
|
|
|
+ "
|
|
|
+ class="thumbnail-dummy"
|
|
|
+ :src="song.thumbnail"
|
|
|
+ ref="thumbnailElement"
|
|
|
+ @load="onThumbnailLoad"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div
|
|
|
+ class="edit-section"
|
|
|
+ v-if="songDataLoaded && !songDeleted"
|
|
|
+ >
|
|
|
+ <div class="control is-grouped">
|
|
|
+ <div class="title-container">
|
|
|
+ <label class="label">Title</label>
|
|
|
+ <p class="control has-addons">
|
|
|
+ <input
|
|
|
+ class="input"
|
|
|
+ type="text"
|
|
|
+ :ref="
|
|
|
+ el => (inputs['title-input'] = el)
|
|
|
+ "
|
|
|
+ v-model="song.title"
|
|
|
+ placeholder="Enter song title..."
|
|
|
+ @keyup.shift.enter="
|
|
|
+ getAlbumData('title')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button youtube-get-button"
|
|
|
+ @click="getYouTubeData('title')"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="youtube-icon"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from YouTube"
|
|
|
+ ></div>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button album-get-button"
|
|
|
+ @click="getAlbumData('title')"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from Discogs"
|
|
|
+ >album</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="duration-container">
|
|
|
+ <label class="label">Duration</label>
|
|
|
+ <p class="control has-addons">
|
|
|
+ <input
|
|
|
+ class="input"
|
|
|
+ type="text"
|
|
|
+ placeholder="Enter song duration..."
|
|
|
+ v-model.number="song.duration"
|
|
|
+ @keyup.shift.enter="fillDuration()"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button duration-fill-button"
|
|
|
+ @click="fillDuration()"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ v-tippy
|
|
|
+ content="Sync duration with YouTube"
|
|
|
+ >sync</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="skip-duration-container">
|
|
|
+ <label class="label">Skip duration</label>
|
|
|
+ <p class="control">
|
|
|
+ <input
|
|
|
+ class="input"
|
|
|
+ type="text"
|
|
|
+ placeholder="Enter skip duration..."
|
|
|
+ v-model.number="song.skipDuration"
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="control is-grouped">
|
|
|
+ <div class="album-art-container">
|
|
|
+ <label class="label">
|
|
|
+ Thumbnail
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ thumbnailNotSquare &&
|
|
|
+ !isYoutubeThumbnail
|
|
|
+ "
|
|
|
+ class="material-icons thumbnail-warning"
|
|
|
+ content="Thumbnail not square, it will be stretched"
|
|
|
+ v-tippy="{ theme: 'info' }"
|
|
|
+ >
|
|
|
+ warning
|
|
|
+ </i>
|
|
|
+ <i
|
|
|
+ v-if="
|
|
|
+ thumbnailLoadError &&
|
|
|
+ !isYoutubeThumbnail
|
|
|
+ "
|
|
|
+ class="material-icons thumbnail-warning"
|
|
|
+ content="Error loading thumbnail"
|
|
|
+ v-tippy="{ theme: 'info' }"
|
|
|
+ >
|
|
|
+ warning
|
|
|
+ </i>
|
|
|
+ </label>
|
|
|
+
|
|
|
+ <p class="control has-addons">
|
|
|
+ <input
|
|
|
+ class="input"
|
|
|
+ type="text"
|
|
|
+ v-model="song.thumbnail"
|
|
|
+ placeholder="Enter link to thumbnail..."
|
|
|
+ @keyup.shift.enter="
|
|
|
+ getAlbumData('albumArt')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button youtube-get-button"
|
|
|
+ @click="getYouTubeData('thumbnail')"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="youtube-icon"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from YouTube"
|
|
|
+ ></div>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button album-get-button"
|
|
|
+ @click="getAlbumData('albumArt')"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from Discogs"
|
|
|
+ >album</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <div class="youtube-id-container">
|
|
|
+ <label class="label">YouTube ID</label>
|
|
|
+ <p class="control">
|
|
|
+ <input
|
|
|
+ class="input"
|
|
|
+ type="text"
|
|
|
+ placeholder="Enter YouTube ID..."
|
|
|
+ v-model="song.youtubeId"
|
|
|
+ />
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ <div class="verified-container">
|
|
|
+ <label class="label">Verified</label>
|
|
|
+ <p class="is-expanded checkbox-control">
|
|
|
+ <label class="switch">
|
|
|
+ <input
|
|
|
+ type="checkbox"
|
|
|
+ id="verified"
|
|
|
+ v-model="song.verified"
|
|
|
+ />
|
|
|
+ <span class="slider round"></span>
|
|
|
+ </label>
|
|
|
+ </p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="control is-grouped">
|
|
|
+ <div class="artists-container">
|
|
|
+ <label class="label">Artists</label>
|
|
|
+ <p class="control has-addons">
|
|
|
+ <auto-suggest
|
|
|
+ v-model="artistInputValue"
|
|
|
+ ref="new-artist"
|
|
|
+ placeholder="Add artist..."
|
|
|
+ :all-items="
|
|
|
+ autosuggest.allItems.artists
|
|
|
+ "
|
|
|
+ @submitted="addTag('artists')"
|
|
|
+ @keyup.shift.enter="
|
|
|
+ getAlbumData('artists')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button youtube-get-button"
|
|
|
+ @click="getYouTubeData('author')"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="youtube-icon"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from YouTube"
|
|
|
+ ></div>
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button album-get-button"
|
|
|
+ @click="getAlbumData('artists')"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from Discogs"
|
|
|
+ >album</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-info add-button"
|
|
|
+ @click="addTag('artists')"
|
|
|
+ >
|
|
|
+ <i class="material-icons">add</i>
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ <div class="list-container">
|
|
|
+ <div
|
|
|
+ class="list-item"
|
|
|
+ v-for="artist in song.artists"
|
|
|
+ :key="artist"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="list-item-circle"
|
|
|
+ @click="
|
|
|
+ removeTag('artists', artist)
|
|
|
+ "
|
|
|
+ >
|
|
|
+ <i class="material-icons">close</i>
|
|
|
+ </div>
|
|
|
+ <p>{{ artist }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="genres-container">
|
|
|
+ <label class="label">
|
|
|
+ <span>Genres</span>
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ @click="toggleGenreHelper"
|
|
|
+ @dblclick="resetGenreHelper"
|
|
|
+ v-tippy
|
|
|
+ content="View list of genres"
|
|
|
+ >info</i
|
|
|
+ >
|
|
|
+ </label>
|
|
|
+ <p class="control has-addons">
|
|
|
+ <auto-suggest
|
|
|
+ v-model="genreInputValue"
|
|
|
+ ref="new-genre"
|
|
|
+ placeholder="Add genre..."
|
|
|
+ :all-items="autosuggest.allItems.genres"
|
|
|
+ @submitted="addTag('genres')"
|
|
|
+ @keyup.shift.enter="
|
|
|
+ getAlbumData('genres')
|
|
|
+ "
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button album-get-button"
|
|
|
+ @click="getAlbumData('genres')"
|
|
|
+ >
|
|
|
+ <i
|
|
|
+ class="material-icons"
|
|
|
+ v-tippy
|
|
|
+ content="Fill from Discogs"
|
|
|
+ >album</i
|
|
|
+ >
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-info add-button"
|
|
|
+ @click="addTag('genres')"
|
|
|
+ >
|
|
|
+ <i class="material-icons">add</i>
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ <div class="list-container">
|
|
|
+ <div
|
|
|
+ class="list-item"
|
|
|
+ v-for="genre in song.genres"
|
|
|
+ :key="genre"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="list-item-circle"
|
|
|
+ @click="removeTag('genres', genre)"
|
|
|
+ >
|
|
|
+ <i class="material-icons">close</i>
|
|
|
+ </div>
|
|
|
+ <p>{{ genre }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="tags-container">
|
|
|
+ <label class="label">Tags</label>
|
|
|
+ <p class="control has-addons">
|
|
|
+ <auto-suggest
|
|
|
+ v-model="tagInputValue"
|
|
|
+ ref="new-tag"
|
|
|
+ placeholder="Add tag..."
|
|
|
+ :all-items="autosuggest.allItems.tags"
|
|
|
+ @submitted="addTag('tags')"
|
|
|
+ />
|
|
|
+ <button
|
|
|
+ class="button is-info add-button"
|
|
|
+ @click="addTag('tags')"
|
|
|
+ >
|
|
|
+ <i class="material-icons">add</i>
|
|
|
+ </button>
|
|
|
+ </p>
|
|
|
+ <div class="list-container">
|
|
|
+ <div
|
|
|
+ class="list-item"
|
|
|
+ v-for="tag in song.tags"
|
|
|
+ :key="tag"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ class="list-item-circle"
|
|
|
+ @click="removeTag('tags', tag)"
|
|
|
+ >
|
|
|
+ <i class="material-icons">close</i>
|
|
|
+ </div>
|
|
|
+ <p>{{ tag }}</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ class="right-section"
|
|
|
+ v-if="songDataLoaded && !songDeleted"
|
|
|
+ >
|
|
|
+ <div id="tabs-container">
|
|
|
+ <div id="tab-selection">
|
|
|
+ <button
|
|
|
+ class="button is-default"
|
|
|
+ :class="{ selected: tab === 'discogs' }"
|
|
|
+ :ref="el => (tabs['discogs-tab'] = el)"
|
|
|
+ @click="showTab('discogs')"
|
|
|
+ >
|
|
|
+ Discogs
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ v-if="!newSong"
|
|
|
+ class="button is-default"
|
|
|
+ :class="{ selected: tab === 'reports' }"
|
|
|
+ :ref="el => (tabs['reports-tab'] = el)"
|
|
|
+ @click="showTab('reports')"
|
|
|
+ >
|
|
|
+ Reports ({{ reports.length }})
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-default"
|
|
|
+ :class="{ selected: tab === 'youtube' }"
|
|
|
+ :ref="el => (tabs['youtube-tab'] = el)"
|
|
|
+ @click="showTab('youtube')"
|
|
|
+ >
|
|
|
+ YouTube
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-default"
|
|
|
+ :class="{ selected: tab === 'musare-songs' }"
|
|
|
+ :ref="el => (tabs['musare-songs-tab'] = el)"
|
|
|
+ @click="showTab('musare-songs')"
|
|
|
+ >
|
|
|
+ Songs
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <discogs
|
|
|
+ class="tab"
|
|
|
+ v-show="tab === 'discogs'"
|
|
|
+ :bulk="bulk"
|
|
|
+ :modal-uuid="modalUuid"
|
|
|
+ :modal-module-path="modalModulePath"
|
|
|
+ />
|
|
|
+ <reports-tab
|
|
|
+ v-if="!newSong"
|
|
|
+ class="tab"
|
|
|
+ v-show="tab === 'reports'"
|
|
|
+ :modal-uuid="modalUuid"
|
|
|
+ :modal-module-path="modalModulePath"
|
|
|
+ />
|
|
|
+ <youtube
|
|
|
+ class="tab"
|
|
|
+ v-show="tab === 'youtube'"
|
|
|
+ :modal-uuid="modalUuid"
|
|
|
+ :modal-module-path="modalModulePath"
|
|
|
+ />
|
|
|
+ <musare-songs
|
|
|
+ class="tab"
|
|
|
+ v-show="tab === 'musare-songs'"
|
|
|
+ :modal-uuid="modalUuid"
|
|
|
+ :modal-module-path="modalModulePath"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ <template #footer>
|
|
|
+ <div v-if="bulk">
|
|
|
+ <button class="button is-primary" @click="editNextSong()">
|
|
|
+ Next
|
|
|
+ </button>
|
|
|
+ <button
|
|
|
+ class="button is-primary"
|
|
|
+ @click="toggleFlag()"
|
|
|
+ v-if="youtubeId && !songDeleted"
|
|
|
+ >
|
|
|
+ {{ flagged ? "Unflag" : "Flag" }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ <div v-if="!newSong && !songDeleted">
|
|
|
+ <save-button
|
|
|
+ :ref="el => (saveButtonRefs['saveButton'] = el)"
|
|
|
+ @clicked="save(song, false, 'saveButton')"
|
|
|
+ />
|
|
|
+ <save-button
|
|
|
+ :ref="el => (saveButtonRefs['saveAndCloseButton'] = el)"
|
|
|
+ :default-message="
|
|
|
+ bulk ? `Save and next` : `Save and close`
|
|
|
+ "
|
|
|
+ @clicked="save(song, true, 'saveAndCloseButton')"
|
|
|
+ />
|
|
|
+
|
|
|
+ <div class="right">
|
|
|
+ <button
|
|
|
+ class="button is-danger icon-with-button material-icons"
|
|
|
+ @click.prevent="
|
|
|
+ confirmAction({
|
|
|
+ message:
|
|
|
+ 'Removing this song will remove it from all playlists and cause a ratings recalculation.',
|
|
|
+ action: remove,
|
|
|
+ params: song._id
|
|
|
+ })
|
|
|
+ "
|
|
|
+ content="Delete Song"
|
|
|
+ v-tippy
|
|
|
+ >
|
|
|
+ delete_forever
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="newSong">
|
|
|
+ <save-button
|
|
|
+ :ref="el => (saveButtonRefs['createButton'] = el)"
|
|
|
+ default-message="Create Song"
|
|
|
+ @clicked="save(song, false, 'createButton', true)"
|
|
|
+ />
|
|
|
+ <save-button
|
|
|
+ :ref="
|
|
|
+ el => (saveButtonRefs['createAndCloseButton'] = el)
|
|
|
+ "
|
|
|
+ :default-message="
|
|
|
+ bulk ? `Create and next` : `Create and close`
|
|
|
+ "
|
|
|
+ @clicked="
|
|
|
+ save(song, true, 'createAndCloseButton', true)
|
|
|
+ "
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </modal>
|
|
|
+ <floating-box
|
|
|
+ id="genreHelper"
|
|
|
+ ref="genreHelper"
|
|
|
+ :column="false"
|
|
|
+ title="Song Genres List"
|
|
|
+ >
|
|
|
+ <template #body>
|
|
|
+ <span
|
|
|
+ v-for="item in autosuggest.allItems.genres"
|
|
|
+ :key="`genre-helper-${item}`"
|
|
|
+ >
|
|
|
+ {{ item }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </floating-box>
|
|
|
+ </div>
|
|
|
+</template>
|
|
|
|
|
|
<style lang="less" scoped>
|
|
|
.night-mode {
|