SongThumbnail.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. <script setup lang="ts">
  2. import { ref, computed, watch } from "vue";
  3. const props = defineProps({
  4. song: { type: Object, default: () => {} },
  5. fallback: { type: Boolean, default: true }
  6. });
  7. const emit = defineEmits(["loadError"]);
  8. const loadError = ref(0);
  9. const loaded = ref(false);
  10. const isYoutubeThumbnail = computed(
  11. () =>
  12. ((props.song.mediaSource &&
  13. props.song.mediaSource.startsWith("youtube:")) ||
  14. props.song.youtubeId) &&
  15. ((props.song.thumbnail &&
  16. (props.song.thumbnail.lastIndexOf("i.ytimg.com") !== -1 ||
  17. props.song.thumbnail.lastIndexOf("img.youtube.com") !== -1)) ||
  18. (props.fallback &&
  19. (!props.song.thumbnail ||
  20. (props.song.thumbnail &&
  21. (props.song.thumbnail.lastIndexOf(
  22. "notes-transparent"
  23. ) !== -1 ||
  24. props.song.thumbnail.lastIndexOf(
  25. "/assets/notes.png"
  26. ) !== -1 ||
  27. props.song.thumbnail === "empty")) ||
  28. loadError.value === 1)))
  29. );
  30. const onLoadError = () => {
  31. // Error codes
  32. // -1 - Error occured, fallback disabled
  33. // 0 - No errors
  34. // 1 - Error occured with thumbnail, fallback enabled
  35. // 2 - Error occured with youtube thumbnail, fallback enabled
  36. if (!props.fallback) loadError.value = -1;
  37. else if (
  38. loadError.value === 0 &&
  39. !isYoutubeThumbnail.value &&
  40. !(
  41. props.song.mediaSource &&
  42. props.song.mediaSource.startsWith("soundcloud:")
  43. )
  44. )
  45. loadError.value = 1;
  46. else loadError.value = 2;
  47. emit("loadError", loadError.value);
  48. };
  49. const onLoad = () => {
  50. loaded.value = true;
  51. };
  52. watch(
  53. () => props.song,
  54. () => {
  55. loadError.value = 0;
  56. emit("loadError", loadError.value);
  57. }
  58. );
  59. </script>
  60. <template>
  61. <div class="thumbnail">
  62. <slot name="icon" />
  63. <template v-if="loadError < 2">
  64. <div
  65. v-if="loaded"
  66. class="thumbnail-bg"
  67. :style="{
  68. 'background-image': `url('${
  69. isYoutubeThumbnail
  70. ? `https://img.youtube.com/vi/${
  71. song.mediaSource
  72. ? song.mediaSource.split(':')[1]
  73. : song.youtubeId
  74. }/mqdefault.jpg`
  75. : song.thumbnail
  76. }')`
  77. }"
  78. ></div>
  79. <img
  80. loading="lazy"
  81. :src="
  82. isYoutubeThumbnail
  83. ? `https://img.youtube.com/vi/${
  84. song.mediaSource
  85. ? song.mediaSource.split(':')[1]
  86. : song.youtubeId
  87. }/mqdefault.jpg`
  88. : song.thumbnail
  89. "
  90. @error="onLoadError"
  91. @load="onLoad"
  92. />
  93. </template>
  94. <img v-else loading="lazy" src="/assets/notes-transparent.png" />
  95. </div>
  96. </template>
  97. <style lang="less">
  98. .thumbnail {
  99. min-width: 130px;
  100. height: 130px;
  101. position: relative;
  102. margin-top: -15px;
  103. margin-bottom: -15px;
  104. margin-left: -10px;
  105. overflow: hidden;
  106. img {
  107. width: 100%;
  108. margin-top: auto;
  109. margin-bottom: auto;
  110. z-index: 1;
  111. position: absolute;
  112. top: 0;
  113. bottom: 0;
  114. left: 0;
  115. right: 0;
  116. }
  117. .thumbnail-bg {
  118. height: 100%;
  119. width: 100%;
  120. display: block;
  121. position: absolute;
  122. top: 0;
  123. filter: blur(1px);
  124. background: url("/assets/notes-transparent.png") no-repeat center center;
  125. background-size: cover;
  126. }
  127. }
  128. </style>