mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-26 21:13:08 +02:00
upd: split mod tracker into 2 components
SkModPlayer now just contains the pattern display, with controls being moved to the new SkMediaModule component
This commit is contained in:
parent
d03ad221a1
commit
8702c1dd24
6 changed files with 346 additions and 294 deletions
|
@ -1085,6 +1085,7 @@ showClipButtonInNoteFooter: "Add \"Clip\" to note action menu"
|
||||||
reactionsDisplaySize: "Reaction display size"
|
reactionsDisplaySize: "Reaction display size"
|
||||||
limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size."
|
limitWidthOfReaction: "Limits the maximum width of reactions and display them in reduced size."
|
||||||
noteIdOrUrl: "Note ID or URL"
|
noteIdOrUrl: "Note ID or URL"
|
||||||
|
module: "Module"
|
||||||
video: "Video"
|
video: "Video"
|
||||||
videos: "Videos"
|
videos: "Videos"
|
||||||
dataSaver: "Data Saver"
|
dataSaver: "Data Saver"
|
||||||
|
|
1
locales/index.d.ts
vendored
1
locales/index.d.ts
vendored
|
@ -1095,6 +1095,7 @@ export interface Locale {
|
||||||
"reactionsDisplaySize": string;
|
"reactionsDisplaySize": string;
|
||||||
"limitWidthOfReaction": string;
|
"limitWidthOfReaction": string;
|
||||||
"noteIdOrUrl": string;
|
"noteIdOrUrl": string;
|
||||||
|
"module": string;
|
||||||
"video": string;
|
"video": string;
|
||||||
"videos": string;
|
"videos": string;
|
||||||
"audio": string;
|
"audio": string;
|
||||||
|
|
|
@ -1092,6 +1092,7 @@ showClipButtonInNoteFooter: "ノートのアクションにクリップを追加
|
||||||
reactionsDisplaySize: "リアクションの表示サイズ"
|
reactionsDisplaySize: "リアクションの表示サイズ"
|
||||||
limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
|
limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小して表示する"
|
||||||
noteIdOrUrl: "ノートIDまたはURL"
|
noteIdOrUrl: "ノートIDまたはURL"
|
||||||
|
module: "Module" # TODO: translate
|
||||||
video: "動画"
|
video: "動画"
|
||||||
videos: "動画"
|
videos: "動画"
|
||||||
audio: "音声"
|
audio: "音声"
|
||||||
|
|
|
@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template v-for="media in mediaList.filter(media => previewable(media))">
|
<template v-for="media in mediaList.filter(media => previewable(media))">
|
||||||
<XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/>
|
<XVideo v-if="media.type.startsWith('video')" :key="`video:${media.id}`" :class="$style.media" :video="media"/>
|
||||||
<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/>
|
<XImage v-else-if="media.type.startsWith('image')" :key="`image:${media.id}`" :class="$style.media" class="image" :data-id="media.id" :image="media" :raw="raw"/>
|
||||||
<XModPlayer v-else-if="isModule(media)" :key="media.id" :module="media"/>
|
<XModule v-else-if="isModule(media)" :key="`module:${media.id}`" :class="$style.media" :module="media"/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -37,7 +37,7 @@ import 'photoswipe/style.css';
|
||||||
import XBanner from '@/components/MkMediaBanner.vue';
|
import XBanner from '@/components/MkMediaBanner.vue';
|
||||||
import XImage from '@/components/MkMediaImage.vue';
|
import XImage from '@/components/MkMediaImage.vue';
|
||||||
import XVideo from '@/components/MkMediaVideo.vue';
|
import XVideo from '@/components/MkMediaVideo.vue';
|
||||||
import XModPlayer from '@/components/SkModPlayer.vue';
|
import XModule from '@/components/SkMediaModule.vue';
|
||||||
import * as os from '@/os.js';
|
import * as os from '@/os.js';
|
||||||
import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@/const.js';
|
import { FILE_TYPE_BROWSERSAFE, FILE_EXT_TRACKER_MODULES, FILE_TYPE_TRACKER_MODULES } from '@/const.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
|
|
245
packages/frontend/src/components/SkMediaModule.vue
Normal file
245
packages/frontend/src/components/SkMediaModule.vue
Normal file
|
@ -0,0 +1,245 @@
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: syuilo and other misskey contributors
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="hide" :class="[$style.hidden, (module.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" @click="hide = false">
|
||||||
|
<!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること -->
|
||||||
|
<div :class="$style.sensitive">
|
||||||
|
<b v-if="module.isSensitive" style="display: block;"><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.module}${module.size ? ' ' + bytes(module.size) : ''})` : '' }}</b>
|
||||||
|
<b v-else style="display: block;"><i class="ph-music-notes ph-bold ph-lg"></i> {{ defaultStore.state.dataSaver.media && module.size ? bytes(module.size) : i18n.ts.module }}</b>
|
||||||
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else :class="[$style.visible, (module.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]">
|
||||||
|
<SkModPlayer ref="moduleEl" :src="module.url"/>
|
||||||
|
<div v-if="moduleEl" :class="$style.controls">
|
||||||
|
<button v-if="!moduleEl.loading" @click="moduleEl.playPause()">
|
||||||
|
<i v-if="moduleEl.playing" class="ph-pause ph-bold ph-lg"></i>
|
||||||
|
<i v-else class="ph-play ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<MkLoading v-else :em="true"/>
|
||||||
|
<button @click="moduleEl.stop()">
|
||||||
|
<i class="ph-stop ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<button @click="moduleEl.toggleLoop()">
|
||||||
|
<i v-if="moduleEl.loop" class="ph-repeat ph-bold ph-lg"></i>
|
||||||
|
<i v-else class="ph-repeat-once ph-bold ph-lg"></i>
|
||||||
|
</button>
|
||||||
|
<input ref="progress" v-model="moduleEl.position" :class="$style.progress" type="range" min="0" :max="moduleEl.length" step="0.1" @mousedown="moduleEl.initSeek()" @mouseup="moduleEl.performSeek()"/>
|
||||||
|
<input v-model="moduleEl.volume" type="range" min="0" max="1" step="0.1"/>
|
||||||
|
<a :title="i18n.ts.download" :href="module.url" target="_blank">
|
||||||
|
<i class="ph-download ph-bold ph-lg"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<i class="ph-eye-slash ph-bold ph-lg" :class="$style.hide" @click="hide = true"></i>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, shallowRef, watch } from 'vue';
|
||||||
|
import * as Misskey from 'misskey-js';
|
||||||
|
import bytes from '@/filters/bytes.js';
|
||||||
|
import SkModPlayer from '@/components/SkModPlayer.vue';
|
||||||
|
import { defaultStore } from '@/store.js';
|
||||||
|
import { i18n } from '@/i18n.js';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
module: Misskey.entities.DriveFile;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.module.isSensitive && defaultStore.state.nsfw !== 'ignore'));
|
||||||
|
|
||||||
|
const moduleEl = shallowRef<typeof SkModPlayer>();
|
||||||
|
|
||||||
|
watch(moduleEl, () => {
|
||||||
|
if (moduleEl.value) {
|
||||||
|
moduleEl.value.volume.value = 0.3;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
.visible {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensitiveContainer {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
border-radius: inherit;
|
||||||
|
box-shadow: inset 0 0 0 4px var(--warn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background-color: black;
|
||||||
|
color: var(--accentLighten);
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: .5;
|
||||||
|
padding: 3px 6px;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background: #111;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sensitive {
|
||||||
|
display: table-cell;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--bg);
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
> * {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
> button, a {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--accent);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--fg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> input[type=range] {
|
||||||
|
height: 21px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 90px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 4px 8px;
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-fill-lower, &::-ms-fill-upper {
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
animate: 0.2s;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
border: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 14px;
|
||||||
|
border-radius: 0;
|
||||||
|
background: var(--accentLighten);
|
||||||
|
cursor: pointer;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
box-shadow: calc(-100vw - 14px) 0 0 100vw var(--accent);
|
||||||
|
clip-path: polygon(1px 0, 100% 0, 100% 100%, 1px 100%, 1px calc(50% + 10.5px), -100vw calc(50% + 10.5px), -100vw calc(50% - 10.5px), 0 calc(50% - 10.5px));
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
animate: 0.2s;
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-progress {
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
border: none;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 0;
|
||||||
|
width: 14px;
|
||||||
|
background: var(--accentLighten);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 0;
|
||||||
|
animate: 0.2s;
|
||||||
|
background: transparent;
|
||||||
|
border-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-fill-lower {
|
||||||
|
background: var(--accent);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-fill-upper {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1px solid var(--fg);
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-ms-thumb {
|
||||||
|
margin-top: 1px;
|
||||||
|
border: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 14px;
|
||||||
|
border-radius: 0;
|
||||||
|
background: var(--accentLighten);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.progress {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,67 +1,32 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="!available" :class="$style.disabled">
|
<div :class="$style.root">
|
||||||
<MkLoading v-if="fetching"/>
|
<div v-if="patternShow">
|
||||||
<MkError v-else-if="error" @retry="load()"/>
|
<div v-if="patData.length !== 0" ref="modPattern" :class="$style.pattern">
|
||||||
</div>
|
<span
|
||||||
<div v-else-if="hide" :class="$style.disabled" @click="toggleVisible()">
|
v-for="(row, i) in patData[currentPattern]"
|
||||||
<div>
|
ref="initRow"
|
||||||
<b><i class="ph-eye ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}</b>
|
:key="i"
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
:class="[$style.row, { [$style.active]: isRowActive(i) }]"
|
||||||
</div>
|
>
|
||||||
</div>
|
<span :class="{ [$style.quarter]: i % 4 === 0 }">{{ indexText(i) }}</span>
|
||||||
|
<span :class="$style.column">{{ getRowText(row) }}</span>
|
||||||
<div v-else :class="$style.enabled">
|
</span>
|
||||||
<div :class="$style.patternDisplay">
|
|
||||||
<div v-if="patternShow">
|
|
||||||
<div v-if="patData.length !== 0" ref="modPattern" :class="$style.pattern">
|
|
||||||
<span
|
|
||||||
v-for="(row, i) in patData[currentPattern]"
|
|
||||||
ref="initRow"
|
|
||||||
:key="i"
|
|
||||||
:class="[$style.row, { [$style.active]: isRowActive(i) }]"
|
|
||||||
>
|
|
||||||
<span :class="{ [$style.colQuarter]: i % 4 === 0 }">{{ indexText(i) }}</span>
|
|
||||||
<span :class="$style.inner">{{ getRowText(row) }}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<MkLoading v-else/>
|
|
||||||
</div>
|
|
||||||
<div v-else :class="$style.pattern" @click="showPattern()">
|
|
||||||
<p>{{ i18n.ts.patternHidden }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<MkLoading v-else/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="$style.controls">
|
<div v-else :class="$style.pattern" @click="showPattern()">
|
||||||
<button v-if="!loading" :class="$style.play" @click="playPause()">
|
<p>{{ i18n.ts.patternHidden }}</p>
|
||||||
<i v-if="playing" class="ph-pause ph-bold ph-lg"></i>
|
|
||||||
<i v-else class="ph-play ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<MkLoading v-else :em="true"/>
|
|
||||||
<button :class="$style.stop" @click="stop()">
|
|
||||||
<i class="ph-stop ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<button :class="$style.loop" @click="toggleLoop()">
|
|
||||||
<i v-if="loop" class="ph-repeat ph-bold ph-lg"></i>
|
|
||||||
<i v-else class="ph-repeat-once ph-bold ph-lg"></i>
|
|
||||||
</button>
|
|
||||||
<input ref="progress" v-model="position" :class="$style.progress" type="range" min="0" :max="length" step="0.1" @mousedown="initSeek()" @mouseup="performSeek()"/>
|
|
||||||
<input v-model="player.context.gain.value" type="range" min="0" max="1" step="0.1"/>
|
|
||||||
<a :class="$style.download" :title="i18n.ts.download" :href="module.url" target="_blank">
|
|
||||||
<i class="ph-download ph-bold ph-lg"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
<i :class="$style.hide" class="ph-eye-slash ph-bold ph-lg" @click="toggleVisible()"></i>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, shallowRef, nextTick, onDeactivated, onMounted } from 'vue';
|
import { computed, ref, shallowRef, onDeactivated, onMounted, nextTick, watch } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
|
||||||
import { ChiptuneJsPlayer } from '@/scripts/chiptune2.js';
|
import { ChiptuneJsPlayer } from '@/scripts/chiptune2.js';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
module: Misskey.entities.DriveFile
|
src: string
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interface ModRow {
|
interface ModRow {
|
||||||
|
@ -74,11 +39,9 @@ interface ModRow {
|
||||||
|
|
||||||
const available = ref(false);
|
const available = ref(false);
|
||||||
const initRow = shallowRef<HTMLSpanElement>();
|
const initRow = shallowRef<HTMLSpanElement>();
|
||||||
const hide = ref(defaultStore.state.nsfw === 'force' ? true : props.module.isSensitive && defaultStore.state.nsfw !== 'ignore');
|
|
||||||
const patternShow = ref(false);
|
const patternShow = ref(false);
|
||||||
const playing = ref(false);
|
const playing = ref(false);
|
||||||
const modPattern = ref<HTMLDivElement>();
|
const modPattern = ref<HTMLDivElement>();
|
||||||
const progress = ref<HTMLProgressElement>();
|
|
||||||
const position = ref(0);
|
const position = ref(0);
|
||||||
const player = shallowRef(new ChiptuneJsPlayer());
|
const player = shallowRef(new ChiptuneJsPlayer());
|
||||||
const patData = shallowRef<readonly ModRow[][]>([]);
|
const patData = shallowRef<readonly ModRow[][]>([]);
|
||||||
|
@ -89,15 +52,34 @@ const loop = ref(false);
|
||||||
const fetching = ref(true);
|
const fetching = ref(true);
|
||||||
const error = ref(false);
|
const error = ref(false);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
const currentRow = ref(0);
|
||||||
|
|
||||||
|
const volume = computed({
|
||||||
|
get() {
|
||||||
|
return player.value.context.gain.value;
|
||||||
|
},
|
||||||
|
|
||||||
|
set(value) {
|
||||||
|
player.value.context.gain.value = value;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
let currentRow = 0;
|
|
||||||
let rowHeight = 0;
|
|
||||||
let buffer: ArrayBuffer|null = null;
|
let buffer: ArrayBuffer|null = null;
|
||||||
|
let rowHeight = 0;
|
||||||
let isSeeking = false;
|
let isSeeking = false;
|
||||||
|
|
||||||
|
watch(currentRow, (row) => {
|
||||||
|
if (!modPattern.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rowHeight === 0 && initRow.value) rowHeight = initRow.value[0].getBoundingClientRect().height;
|
||||||
|
modPattern.value.scrollTop = row * rowHeight;
|
||||||
|
});
|
||||||
|
|
||||||
async function load() {
|
async function load() {
|
||||||
try {
|
try {
|
||||||
buffer = await player.value.load(props.module.url);
|
buffer = await player.value.load(props.src);
|
||||||
available.value = true;
|
available.value = true;
|
||||||
error.value = false;
|
error.value = false;
|
||||||
fetching.value = false;
|
fetching.value = false;
|
||||||
|
@ -139,7 +121,7 @@ function playPause() {
|
||||||
}
|
}
|
||||||
|
|
||||||
player.value.addHandler('onRowChange', (i: { index: number }) => {
|
player.value.addHandler('onRowChange', (i: { index: number }) => {
|
||||||
currentRow = i.index;
|
currentRow.value = i.index;
|
||||||
currentPattern.value = player.value.getPattern();
|
currentPattern.value = player.value.getPattern();
|
||||||
length.value = player.value.duration();
|
length.value = player.value.duration();
|
||||||
if (!isSeeking) {
|
if (!isSeeking) {
|
||||||
|
@ -183,7 +165,7 @@ async function stop(noDisplayUpdate = false) {
|
||||||
}
|
}
|
||||||
player.value.stop();
|
player.value.stop();
|
||||||
position.value = 0;
|
position.value = 0;
|
||||||
currentRow = 0;
|
currentRow.value = 0;
|
||||||
player.value.clearHandlers();
|
player.value.clearHandlers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,21 +184,8 @@ function performSeek() {
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleVisible() {
|
|
||||||
hide.value = !hide.value;
|
|
||||||
nextTick(() => { stop(hide.value); });
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRowActive(i: number) {
|
function isRowActive(i: number) {
|
||||||
if (i !== currentRow) {
|
return i === currentRow.value;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modPattern.value) {
|
|
||||||
if (rowHeight === 0 && initRow.value) rowHeight = initRow.value[0].getBoundingClientRect().height;
|
|
||||||
modPattern.value.scrollTop = currentRow * rowHeight;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexText(i: number) {
|
function indexText(i: number) {
|
||||||
|
@ -254,8 +223,6 @@ function getRow(pattern: number, rowOffset: number): ModRow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function display(reset = false) {
|
function display(reset = false) {
|
||||||
if (!patternShow.value) return;
|
|
||||||
|
|
||||||
if (reset) {
|
if (reset) {
|
||||||
currentPattern.value = player.value.getPattern();
|
currentPattern.value = player.value.getPattern();
|
||||||
}
|
}
|
||||||
|
@ -286,230 +253,67 @@ function display(reset = false) {
|
||||||
onDeactivated(() => {
|
onDeactivated(() => {
|
||||||
stop();
|
stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
initSeek,
|
||||||
|
performSeek,
|
||||||
|
playPause,
|
||||||
|
stop,
|
||||||
|
toggleLoop,
|
||||||
|
length,
|
||||||
|
loading,
|
||||||
|
loop,
|
||||||
|
playing,
|
||||||
|
position,
|
||||||
|
volume,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
.hide {
|
.root {
|
||||||
border-radius: var(--radius-sm) !important;
|
width: 100%;
|
||||||
background-color: black !important;
|
height: 100%;
|
||||||
color: var(--accentLighten) !important;
|
|
||||||
font-size: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.enabled {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
display: flex;
|
color: white;
|
||||||
flex-direction: column;
|
background-color: black;
|
||||||
|
text-align: center;
|
||||||
> i {
|
font: 12px monospace;
|
||||||
display: block;
|
white-space: pre;
|
||||||
position: absolute;
|
user-select: none;
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
background-color: var(--fg);
|
|
||||||
color: var(--accentLighten);
|
|
||||||
font-size: 14px;
|
|
||||||
opacity: .5;
|
|
||||||
padding: 3px 6px;
|
|
||||||
text-align: center;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 12px;
|
|
||||||
right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .patternDisplay {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
color: white;
|
|
||||||
background-color: black;
|
|
||||||
text-align: center;
|
|
||||||
font: 12px monospace;
|
|
||||||
white-space: pre;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
.pattern {
|
|
||||||
display: grid;
|
|
||||||
overflow-y: hidden;
|
|
||||||
height: 0;
|
|
||||||
padding-top: calc((56.25% - 48px) / 2);
|
|
||||||
padding-bottom: calc((56.25% - 48px) / 2);
|
|
||||||
content-visibility: auto;
|
|
||||||
|
|
||||||
.row {
|
|
||||||
opacity: 0.5;
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .colQuarter {
|
|
||||||
color: var(--badge);
|
|
||||||
}
|
|
||||||
|
|
||||||
> .inner {
|
|
||||||
background: repeating-linear-gradient(
|
|
||||||
to right,
|
|
||||||
var(--fg) 0 4ch,
|
|
||||||
var(--codeBoolean) 4ch 6ch,
|
|
||||||
var(--codeNumber) 6ch 9ch,
|
|
||||||
var(--codeString) 9ch 10ch,
|
|
||||||
var(--error) 10ch 12ch
|
|
||||||
);
|
|
||||||
background-clip: text;
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> .controls {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
background-color: var(--bg);
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
> * {
|
|
||||||
padding: 4px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
> button, a {
|
|
||||||
border: none;
|
|
||||||
background-color: transparent;
|
|
||||||
color: var(--accent);
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--fg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
> input[type=range] {
|
|
||||||
height: 21px;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
width: 90px;
|
|
||||||
padding: 0;
|
|
||||||
margin: 4px 8px;
|
|
||||||
overflow-x: hidden;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
&::-webkit-slider-runnable-track {
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-fill-lower, &::-ms-fill-upper {
|
|
||||||
background: var(--bg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-slider-runnable-track {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0;
|
|
||||||
animate: 0.2s;
|
|
||||||
background: var(--bg);
|
|
||||||
border: 1px solid var(--fg);
|
|
||||||
overflow-x: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-webkit-slider-thumb {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 14px;
|
|
||||||
border-radius: 0;
|
|
||||||
background: var(--accentLighten);
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
box-shadow: calc(-100vw - 14px) 0 0 100vw var(--accent);
|
|
||||||
clip-path: polygon(1px 0, 100% 0, 100% 100%, 1px 100%, 1px calc(50% + 10.5px), -100vw calc(50% + 10.5px), -100vw calc(50% - 10.5px), 0 calc(50% - 10.5px));
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-track {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0;
|
|
||||||
animate: 0.2s;
|
|
||||||
background: var(--bg);
|
|
||||||
border: 1px solid var(--fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-progress {
|
|
||||||
cursor: pointer;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-range-thumb {
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 0;
|
|
||||||
width: 14px;
|
|
||||||
background: var(--accentLighten);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-track {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 0;
|
|
||||||
animate: 0.2s;
|
|
||||||
background: transparent;
|
|
||||||
border-color: transparent;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-fill-lower {
|
|
||||||
background: var(--accent);
|
|
||||||
border: 1px solid var(--fg);
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-fill-upper {
|
|
||||||
background: var(--bg);
|
|
||||||
border: 1px solid var(--fg);
|
|
||||||
border-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-ms-thumb {
|
|
||||||
margin-top: 1px;
|
|
||||||
border: none;
|
|
||||||
height: 100%;
|
|
||||||
width: 14px;
|
|
||||||
border-radius: 0;
|
|
||||||
background: var(--accentLighten);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.progress {
|
|
||||||
flex-grow: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled {
|
.pattern {
|
||||||
display: flex;
|
display: grid;
|
||||||
justify-content: center;
|
overflow-y: hidden;
|
||||||
align-items: center;
|
height: 0;
|
||||||
background: #111;
|
padding-top: calc((56.25% - 48px) / 2);
|
||||||
color: #fff;
|
padding-bottom: calc((56.25% - 48px) / 2);
|
||||||
|
content-visibility: auto;
|
||||||
|
}
|
||||||
|
|
||||||
> div {
|
.row {
|
||||||
display: table-cell;
|
opacity: 0.5;
|
||||||
text-align: center;
|
}
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
> b {
|
.active {
|
||||||
display: block;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.quarter {
|
||||||
|
color: var(--badge);
|
||||||
|
}
|
||||||
|
|
||||||
|
.column {
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
to right,
|
||||||
|
var(--fg) 0 4ch,
|
||||||
|
var(--codeBoolean) 4ch 6ch,
|
||||||
|
var(--codeNumber) 6ch 9ch,
|
||||||
|
var(--codeString) 9ch 10ch,
|
||||||
|
var(--error) 10ch 12ch
|
||||||
|
);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in a new issue