瀏覽代碼

feat: Add media item component

Owen Diffey 2 周之前
父節點
當前提交
d54c63db41
共有 1 個文件被更改,包括 245 次插入0 次删除
  1. 245 0
      frontend/src/pages/NewStation/Components/MediaItem.vue

+ 245 - 0
frontend/src/pages/NewStation/Components/MediaItem.vue

@@ -0,0 +1,245 @@
+<script lang="ts" setup>
+import { defineAsyncComponent, ref } from "vue";
+import { storeToRefs } from "pinia";
+import dayjs from "@/dayjs";
+import { useModalsStore } from "@/stores/modals";
+import { useUserAuthStore } from "@/stores/userAuth";
+import DropdownListItem from "@/pages/NewStation/Components/DropdownListItem.vue";
+
+const AddToPlaylistDropdown = defineAsyncComponent(
+	() => import("@/pages/NewStation/Components/AddToPlaylistDropdown.vue")
+);
+const Button = defineAsyncComponent(
+	() => import("@/pages/NewStation/Components/Button.vue")
+);
+const DropdownList = defineAsyncComponent(
+	() => import("@/pages/NewStation/Components/DropdownList.vue")
+);
+const SongThumbnail = defineAsyncComponent(
+	() => import("@/components/SongThumbnail.vue")
+);
+const UserLink = defineAsyncComponent(
+	() => import("@/components/UserLink.vue")
+);
+
+// TODO: Experimental: soundcloud
+
+const props = defineProps<{
+	media: any;
+}>();
+
+const { openModal } = useModalsStore();
+
+const userAuthStore = useUserAuthStore();
+const { hasPermission } = userAuthStore;
+const { loggedIn } = storeToRefs(userAuthStore);
+
+const actions = ref();
+
+const expandActions = () => {
+	actions.value.expand();
+};
+
+const collapseActions = () => {
+	actions.value.collapse();
+};
+
+const edit = () => {
+	collapseActions();
+
+	openModal({
+		modal: "editSong",
+		props: { song: props.media }
+	});
+};
+
+const report = () => {
+	collapseActions();
+
+	openModal({
+		modal: "report",
+		props: { song: props.media }
+	});
+};
+
+const view = () => {
+	collapseActions();
+
+	openModal({
+		modal: "viewMedia",
+		props: { mediaSource: props.media.mediaSource }
+	});
+};
+
+defineExpose({
+	expandActions,
+	collapseActions
+});
+</script>
+
+<template>
+	<div class="media-item">
+		<SongThumbnail :song="media" />
+		<div class="media-item__content">
+			<p class="media-item__title" :title="media.title">
+				{{ media.title }}
+			</p>
+			<p class="media-item__artists" :title="media.artists?.join(', ')">
+				{{ media.artists?.join(", ") }}
+			</p>
+			<p
+				v-if="media.requestedBy || media.requestedType"
+				class="media-item__requested"
+			>
+				<strong>
+					{{ dayjs.duration(media.duration, "s").formatDuration() }}
+				</strong>
+				<span class="media-item__divider">&middot;</span>
+				<UserLink
+					v-if="media.requestedBy"
+					:key="media.mediaSource"
+					:user-id="media.requestedBy"
+				/>
+				<span v-else>Station</span>
+				<span>
+					<template v-if="media.requestedType === 'autofill'">
+						requested automatically
+					</template>
+					<template v-else-if="media.requestedType === 'autorequest'">
+						autorequested
+					</template>
+					<template v-else>requested</template>
+				</span>
+				<span :title="dayjs(media.requestedAt).format()">{{
+					dayjs(media.requestedAt).fromNow()
+				}}</span>
+			</p>
+		</div>
+		<DropdownList ref="actions">
+			<Button icon="more_horiz" square inverse title="Actions" />
+
+			<template #options>
+				<slot name="actions" />
+
+				<template v-if="loggedIn">
+					<DropdownListItem>
+						<AddToPlaylistDropdown :media="media">
+							<button class="dropdown-list-item__action">
+								<span
+									class="material-icons dropdown-list-item__icon"
+									aria-hidden="true"
+								>
+									playlist_add
+								</span>
+								Add to playlist
+							</button>
+						</AddToPlaylistDropdown>
+					</DropdownListItem>
+					<DropdownListItem
+						icon="play_arrow"
+						label="View media"
+						@click="view"
+					/>
+					<DropdownListItem
+						icon="flag"
+						label="Report song"
+						@click="report"
+					/>
+					<DropdownListItem
+						v-if="media._id && hasPermission('songs.update')"
+						icon="edit"
+						label="Edit song"
+						@click="edit"
+					/>
+				</template>
+				<DropdownListItem
+					v-else
+					icon="play_arrow"
+					label="View on YouTube"
+					:href="`https://www.youtube.com/watch?v=${media.mediaSource.split(':')[1]}`"
+					target="_blank"
+				/>
+			</template>
+		</DropdownList>
+	</div>
+</template>
+
+<style lang="less" scoped>
+.media-item {
+	display: flex;
+	flex-shrink: 0;
+	height: 48px;
+	background-color: var(--white);
+	border-radius: 5px;
+	border: solid 1px var(--light-grey-1);
+	gap: 5px;
+	overflow: hidden;
+
+	:deep(.thumbnail) {
+		height: 48px;
+		min-width: 48px;
+		flex-shrink: 0;
+		margin: 0;
+	}
+
+	&__content {
+		display: flex;
+		flex-direction: column;
+		flex-grow: 1;
+		min-width: 0;
+		justify-content: center;
+	}
+
+	&__title {
+		display: inline-flex;
+		align-items: center;
+		gap: 2px;
+		font-size: 12px !important;
+		line-height: 16px;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	&__artists {
+		display: inline-flex;
+		align-items: center;
+		font-size: 11px !important;
+		font-weight: 500 !important;
+		line-height: 12px;
+		color: var(--dark-grey-1);
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	&__requested {
+		display: inline-flex;
+		align-items: center;
+		gap: 2px;
+		font-size: 10px !important;
+		font-weight: 500 !important;
+		line-height: 12px;
+		color: var(--dark-grey-1);
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
+
+	&__divider {
+		font-size: 25px;
+	}
+
+	& > :deep(.dropdown-list__reference) {
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		padding-right: 5px;
+	}
+
+	:deep(.dropdown-list-item > .dropdown-list__reference) {
+		display: flex;
+		flex-grow: 1;
+	}
+}
+</style>