mirror of
https://activitypub.software/TransFem-org/Sharkey
synced 2024-12-20 15:30:09 +00:00
Dynamic loading, error handling, and types
This commit is contained in:
parent
8d1d09e42d
commit
24bc2cc19a
1 changed files with 117 additions and 57 deletions
|
@ -19,17 +19,27 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<span>Always be wary of arbitrary code execution!</span>
|
<span>Always be wary of arbitrary code execution!</span>
|
||||||
<span>{{ i18n.ts.clickToShow }}</span>
|
<span>{{ i18n.ts.clickToShow }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="ruffleError" class="player-hide">
|
||||||
|
<b><i class="ph-warning ph-bold ph-lg"></i> Flash Content Failed To Load:</b>
|
||||||
|
<code>{{ ruffleError }}</code>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="loadingStatus" class="player-hide">
|
||||||
|
<b>Flash Content Is Loading<MkEllipsis/></b>
|
||||||
|
<MkLoading/>
|
||||||
|
<p>{{ loadingStatus }}</p>
|
||||||
|
</div>
|
||||||
<div ref="ruffleContainer" class="container"></div>
|
<div ref="ruffleContainer" class="container"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<button class="play" @click="playPause()">
|
<button class="play" @click="playPause()">
|
||||||
<i v-if="player.value?.isPlaying" class="ph-pause ph-bold ph-lg"></i> <!-- FIXME: Broken? -->
|
<i v-if="player?.isPlaying" class="ph-pause ph-bold ph-lg"></i> <!-- FIXME: Broken? (Though less so than before) -->
|
||||||
<i v-else class="ph-play ph-bold ph-lg"></i>
|
<i v-else class="ph-play ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="stop" @click="stop()">
|
<button class="stop" @click="stop()">
|
||||||
<i class="ph-stop ph-bold ph-lg"></i>
|
<i class="ph-stop ph-bold ph-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<input v-model="player.volume" type="range" min="0" max="1" step="0.1"/>
|
<input v-if="player" v-model="player.volume" type="range" min="0" max="1" step="0.1"/>
|
||||||
|
<input v-else type="range" min="0" max="1" value="1" disabled/>
|
||||||
<a class="download" :title="i18n.ts.download" :href="flashFile.url" target="_blank">
|
<a class="download" :title="i18n.ts.download" :href="flashFile.url" target="_blank">
|
||||||
<i class="ph-download ph-bold ph-lg"></i>
|
<i class="ph-download ph-bold ph-lg"></i>
|
||||||
</a>
|
</a>
|
||||||
|
@ -43,12 +53,15 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, nextTick, computed, watch, onDeactivated, onMounted } from 'vue';
|
import { ref, computed, onDeactivated } from 'vue';
|
||||||
import * as Misskey from 'misskey-js';
|
import * as Misskey from 'misskey-js';
|
||||||
import packageInfo from '../../package.json';
|
import packageInfo from '../../package.json';
|
||||||
|
import MkEllipsis from './global/MkEllipsis.vue';
|
||||||
|
import MkLoading from './global/MkLoading.vue';
|
||||||
import { i18n } from '@/i18n.js';
|
import { i18n } from '@/i18n.js';
|
||||||
import { defaultStore } from '@/store.js';
|
import { defaultStore } from '@/store.js';
|
||||||
import '@ruffle-rs/ruffle';
|
import { PublicAPI, PublicAPILike } from '@/types/ruffle/setup'; // This gives us the types for window.RufflePlayer, etc via side effects
|
||||||
|
import { PlayerElement } from '@/types/ruffle/player';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
flashFile: Misskey.entities.DriveFile
|
flashFile: Misskey.entities.DriveFile
|
||||||
|
@ -60,14 +73,37 @@ const comment = computed(() => { return props.flashFile.comment ?? ''; });
|
||||||
let hide = ref((defaultStore.state.nsfw === 'force') ? true : isSensitive.value && (defaultStore.state.nsfw !== 'ignore'));
|
let hide = ref((defaultStore.state.nsfw === 'force') ? true : isSensitive.value && (defaultStore.state.nsfw !== 'ignore'));
|
||||||
let playerHide = ref(true);
|
let playerHide = ref(true);
|
||||||
let ruffleContainer = ref<HTMLDivElement>();
|
let ruffleContainer = ref<HTMLDivElement>();
|
||||||
|
let loadingStatus = ref<string | undefined>(undefined);
|
||||||
|
let player = ref<PlayerElement | undefined>(undefined);
|
||||||
|
let ruffleError = ref<string | undefined>(undefined);
|
||||||
|
|
||||||
declare global {
|
function dismissWarning() {
|
||||||
interface Window {
|
playerHide.value = false;
|
||||||
RufflePlayer: any;
|
loadRuffle().then(() => {
|
||||||
|
try {
|
||||||
|
createPlayer();
|
||||||
|
loadContent();
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error);
|
||||||
}
|
}
|
||||||
|
}).catch(handleError);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.RufflePlayer = window.RufflePlayer || {};
|
function handleError(error: unknown) {
|
||||||
|
if (error instanceof Error) ruffleError.value = error.stack;
|
||||||
|
else ruffleError.value = `${error}`; // Fallback for if something is horribly wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws if unpkg shits itself
|
||||||
|
*/
|
||||||
|
async function loadRuffle() {
|
||||||
|
if (window.RufflePlayer !== undefined) return;
|
||||||
|
loadingStatus.value = 'Loading Ruffle player';
|
||||||
|
await import('@ruffle-rs/ruffle'); // Assumption: this will throw if unpkg has a hiccup or something. If not, the next undefined check will catch it.
|
||||||
|
window.RufflePlayer = window.RufflePlayer as PublicAPILike | PublicAPI | undefined; // Assert unknown type due to side effects
|
||||||
|
if (window.RufflePlayer === undefined) throw Error('unpkg has shit itself, but not in an expected way (has unpkg permanently shut down? how close is the heat death of the universe?)');
|
||||||
|
|
||||||
window.RufflePlayer.config = {
|
window.RufflePlayer.config = {
|
||||||
// Options affecting the whole page
|
// Options affecting the whole page
|
||||||
'publicPath': `https://unpkg.com/@ruffle-rs/ruffle@${packageInfo.dependencies['@ruffle-rs/ruffle']}/`,
|
'publicPath': `https://unpkg.com/@ruffle-rs/ruffle@${packageInfo.dependencies['@ruffle-rs/ruffle']}/`,
|
||||||
|
@ -81,8 +117,8 @@ window.RufflePlayer.config = {
|
||||||
'wmode': 'window',
|
'wmode': 'window',
|
||||||
'letterbox': 'on',
|
'letterbox': 'on',
|
||||||
'warnOnUnsupportedContent': true,
|
'warnOnUnsupportedContent': true,
|
||||||
'contextMenu': 'off',
|
'contextMenu': 'off', // Prevent two overlapping context menus. Most of the stuff in this context menu is available in the controls below the player.
|
||||||
'showSwfDownload': false,
|
'showSwfDownload': false, // Handled by custom download button
|
||||||
'upgradeToHttps': window.location.protocol === 'https:',
|
'upgradeToHttps': window.location.protocol === 'https:',
|
||||||
'maxExecutionDuration': 15,
|
'maxExecutionDuration': 15,
|
||||||
'logLevel': 'error',
|
'logLevel': 'error',
|
||||||
|
@ -106,20 +142,36 @@ window.RufflePlayer.config = {
|
||||||
'playerRuntime': 'flashPlayer',
|
'playerRuntime': 'flashPlayer',
|
||||||
'allowFullscreen': false, // Handled by custom fullscreen button
|
'allowFullscreen': false, // Handled by custom fullscreen button
|
||||||
};
|
};
|
||||||
|
|
||||||
const ruffle = window.RufflePlayer.newest();
|
|
||||||
const player = ref(ruffle.createPlayer());
|
|
||||||
player.value.style.width = '100%';
|
|
||||||
player.value.style.height = '100%';
|
|
||||||
|
|
||||||
function dismissWarning() {
|
|
||||||
playerHide.value = false;
|
|
||||||
loadContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws If `ruffle.newest()` fails (impossible)
|
||||||
|
*/
|
||||||
|
function createPlayer() {
|
||||||
|
if (player.value !== undefined) return;
|
||||||
|
const ruffle = (() => {
|
||||||
|
const ruffleAPI = (window.RufflePlayer as PublicAPI).newest();
|
||||||
|
if (ruffleAPI === null) {
|
||||||
|
// This error exists because non-null assertions are forbidden, apparently.
|
||||||
|
throw Error('Ruffle could not get the latest Ruffle source. Since we\'re loading from unpkg this is genuinely impossible and you must\'ve done something incredibly cursed.');
|
||||||
|
}
|
||||||
|
return ruffleAPI;
|
||||||
|
})();
|
||||||
|
player.value = ruffle.createPlayer();
|
||||||
|
player.value.style.width = '100%';
|
||||||
|
player.value.style.height = '100%';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws If `player.value` is uninitialized.
|
||||||
|
*/
|
||||||
function loadContent() {
|
function loadContent() {
|
||||||
|
if (player.value === undefined) throw Error('Player is uninitialized.');
|
||||||
ruffleContainer.value?.appendChild(player.value);
|
ruffleContainer.value?.appendChild(player.value);
|
||||||
player.value.load(url.value).catch((error) => {
|
loadingStatus.value = 'Loading Flash file';
|
||||||
|
player.value.load(url.value).then(() => {
|
||||||
|
loadingStatus.value = undefined;
|
||||||
|
}).catch((error) => {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -129,6 +181,7 @@ function playPause() {
|
||||||
dismissWarning();
|
dismissWarning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (player.value === undefined) return; // Not done loading or something
|
||||||
if (player.value.isPlaying) {
|
if (player.value.isPlaying) {
|
||||||
player.value.pause();
|
player.value.pause();
|
||||||
} else {
|
} else {
|
||||||
|
@ -137,6 +190,7 @@ function playPause() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function fullscreen() {
|
function fullscreen() {
|
||||||
|
if (player.value === undefined) return; // Can't fullscreen an element that doesn't exist.
|
||||||
if (player.value.isFullscreen) {
|
if (player.value.isFullscreen) {
|
||||||
player.value.exitFullscreen();
|
player.value.exitFullscreen();
|
||||||
} else {
|
} else {
|
||||||
|
@ -145,6 +199,7 @@ function fullscreen() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
|
if (player.value === undefined) return; // FIXME: This doesn't stop the loading process. (Though, should this even be implemented?)
|
||||||
try {
|
try {
|
||||||
ruffleContainer.value?.removeChild(player.value);
|
ruffleContainer.value?.removeChild(player.value);
|
||||||
} catch {
|
} catch {
|
||||||
|
@ -176,6 +231,7 @@ onDeactivated(() => {
|
||||||
.height-hack {
|
.height-hack {
|
||||||
/* HACK: I'm too stupid to do this better apparently. Copy-pasted from MkMediaList.vue */
|
/* HACK: I'm too stupid to do this better apparently. Copy-pasted from MkMediaList.vue */
|
||||||
/* height: 100% doesn't work */
|
/* height: 100% doesn't work */
|
||||||
|
/* FIXME: This breaks with more than one attachment, and the controls start overlapping the note buttons (like, boost, reply, etc) */
|
||||||
height: clamp(
|
height: clamp(
|
||||||
64px,
|
64px,
|
||||||
50cqh,
|
50cqh,
|
||||||
|
@ -294,6 +350,10 @@ onDeactivated(() => {
|
||||||
margin: 4px 8px;
|
margin: 4px 8px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue