Merge pull request #240 from arabcoders/dev
Small improvements to file browser.
This commit is contained in:
commit
80737f7d90
12 changed files with 320 additions and 58 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
|
@ -23,6 +23,7 @@
|
|||
"daterange",
|
||||
"dotenv",
|
||||
"finaldir",
|
||||
"flac",
|
||||
"getpid",
|
||||
"gpac",
|
||||
"httpx",
|
||||
|
|
@ -36,6 +37,7 @@
|
|||
"muxdelay",
|
||||
"nodesc",
|
||||
"noprogress",
|
||||
"plexmatch",
|
||||
"postprocessor",
|
||||
"preferredcodec",
|
||||
"preferredquality",
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ from .Utils import (
|
|||
encrypt_data,
|
||||
extract_info,
|
||||
get_file,
|
||||
get_file_sidecar,
|
||||
get_files,
|
||||
get_mime_type,
|
||||
get_sidecar_subtitles,
|
||||
validate_url,
|
||||
validate_uuid,
|
||||
)
|
||||
|
|
@ -1550,15 +1550,19 @@ class HttpAPI(Common):
|
|||
ff_info = await ffprobe(realFile)
|
||||
|
||||
response = {
|
||||
"title": str(Path(realFile).stem),
|
||||
"ffprobe": ff_info,
|
||||
"mimetype": get_mime_type(ff_info.get("metadata", {}), realFile),
|
||||
"sidecar": get_sidecar_subtitles(realFile),
|
||||
"sidecar": get_file_sidecar(realFile),
|
||||
}
|
||||
|
||||
for i, f in enumerate(response["sidecar"]):
|
||||
response["sidecar"][i]["file"] = (
|
||||
str(Path(realFile).with_name(f["file"].name)).replace(self.config.download_path, "").strip("/")
|
||||
)
|
||||
for key in response["sidecar"]:
|
||||
for i, f in enumerate(response["sidecar"][key]):
|
||||
response["sidecar"][key][i]["file"] = (
|
||||
str(Path(realFile).with_name(Path(f["file"]).name))
|
||||
.replace(self.config.download_path, "")
|
||||
.strip("/")
|
||||
)
|
||||
|
||||
return web.json_response(data=response, status=web.HTTPOk.status_code, dumps=self.encoder.encode)
|
||||
except Exception as e:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
from urllib.parse import quote
|
||||
|
||||
from .ffprobe import ffprobe
|
||||
from .Utils import StreamingError, get_sidecar_subtitles
|
||||
from .Utils import StreamingError, get_file_sidecar
|
||||
|
||||
|
||||
class Playlist:
|
||||
|
|
@ -30,9 +30,9 @@ class Playlist:
|
|||
subs = ""
|
||||
|
||||
duration: float = float(ff.metadata.get("duration"))
|
||||
for sub_file in get_sidecar_subtitles(file):
|
||||
for sub_file in get_file_sidecar(file).get("subtitle", []):
|
||||
lang = sub_file["lang"]
|
||||
item = sub_file["file"]
|
||||
item = Path(sub_file["file"])
|
||||
name = sub_file["name"]
|
||||
|
||||
subs = ',SUBTITLES="subs"'
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@ YTDLP_INFO_CLS: yt_dlp.YoutubeDL = None
|
|||
|
||||
ALLOWED_SUBS_EXTENSIONS: tuple[str] = (".srt", ".vtt", ".ass")
|
||||
|
||||
FILES_TYPE: list = [
|
||||
{"rx": re.compile(r"\.(avi|ts|mkv|mp4|mp3|mpv|ogm|m4v|webm|m4b)$", re.IGNORECASE), "type": "video"},
|
||||
{"rx": re.compile(r"\.(mp3|flac|aac|opus|wav|m4a)$", re.IGNORECASE), "type": "audio"},
|
||||
{"rx": re.compile(r"\.(srt|ass|ssa|smi|sub|idx)$", re.IGNORECASE), "type": "subtitle"},
|
||||
{"rx": re.compile(r"\.(jpg|jpeg|png|gif|bmp|webp)$", re.IGNORECASE), "type": "image"},
|
||||
{"rx": re.compile(r"\.(txt|nfo|md|json|yml|yaml|plexmatch)$", re.IGNORECASE), "type": "text"},
|
||||
{"rx": re.compile(r"\.(nfo|json|jpg|torrent|\.info\.json)$", re.IGNORECASE), "type": "metadata"},
|
||||
]
|
||||
|
||||
|
||||
class StreamingError(Exception):
|
||||
"""Raised when an error occurs during streaming."""
|
||||
|
|
@ -480,7 +489,7 @@ def validate_uuid(uuid_str: str, version: int = 4) -> bool:
|
|||
return False
|
||||
|
||||
|
||||
def get_sidecar_subtitles(file: pathlib.Path) -> list[dict]:
|
||||
def get_file_sidecar(file: pathlib.Path) -> list[dict]:
|
||||
"""
|
||||
Get sidecar files for the given file.
|
||||
|
||||
|
|
@ -491,26 +500,60 @@ def get_sidecar_subtitles(file: pathlib.Path) -> list[dict]:
|
|||
list: List of sidecar files.
|
||||
|
||||
"""
|
||||
files = []
|
||||
files = {}
|
||||
|
||||
for i, f in enumerate(file.parent.glob(f"{glob.escape(file.stem)}.*")):
|
||||
if f == file or f.is_file() is False or f.stem.startswith("."):
|
||||
continue
|
||||
|
||||
if f.suffix not in ALLOWED_SUBS_EXTENSIONS:
|
||||
continue
|
||||
|
||||
if f.stat().st_size < 1:
|
||||
continue
|
||||
|
||||
lg = re.search(r"\.(?P<lang>\w{2,3})\.\w{3}$", f.name)
|
||||
lang = lg.groupdict().get("lang") if lg else "und"
|
||||
content_type = "Unknown"
|
||||
for pattern in FILES_TYPE:
|
||||
if pattern["rx"].search(f.name):
|
||||
content_type = pattern["type"]
|
||||
break
|
||||
|
||||
files.append({"file": f, "lang": lang, "name": f"{f.suffix[1:].upper()} ({i}) - {lang}"})
|
||||
if content_type == "subtitle":
|
||||
if f.suffix not in ALLOWED_SUBS_EXTENSIONS:
|
||||
continue
|
||||
lg = re.search(r"\.(?P<lang>\w{2,3})\.\w{3}$", f.name)
|
||||
lang = lg.groupdict().get("lang") if lg else "und"
|
||||
content = {"file": f, "lang": lang, "name": f"{f.suffix[1:].upper()} ({i}) - {lang}"}
|
||||
else:
|
||||
content = {"file": f}
|
||||
|
||||
if content_type not in files:
|
||||
files[content_type] = []
|
||||
|
||||
files[content_type].append(content)
|
||||
|
||||
images = get_possible_images(str(file.parent))
|
||||
if len(images) > 0:
|
||||
if "image" not in files:
|
||||
files["image"] = []
|
||||
|
||||
files["image"].extend(images)
|
||||
|
||||
return files
|
||||
|
||||
|
||||
@lru_cache(maxsize=512)
|
||||
def get_possible_images(dir: str) -> list[dict]:
|
||||
images = []
|
||||
|
||||
path_loc = pathlib.Path(dir, "test.jpg")
|
||||
|
||||
for filename in ["poster", "thumbnail", "artwork", "cover", "fanart"]:
|
||||
for ext in [".jpg", ".jpeg", ".png", ".webp"]:
|
||||
f = path_loc.with_stem(filename).with_suffix(ext)
|
||||
if f.exists():
|
||||
images.append({"file": f})
|
||||
|
||||
return images
|
||||
|
||||
|
||||
def get_mime_type(metadata: dict, file_path: pathlib.Path) -> str:
|
||||
"""
|
||||
Determine the correct MIME type for a video file based on ffprobe metadata.
|
||||
|
|
@ -738,13 +781,28 @@ def get_files(base_path: str, dir: str | None = None):
|
|||
if file.name.startswith(".") or file.name.startswith("_"):
|
||||
continue
|
||||
|
||||
content_type = None
|
||||
|
||||
for pattern in FILES_TYPE:
|
||||
if pattern["rx"].search(file.name):
|
||||
content_type = pattern["type"]
|
||||
break
|
||||
|
||||
if not content_type and file.is_dir():
|
||||
content_type = "dir"
|
||||
|
||||
if not content_type:
|
||||
content_type = "download"
|
||||
|
||||
stat = file.stat()
|
||||
contents.append(
|
||||
{
|
||||
"type": "file" if file.is_file() else "dir",
|
||||
"content_type": content_type,
|
||||
"name": file.name,
|
||||
"path": str(file).replace(base_path, "").strip("/"),
|
||||
"size": stat.st_size,
|
||||
"mime": get_mime_type({}, file) if file.is_file() else "directory",
|
||||
"mtime": datetime.datetime.fromtimestamp(stat.st_mtime, tz=datetime.UTC).isoformat(),
|
||||
"ctime": datetime.datetime.fromtimestamp(stat.st_ctime, tz=datetime.UTC).isoformat(),
|
||||
"is_dir": file.is_dir(),
|
||||
|
|
|
|||
|
|
@ -281,3 +281,8 @@ hr {
|
|||
background-repeat: no-repeat;
|
||||
background-blend-mode: darken;
|
||||
}
|
||||
|
||||
.is-unbounded-model {
|
||||
max-height: unset !important;
|
||||
width: unset !important;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,16 @@
|
|||
<style scoped>
|
||||
.embed-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 80vh;
|
||||
width: 80vw;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div class="content">
|
||||
<h1 class="has-text-white">Not downloaded yet.</h1>
|
||||
<figure class="image is-16by9">
|
||||
<iframe class="has-ratio" :src="url" frameborder="0" allowfullscreen />
|
||||
</figure>
|
||||
<iframe class="embed-content" :src="url" frameborder="0" allowfullscreen />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="modal is-active">
|
||||
<div class="modal is-active" v-if="false === externalModel">
|
||||
<div class="modal-background" @click="closeModal"></div>
|
||||
<div class="modal-content" style="width:60vw;">
|
||||
<div style="font-size:30vh; width: 99%" class="has-text-centered" v-if="isLoading">
|
||||
|
|
@ -20,6 +20,17 @@
|
|||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="closeModal"></button>
|
||||
</div>
|
||||
<div style="width:70vw; height: 80vh;" v-else>
|
||||
<div class="p-0 m-0" style="position: relative">
|
||||
<div class="content" style="white-space: pre;">
|
||||
<code class="p-4 is-block" v-text="data" />
|
||||
<button class="button is-small m-4" @click="() => copyText(JSON.stringify(data, null, 4))"
|
||||
style="position: absolute; top:0; right:0;">
|
||||
<span class="icon"><i class="fas fa-copy"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -34,7 +45,18 @@ const toast = useToast()
|
|||
const props = defineProps({
|
||||
link: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
required: false,
|
||||
},
|
||||
useUrl: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
externalModel: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
||||
|
|
@ -49,12 +71,19 @@ const eventFunc = e => {
|
|||
onMounted(async () => {
|
||||
window.addEventListener('keydown', eventFunc)
|
||||
|
||||
const url = '/api/yt-dlp/url/info?url=' + encodePath(props.link)
|
||||
const url = props.useUrl ? props.link : '/api/yt-dlp/url/info?url=' + encodePath(props.link)
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
const response = await request(url, { credentials: 'include' });
|
||||
data.value = await response.json()
|
||||
const response = await request(url, { credentials: 'include' })
|
||||
const body = await response.text()
|
||||
|
||||
try {
|
||||
data.value = JSON.parse(body)
|
||||
} catch (e) {
|
||||
data.value = body
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`Error: ${e.message}`)
|
||||
|
|
|
|||
|
|
@ -350,7 +350,7 @@
|
|||
|
||||
<div class="modal is-active" v-if="video_item">
|
||||
<div class="modal-background" @click="closeVideo"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content is-unbounded-model">
|
||||
<VideoPlayer type="default" :isMuted="false" autoplay="true" :isControls="true" :item="video_item"
|
||||
class="is-fullwidth" @closeModel="closeVideo" />
|
||||
</div>
|
||||
|
|
@ -359,7 +359,7 @@
|
|||
|
||||
<div class="modal is-active" v-if="embed_url">
|
||||
<div class="modal-background" @click="embed_url = ''"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content is-unbounded-model">
|
||||
<EmbedPlayer :url="embed_url" @closeModel="embed_url = ''" />
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="embed_url = ''"></button>
|
||||
|
|
@ -370,7 +370,7 @@
|
|||
<script setup>
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
import { makeDownload, formatBytes, ucFirst } from '~/utils/index'
|
||||
import { makeDownload, formatBytes } from '~/utils/index'
|
||||
import toast from '~/plugins/toast'
|
||||
import { isEmbedable, getEmbedable } from '~/utils/embedable'
|
||||
|
||||
|
|
|
|||
66
ui/components/ImageView.vue
Normal file
66
ui/components/ImageView.vue
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<style>
|
||||
.is-image {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<template>
|
||||
<div>
|
||||
<div style="font-size:30vh; width: 99%" class="has-text-centered" v-if="isLoading">
|
||||
<i class="fas fa-circle-notch fa-spin"></i>
|
||||
</div>
|
||||
<div v-else>
|
||||
<img :src="image" class="is-image">
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { request } from '~/utils/index'
|
||||
|
||||
const emitter = defineEmits(['closeModel'])
|
||||
const isLoading = ref(false)
|
||||
const data = ref({})
|
||||
const toast = useToast()
|
||||
const image = ref('')
|
||||
|
||||
const props = defineProps({
|
||||
link: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const eventFunc = e => {
|
||||
if ('Escape' === e.key) {
|
||||
emitter('closeModel')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
window.addEventListener('keydown', eventFunc)
|
||||
|
||||
|
||||
const url = props.link.startsWith('/') ? props.link : '/api/thumbnail?url=' + encodePath(props.link)
|
||||
|
||||
try {
|
||||
isLoading.value = true
|
||||
|
||||
const imgRequest = await request(url, { credentials: 'include' })
|
||||
if (200 !== imgRequest.status) {
|
||||
return
|
||||
}
|
||||
|
||||
image.value = URL.createObjectURL(await imgRequest.blob())
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
toast.error(`Error: ${e.message}`)
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => window.removeEventListener('keydown', eventFunc))
|
||||
</script>
|
||||
|
|
@ -137,7 +137,7 @@
|
|||
</header>
|
||||
<div v-if="false === hideThumbnail" class="card-image">
|
||||
<figure class="image is-3by1">
|
||||
<span v-if="isEmbedable(item.url)" @click="() => embed_url = getEmbedable(item.url)" class="play-overlay">
|
||||
<span v-if="isEmbedable(item.url)" @click="embed_url = getEmbedable(item.url)" class="play-overlay">
|
||||
<div class="play-icon embed-icon"></div>
|
||||
<img @load="e => pImg(e)" :src="'/api/thumbnail?url=' + encodePath(item.extras.thumbnail)"
|
||||
v-if="item.extras?.thumbnail" />
|
||||
|
|
@ -224,7 +224,7 @@
|
|||
|
||||
<div class="modal is-active" v-if="embed_url">
|
||||
<div class="modal-background" @click="embed_url = ''"></div>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content is-unbounded-model">
|
||||
<EmbedPlayer :url="embed_url" @closeModel="embed_url = ''" />
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="embed_url = ''"></button>
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
}
|
||||
|
||||
</style>
|
||||
<template>
|
||||
<div v-if="infoLoaded">
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
<source v-for="source in sources" :key="source.src" :src="source.src" @error="source.onerror"
|
||||
:type="source.type" />
|
||||
<track v-for="(track, i) in tracks" :key="track.file" :kind="track.kind" :label="track.label"
|
||||
:srclang="track.lang" :src="track.file" :default="notFirefox && i === 0" />
|
||||
:srclang="track.lang" :src="track.file" :default="notFirefox && 0 === i" />
|
||||
</video>
|
||||
</div>
|
||||
<div style="text-align: center;" v-else>
|
||||
|
|
@ -55,16 +55,16 @@ const volume = useStorage('player_volume', 1)
|
|||
const notFirefox = !navigator.userAgent.toLowerCase().includes('firefox')
|
||||
const infoLoaded = ref(false)
|
||||
|
||||
let hls = null;
|
||||
let hls = null
|
||||
|
||||
const eventFunc = e => {
|
||||
if (e.key === 'Escape') {
|
||||
if ('Escape' === e.key) {
|
||||
emitter('closeModel')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const req = await request(makeDownload(config, props.item, 'api/file/info'));
|
||||
const req = await request(makeDownload(config, props.item, 'api/file/info'))
|
||||
|
||||
const response = await req.json()
|
||||
|
||||
|
|
@ -74,17 +74,14 @@ onMounted(async () => {
|
|||
return
|
||||
}
|
||||
|
||||
infoLoaded.value = true
|
||||
await nextTick()
|
||||
|
||||
video.value.volume = volume.value
|
||||
|
||||
video.value.addEventListener('volumechange', () => {
|
||||
volume.value = video.value.volume
|
||||
})
|
||||
|
||||
if (props.item.extras?.thumbnail) {
|
||||
thumbnail.value = '/api/thumbnail?url=' + encodePath(props.item.extras.thumbnail)
|
||||
} else {
|
||||
if (response?.sidecar?.image && response.sidecar.image.length > 0) {
|
||||
thumbnail.value = makeDownload(config, { "filename": response.sidecar.image[0]['file'] })
|
||||
}
|
||||
}
|
||||
|
||||
// -- check if mimetype is video/mp4 and device is apple
|
||||
|
|
@ -115,12 +112,17 @@ onMounted(async () => {
|
|||
if (props.item?.title) {
|
||||
title.value = props.item.title
|
||||
}
|
||||
else {
|
||||
if (response?.title) {
|
||||
title.value = response.title
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.item.extras?.is_video && props.item.extras?.is_audio) {
|
||||
isAudio.value = true
|
||||
}
|
||||
|
||||
response.sidecar.forEach((cap, id) => {
|
||||
response?.sidecar?.subtitle?.forEach((cap, id) => {
|
||||
tracks.value.push({
|
||||
kind: "captions",
|
||||
label: cap.name,
|
||||
|
|
@ -130,9 +132,12 @@ onMounted(async () => {
|
|||
})
|
||||
|
||||
if (isApple) {
|
||||
document.documentElement.style.setProperty('--webkit-text-track-display', 'block');
|
||||
document.documentElement.style.setProperty('--webkit-text-track-display', 'block')
|
||||
}
|
||||
|
||||
infoLoaded.value = true
|
||||
await nextTick()
|
||||
|
||||
prepareVideoPlayer()
|
||||
window.addEventListener('keydown', eventFunc)
|
||||
})
|
||||
|
|
@ -162,7 +167,7 @@ const prepareVideoPlayer = () => {
|
|||
|
||||
let mediaMetadata = {
|
||||
title: title.value,
|
||||
};
|
||||
}
|
||||
|
||||
if (thumbnail.value) {
|
||||
mediaMetadata.artwork = [{ src: thumbnail.value, sizes: '1920x1080', type: 'image/jpeg' }]
|
||||
|
|
@ -172,18 +177,24 @@ const prepareVideoPlayer = () => {
|
|||
mediaMetadata.artist = artist.value
|
||||
}
|
||||
|
||||
navigator.mediaSession.metadata = new MediaMetadata(mediaMetadata);
|
||||
navigator.mediaSession.metadata = new MediaMetadata(mediaMetadata)
|
||||
if (title.value) {
|
||||
window.document.title = `YTPTube - Playing: ${title.value}`
|
||||
}
|
||||
|
||||
video.value.volume = volume.value
|
||||
|
||||
video.value.addEventListener('volumechange', () => {
|
||||
volume.value = video.value.volume
|
||||
})
|
||||
}
|
||||
|
||||
const src_error = () => {
|
||||
if (hls) {
|
||||
return
|
||||
}
|
||||
console.warn('Direct play failed, trying HLS.');
|
||||
attach_hls(makeDownload(config, props.item, 'm3u8'));
|
||||
console.warn('Direct play failed, trying HLS.')
|
||||
attach_hls(makeDownload(config, props.item, 'm3u8'))
|
||||
}
|
||||
|
||||
const attach_hls = link => {
|
||||
|
|
@ -193,7 +204,7 @@ const attach_hls = link => {
|
|||
lowLatencyMode: true,
|
||||
backBufferLength: 120,
|
||||
fragLoadingTimeOut: 200000,
|
||||
});
|
||||
})
|
||||
|
||||
hls.loadSource(link)
|
||||
hls.attachMedia(video.value)
|
||||
|
|
|
|||
|
|
@ -52,15 +52,30 @@
|
|||
<td class="has-text-centered is-vcentered">
|
||||
<span class="icon">
|
||||
<i class="fas fa-solid"
|
||||
:class="{ 'fa-file': item.type === 'file', 'fa-folder': item.type === 'dir' }" />
|
||||
:class="{ 'fa-file': 'file' === item.type, 'fa-folder': 'dir' === item.type }" />
|
||||
</span>
|
||||
</td>
|
||||
<td class="is-text-overflow is-vcentered" v-tooltip="item.name">
|
||||
<a :href="`/browser/${item.path}`" v-if="item.type === 'dir'"
|
||||
@click.prevent="reloadContent(item.path)">
|
||||
{{ item.name }}
|
||||
</a>
|
||||
<a :href="makeDownload({}, { filename: item.path, folder: '' })" v-else>{{ item.name }}</a>
|
||||
<td class="is-text-overflow is-vcentered">
|
||||
<div class="field is-grouped">
|
||||
<div class="control is-text-overflow is-expanded" v-tooltip="item.name">
|
||||
<a :href="`/browser/${item.path}`" v-if="'dir' === item.type"
|
||||
@click.prevent="reloadContent(item.path)">
|
||||
{{ item.name }}
|
||||
</a>
|
||||
<a :href="makeDownload({}, { filename: item.path, folder: '' })"
|
||||
@click.prevent="handleClick(item)" v-else>
|
||||
{{ item.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="control" v-if="'file' === item.type">
|
||||
<span class="icon">
|
||||
<a :href="makeDownload({}, { filename: item.path, folder: '' })"
|
||||
:download="item.name.split('/').reverse()[0]">
|
||||
<i class="fas fa-download" />
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="has-text-centered is-text-overflow is-unselectable">
|
||||
{{ 'file' === item.type ? formatBytes(item.size) : 'Dir' }}
|
||||
|
|
@ -90,12 +105,24 @@
|
|||
</Message>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal is-active" v-if="model_item">
|
||||
<div class="modal-background" @click="closeModel"></div>
|
||||
<div class="modal-content is-unbounded-model">
|
||||
<VideoPlayer type="default" :isMuted="false" autoplay="true" :isControls="true" :item="model_item"
|
||||
class="is-fullwidth" @closeModel="closeModel" v-if="'video' === model_item.type" />
|
||||
<GetInfo :link="model_item.filename" :useUrl="true" @closeModel="closeModel" :externalModel="true"
|
||||
v-if="'text' === model_item.type" />
|
||||
<ImageView :link="model_item.filename" @closeModel="closeModel" v-if="'image' === model_item.type" />
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" @click="closeModel"></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { request } from '~/utils/index'
|
||||
import moment from 'moment'
|
||||
import { useStorage } from '@vueuse/core'
|
||||
|
||||
const route = useRoute()
|
||||
const toast = useToast()
|
||||
|
|
@ -107,6 +134,12 @@ const initialLoad = ref(true)
|
|||
const items = ref([])
|
||||
const path = ref(`/${route.params.slug?.length > 0 ? route.params.slug?.join('/') : ''}`)
|
||||
|
||||
const bg_enable = useStorage('random_bg', true)
|
||||
const bg_opacity = useStorage('random_bg_opacity', 0.85)
|
||||
|
||||
const model_item = ref()
|
||||
const closeModel = () => model_item.value = null
|
||||
|
||||
watch(() => config.app.basic_mode, async () => {
|
||||
if (!config.app.basic_mode) {
|
||||
return
|
||||
|
|
@ -128,6 +161,45 @@ watch(() => socket.isConnected, async () => {
|
|||
}
|
||||
})
|
||||
|
||||
const handleClick = item => {
|
||||
if ('video' === item.content_type) {
|
||||
model_item.value = {
|
||||
"type": 'video',
|
||||
"filename": item.path,
|
||||
"folder": "",
|
||||
"extras": {},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (['text', 'subtitle', 'metadata'].includes(item.content_type)) {
|
||||
model_item.value = {
|
||||
"type": 'text',
|
||||
"filename": makeDownload(config, { "filename": item.path }),
|
||||
"folder": "",
|
||||
"extras": {},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ('image' === item.content_type) {
|
||||
model_item.value = {
|
||||
"type": 'image',
|
||||
"filename": makeDownload(config, { "filename": item.path }),
|
||||
"folder": "",
|
||||
"extras": {},
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if ('dir' === item.content_type) {
|
||||
reloadContent(item.path)
|
||||
return
|
||||
}
|
||||
|
||||
window.location = makeDownload(config, { "filename": item.path, "folder": "", "extras": {} })
|
||||
}
|
||||
|
||||
const reloadContent = async (dir = '/', fromMounted = false) => {
|
||||
try {
|
||||
isLoading.value = true
|
||||
|
|
@ -230,4 +302,12 @@ const makeBreadCrumb = path => {
|
|||
return links
|
||||
}
|
||||
|
||||
|
||||
watch(model_item, v => {
|
||||
if (!bg_enable.value) {
|
||||
return
|
||||
}
|
||||
|
||||
document.querySelector('body').setAttribute("style", `opacity: ${v ? 1 : bg_opacity.value}`)
|
||||
})
|
||||
</script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue