hippofish/packages/client/src/components/MkMediaList.vue

340 lines
7.2 KiB
Vue
Raw Normal View History

<template>
2023-05-28 05:24:38 +02:00
<div class="hoawjimk files">
2023-04-08 02:01:42 +02:00
<XBanner
v-for="media in mediaList.filter((media) => !previewable(media))"
:key="media.id"
:media="media"
/>
<div
v-if="previewableCount > 0"
2023-05-28 05:24:38 +02:00
class="grid-container"
:data-count="previewableCount < 5 ? previewableCount : null"
2023-04-08 02:01:42 +02:00
:class="{ dmWidth: inDm }"
>
<div ref="gallery" @click.stop>
2023-09-22 05:48:54 +02:00
<template
2023-04-08 02:01:42 +02:00
v-for="media in mediaList.filter((media) =>
2023-07-06 03:28:27 +02:00
previewable(media),
2023-04-08 02:01:42 +02:00
)"
2023-09-22 05:48:54 +02:00
>
<XMedia
v-if="
media.type.startsWith('video') ||
media.type.startsWith('image')
"
:key="media.id"
:class="{ image: media.type.startsWith('image') }"
:data-id="media.id"
:media="media"
:raw="raw"
/>
<XModPlayer
v-else-if="isModule(media)"
:key="media.id"
:module="media"
/>
</template>
2023-04-08 02:01:42 +02:00
</div>
</div>
</div>
</template>
2022-01-27 16:52:05 +01:00
<script lang="ts" setup>
2023-04-08 02:01:42 +02:00
import { onMounted, ref } from "vue";
2023-09-24 06:27:16 +02:00
import type * as firefish from "firefish-js";
2023-04-08 02:01:42 +02:00
import PhotoSwipeLightbox from "photoswipe/lightbox";
import PhotoSwipe from "photoswipe";
import "photoswipe/style.css";
import XBanner from "@/components/MkMediaBanner.vue";
import XMedia from "@/components/MkMedia.vue";
2023-09-22 05:48:54 +02:00
import XModPlayer from "@/components/MkModPlayer.vue";
2023-04-08 02:01:42 +02:00
import * as os from "@/os";
2023-09-22 05:48:54 +02:00
import {
2023-10-22 10:44:32 +02:00
FILE_EXT_TRACKER_MODULES,
2023-09-22 05:48:54 +02:00
FILE_TYPE_BROWSERSAFE,
FILE_TYPE_TRACKER_MODULES,
} from "@/const";
2022-01-27 16:52:05 +01:00
const props = defineProps<{
2023-09-24 06:27:16 +02:00
mediaList: firefish.entities.DriveFile[];
2022-01-27 16:52:05 +01:00
raw?: boolean;
2022-11-15 05:25:59 +01:00
inDm?: boolean;
2022-01-27 16:52:05 +01:00
}>();
2022-01-27 16:52:05 +01:00
const gallery = ref(null);
2023-04-08 02:01:42 +02:00
const pswpZIndex = os.claimZIndex("middle");
2022-01-27 16:52:05 +01:00
onMounted(() => {
const lightbox = new PhotoSwipeLightbox({
dataSource: props.mediaList
2023-04-08 02:01:42 +02:00
.filter((media) => {
if (media.type === "image/svg+xml") return true; // svgのwebpublicはpngなのでtrue
return (
media.type.startsWith("image") &&
FILE_TYPE_BROWSERSAFE.includes(media.type)
);
2022-01-27 16:52:05 +01:00
})
2023-04-08 02:01:42 +02:00
.map((media) => {
2022-01-27 16:52:05 +01:00
const item = {
src: media.url,
w: media.properties.width,
h: media.properties.height,
alt: media.comment,
2022-01-27 16:52:05 +01:00
};
2023-04-08 02:01:42 +02:00
if (
media.properties.orientation != null &&
media.properties.orientation >= 5
) {
2022-01-27 16:52:05 +01:00
[item.w, item.h] = [item.h, item.w];
}
return item;
}),
gallery: gallery.value,
2023-04-08 02:01:42 +02:00
children: ".image",
thumbSelector: ".image img",
2022-01-27 16:52:05 +01:00
loop: false,
2023-04-08 02:01:42 +02:00
padding:
window.innerWidth > 500
? {
top: 32,
bottom: 32,
left: 32,
right: 32,
}
: {
top: 0,
bottom: 0,
left: 0,
right: 0,
},
imageClickAction: "close",
tapAction: "toggle-controls",
2023-05-17 18:34:45 +02:00
preloadFirstSlide: false,
2022-01-27 16:52:05 +01:00
pswpModule: PhotoSwipe,
});
2023-04-08 02:01:42 +02:00
lightbox.on("itemData", (ev) => {
2022-01-27 16:52:05 +01:00
const { itemData } = ev;
2022-01-27 16:52:05 +01:00
// element is children
const { element } = itemData;
2022-01-27 16:52:05 +01:00
const id = element.dataset.id;
2023-04-08 02:01:42 +02:00
const file = props.mediaList.find((media) => media.id === id);
2022-01-27 16:52:05 +01:00
itemData.src = file.url;
itemData.w = Number(file.properties.width);
itemData.h = Number(file.properties.height);
2023-04-08 02:01:42 +02:00
if (
file.properties.orientation != null &&
file.properties.orientation >= 5
) {
2022-01-27 16:52:05 +01:00
[itemData.w, itemData.h] = [itemData.h, itemData.w];
}
itemData.msrc = file.thumbnailUrl;
itemData.alt = file.comment;
2022-01-27 16:52:05 +01:00
itemData.thumbCropped = true;
});
2023-04-08 02:01:42 +02:00
lightbox.on("uiRegister", () => {
2022-09-29 06:21:07 +02:00
lightbox.pswp.ui.registerElement({
2023-04-08 02:01:42 +02:00
name: "altText",
className: "pwsp__alt-text-container",
appendTo: "wrapper",
2022-09-29 06:21:07 +02:00
onInit: (el, pwsp) => {
2023-09-02 01:27:33 +02:00
const textBox = document.createElement("p");
2023-04-08 02:01:42 +02:00
textBox.className = "pwsp__alt-text";
2022-09-29 06:21:07 +02:00
el.appendChild(textBox);
2023-09-02 01:27:33 +02:00
const preventProp = function (ev: Event): void {
ev.stopPropagation();
};
// Allow scrolling/text selection
el.onwheel = preventProp;
el.onclick = preventProp;
el.onpointerdown = preventProp;
el.onpointercancel = preventProp;
el.onpointermove = preventProp;
2023-04-08 02:01:42 +02:00
pwsp.on("change", () => {
textBox.textContent = pwsp.currSlide.data.alt?.trim();
2022-09-29 06:21:07 +02:00
});
},
});
});
2023-05-17 18:34:45 +02:00
lightbox.on("afterInit", () => {
history.pushState(null, "", location.href);
addEventListener("popstate", close);
2023-05-17 19:46:00 +02:00
// This is a workaround. Not sure why, but when clicking to open, it doesn't move focus to the photoswipe. Preventing using esc to close. However when using keyboard to open it already focuses the lightbox fine.
lightbox.pswp.element.focus();
2023-05-20 00:41:59 +02:00
});
2023-05-17 18:34:45 +02:00
lightbox.on("close", () => {
removeEventListener("popstate", close);
history.back();
2023-05-20 00:41:59 +02:00
});
2023-05-17 18:34:45 +02:00
2022-01-27 16:52:05 +01:00
lightbox.init();
2023-05-17 18:34:45 +02:00
function close() {
removeEventListener("popstate", close);
history.forward();
lightbox.pswp.close();
}
});
2022-01-27 16:52:05 +01:00
2023-09-24 06:27:16 +02:00
const previewable = (file: firefish.entities.DriveFile): boolean => {
2023-04-08 02:01:42 +02:00
if (file.type === "image/svg+xml") return true; // svgのwebpublic/thumbnailはpngなのでtrue
2022-01-27 16:52:05 +01:00
// FILE_TYPE_BROWSERSAFEに適合しないものはブラウザで表示するのに不適切
2023-09-22 05:48:54 +02:00
if (isModule(file)) return true;
2023-04-08 02:01:42 +02:00
return (
(file.type.startsWith("video") || file.type.startsWith("image")) &&
FILE_TYPE_BROWSERSAFE.includes(file.type)
);
2022-01-27 16:52:05 +01:00
};
2023-09-22 05:48:54 +02:00
2023-09-24 06:27:16 +02:00
const isModule = (file: firefish.entities.DriveFile): boolean => {
2023-09-22 05:48:54 +02:00
return (
2023-10-22 10:44:32 +02:00
FILE_TYPE_TRACKER_MODULES.includes(file.type) ||
2023-09-22 05:48:54 +02:00
FILE_EXT_TRACKER_MODULES.some((ext) => {
return file.name.toLowerCase().endsWith("." + ext);
})
);
};
const previewableCount = props.mediaList.filter((media) =>
2023-07-06 03:28:27 +02:00
previewable(media),
).length;
</script>
<style lang="scss" scoped>
.hoawjimk {
> .dmWidth {
min-width: 20rem;
max-width: 40rem;
2022-11-15 05:25:59 +01:00
}
2023-05-28 05:24:38 +02:00
> .grid-container {
position: relative;
width: 100%;
margin-top: 4px;
border-radius: var(--radius);
overflow: hidden;
2023-05-08 21:39:20 +02:00
pointer-events: none;
2023-05-28 05:24:38 +02:00
&[data-count] {
2023-04-08 02:01:42 +02:00
padding-top: 56.25%; // 16:9;
2023-05-28 05:24:38 +02:00
> div {
position: absolute;
inset: 0;
}
}
2023-05-28 05:24:38 +02:00
&[data-count="1"] > div {
grid-template-rows: 1fr;
}
2023-05-28 05:24:38 +02:00
&[data-count="2"] > div {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr;
}
2023-05-28 05:24:38 +02:00
&[data-count="3"] > div {
grid-template-columns: 1fr 0.5fr;
grid-template-rows: 1fr 1fr;
> *:nth-child(1) {
grid-row: 1 / 3;
}
2023-05-28 05:24:38 +02:00
> *:nth-child(3) {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
2023-05-28 05:24:38 +02:00
}
2023-05-28 05:24:38 +02:00
&[data-count="4"] > div {
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
2023-05-28 05:24:38 +02:00
&:not([data-count]) > div > div {
max-height: 300px;
}
2023-04-05 06:47:30 +02:00
2023-05-28 05:24:38 +02:00
> div {
display: grid;
grid-gap: 8px;
> div,
> button {
2023-05-28 05:24:38 +02:00
overflow: hidden;
border-radius: 6px;
pointer-events: all;
min-height: 50px;
}
2023-05-28 05:24:38 +02:00
> :nth-child(1) {
grid-column: 1 / 2;
grid-row: 1 / 2;
}
2023-05-28 05:24:38 +02:00
> :nth-child(2) {
grid-column: 2 / 3;
grid-row: 1 / 2;
}
2023-05-28 05:24:38 +02:00
> :nth-child(3) {
grid-column: 1 / 2;
grid-row: 2 / 3;
}
2023-05-28 05:24:38 +02:00
> :nth-child(4) {
grid-column: 2 / 3;
grid-row: 2 / 3;
}
}
}
}
</style>
<style lang="scss">
.pswp {
// なぜか機能しない
2023-04-08 02:01:42 +02:00
//z-index: v-bind(pswpZIndex);
z-index: 2000000;
}
2022-09-29 06:21:07 +02:00
.pwsp__alt-text-container {
display: flex;
flex-direction: row;
align-items: center;
position: absolute;
bottom: 30px;
2022-09-29 06:21:07 +02:00
left: 50%;
transform: translateX(-50%);
width: 75%;
}
.pwsp__alt-text {
color: white;
2022-09-29 06:21:07 +02:00
margin: 0 auto;
text-align: center;
padding: 10px;
background: rgba(0, 0, 0, 0.5);
border-radius: 5px;
max-height: 10vh;
overflow-x: clip;
2022-09-29 06:21:07 +02:00
overflow-y: auto;
overscroll-behavior: contain;
white-space: pre-line;
}
.pwsp__alt-text:empty {
display: none;
2022-09-29 06:21:07 +02:00
}
</style>