mirror of
https://github.com/jellyfin/jellyfin-vue.git
synced 2026-09-02 21:04:08 +03:00
@@ -43,7 +43,7 @@ export function getBaseConfig(packageName: string, forceCache = !CI_environment,
|
|||||||
console.log(`[@jellyfin-vue/configs/lint] (${packageName}) Force enabling caching for this run`);
|
console.log(`[@jellyfin-vue/configs/lint] (${packageName}) Force enabling caching for this run`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warningAsErrors && !newArgs.some(arg => arg.includes('--max-warnings'))) {
|
if (warningAsErrors && newArgs.every(arg => !arg.includes('--max-warnings'))) {
|
||||||
newArgs.push('--max-warnings=0');
|
newArgs.push('--max-warnings=0');
|
||||||
console.log(`[@jellyfin-vue/configs/lint] (${packageName}) Force enabling warnings for this run`);
|
console.log(`[@jellyfin-vue/configs/lint] (${packageName}) Force enabling warnings for this run`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,11 +259,9 @@ export function getTSVueConfig(enableVue = true, tsconfigRootDir = import.meta.d
|
|||||||
...langOptions,
|
...langOptions,
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
...sharedParserOptions,
|
...sharedParserOptions,
|
||||||
...(enableVue
|
...(enableVue && {
|
||||||
? {
|
extraFileExtensions: ['.vue']
|
||||||
extraFileExtensions: ['.vue']
|
})
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -32,4 +32,4 @@ export const darkColors = () => ({
|
|||||||
warning: '#FB8C00'
|
warning: '#FB8C00'
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ColorPalette = keyof ReturnType<typeof lightColors> & keyof ReturnType<typeof darkColors>;
|
export type ColorPalette = keyof ReturnType<typeof lightColors> & keyof ReturnType<typeof darkColors>;
|
||||||
|
|||||||
@@ -171,12 +171,14 @@ const validationRules = [
|
|||||||
* Set one of the objects on Combobox's blur
|
* Set one of the objects on Combobox's blur
|
||||||
*/
|
*/
|
||||||
function onFocus(e: boolean): void {
|
function onFocus(e: boolean): void {
|
||||||
if (!e) {
|
if (e) {
|
||||||
const playbackSpeedIndex = defaultPlaybackSpeeds.indexOf(playbackManager.playbackSpeed.value);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (playbackSpeedIndex !== -1) {
|
const playbackSpeedIndex = defaultPlaybackSpeeds.indexOf(playbackManager.playbackSpeed.value);
|
||||||
_playbackSpeed.value = playbackItems.value[playbackSpeedIndex];
|
|
||||||
}
|
if (playbackSpeedIndex !== -1) {
|
||||||
|
_playbackSpeed.value = playbackItems.value[playbackSpeedIndex];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -211,30 +211,32 @@ const displayName = computed(() => {
|
|||||||
return selectedMediaSource.value?.Name ?? t('mediaInfo');
|
return selectedMediaSource.value?.Name ?? t('mediaInfo');
|
||||||
});
|
});
|
||||||
const generalProperties = computed(() => {
|
const generalProperties = computed(() => {
|
||||||
if (selectedMediaSource.value) {
|
if (!selectedMediaSource.value) {
|
||||||
const p = new Map<string, string | number | boolean | null | undefined>();
|
return;
|
||||||
const formats
|
|
||||||
= isArray(selectedMediaSource.value.Formats)
|
|
||||||
&& selectedMediaSource.value.Formats.length
|
|
||||||
? selectedMediaSource.value.Formats.join(',')
|
|
||||||
: undefined;
|
|
||||||
const fileSize = isNumber(selectedMediaSource.value.Size)
|
|
||||||
? formatFileSize(selectedMediaSource.value.Size)
|
|
||||||
: undefined;
|
|
||||||
const bitrate
|
|
||||||
= isNumber(selectedMediaSource.value.Bitrate)
|
|
||||||
&& selectedMediaSource.value.Bitrate > 0
|
|
||||||
? formatBitRate(selectedMediaSource.value.Bitrate)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
p.set(t('mediaInfoFileContainer'), selectedMediaSource.value.Container);
|
|
||||||
p.set(t('mediaInfoFileFormats'), formats);
|
|
||||||
p.set(t('mediaInfoFilePath'), selectedMediaSource.value.Path);
|
|
||||||
p.set(t('mediaInfoFileSize'), fileSize);
|
|
||||||
p.set(t('mediaInfoGenericBitrate'), bitrate);
|
|
||||||
|
|
||||||
return p.entries();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const p = new Map<string, string | number | boolean | null | undefined>();
|
||||||
|
const formats
|
||||||
|
= isArray(selectedMediaSource.value.Formats)
|
||||||
|
&& selectedMediaSource.value.Formats.length
|
||||||
|
? selectedMediaSource.value.Formats.join(',')
|
||||||
|
: undefined;
|
||||||
|
const fileSize = isNumber(selectedMediaSource.value.Size)
|
||||||
|
? formatFileSize(selectedMediaSource.value.Size)
|
||||||
|
: undefined;
|
||||||
|
const bitrate
|
||||||
|
= isNumber(selectedMediaSource.value.Bitrate)
|
||||||
|
&& selectedMediaSource.value.Bitrate > 0
|
||||||
|
? formatBitRate(selectedMediaSource.value.Bitrate)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
p.set(t('mediaInfoFileContainer'), selectedMediaSource.value.Container);
|
||||||
|
p.set(t('mediaInfoFileFormats'), formats);
|
||||||
|
p.set(t('mediaInfoFilePath'), selectedMediaSource.value.Path);
|
||||||
|
p.set(t('mediaInfoFileSize'), fileSize);
|
||||||
|
p.set(t('mediaInfoGenericBitrate'), bitrate);
|
||||||
|
|
||||||
|
return p.entries();
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -47,24 +47,26 @@ const findSubtitle = (dialogue: ParsedSubtitleTrack['dialogue'], start = 0) => {
|
|||||||
|
|
||||||
const dialogue = computed(() => playerElement.currentExternalSubtitleTrack.value?.parsed?.dialogue);
|
const dialogue = computed(() => playerElement.currentExternalSubtitleTrack.value?.parsed?.dialogue);
|
||||||
const currentSubtitle = computed<undefined | { index: number; sub?: Dialogue }>((previous) => {
|
const currentSubtitle = computed<undefined | { index: number; sub?: Dialogue }>((previous) => {
|
||||||
if (!isNil(dialogue.value)) {
|
if (isNil(dialogue.value)) {
|
||||||
const hasPrevious = !isNil(previous);
|
return;
|
||||||
const nextIndex = hasPrevious ? previous.index + 1 : 0;
|
}
|
||||||
const isNext = hasPrevious && predicate(dialogue.value[nextIndex]);
|
|
||||||
const isCurrent = hasPrevious && predicate(dialogue.value[previous.index]);
|
|
||||||
|
|
||||||
if (isCurrent) {
|
const hasPrevious = !isNil(previous);
|
||||||
return previous;
|
const nextIndex = hasPrevious ? previous.index + 1 : 0;
|
||||||
} else {
|
const isNext = hasPrevious && predicate(dialogue.value[nextIndex]);
|
||||||
const newIndex = isNext ? nextIndex : findSubtitle(dialogue.value, nextIndex);
|
const isCurrent = hasPrevious && predicate(dialogue.value[previous.index]);
|
||||||
|
|
||||||
if (!isNil(newIndex)) {
|
if (isCurrent) {
|
||||||
return { index: newIndex, sub: dialogue.value[newIndex] };
|
return previous;
|
||||||
}
|
} else {
|
||||||
|
const newIndex = isNext ? nextIndex : findSubtitle(dialogue.value, nextIndex);
|
||||||
|
|
||||||
if (hasPrevious) {
|
if (!isNil(newIndex)) {
|
||||||
return { index: previous.index };
|
return { index: newIndex, sub: dialogue.value[newIndex] };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasPrevious) {
|
||||||
|
return { index: previous.index };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -288,7 +288,7 @@ function setupEffects<T extends Record<K, (...args: any[]) => any>, K extends ke
|
|||||||
/**
|
/**
|
||||||
* Does a deep comparison to avoid useless double requests
|
* Does a deep comparison to avoid useless double requests
|
||||||
*/
|
*/
|
||||||
if (!normalizedArgs.every((a, index) => deepEqual(a, toValue(old[index])))) {
|
if (normalizedArgs.some((a, index) => !deepEqual(a, toValue(old[index])))) {
|
||||||
argsRef.value = normalizedArgs;
|
argsRef.value = normalizedArgs;
|
||||||
await runNormally();
|
await runNormally();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,16 +228,18 @@ class RemotePluginAuth extends BaseState<AuthState> {
|
|||||||
* Refreshes the current user infos, to fetch a new picture for instance
|
* Refreshes the current user infos, to fetch a new picture for instance
|
||||||
*/
|
*/
|
||||||
public readonly refreshCurrentUserInfo = async (): Promise<void> => {
|
public readonly refreshCurrentUserInfo = async (): Promise<void> => {
|
||||||
if (!isNil(this.currentUser.value) && !isNil(this.currentServer.value)) {
|
if (isNil(this.currentUser.value) || isNil(this.currentServer.value)) {
|
||||||
const api = useOneTimeAPI(
|
return;
|
||||||
this.currentServer.value.PublicAddress,
|
|
||||||
this.currentUserToken.value
|
|
||||||
);
|
|
||||||
|
|
||||||
this._state.value.users[this._state.value.currentUserIndex] = (
|
|
||||||
await getUserApi(api).getCurrentUser()
|
|
||||||
).data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const api = useOneTimeAPI(
|
||||||
|
this.currentServer.value.PublicAddress,
|
||||||
|
this.currentUserToken.value
|
||||||
|
);
|
||||||
|
|
||||||
|
this._state.value.users[this._state.value.currentUserIndex] = (
|
||||||
|
await getUserApi(api).getCurrentUser()
|
||||||
|
).data;
|
||||||
};
|
};
|
||||||
|
|
||||||
private readonly _refreshServers = async (): Promise<void> => {
|
private readonly _refreshServers = async (): Promise<void> => {
|
||||||
@@ -256,7 +258,7 @@ class RemotePluginAuth extends BaseState<AuthState> {
|
|||||||
* @param skipRequest - Skips the request and directly removes the user from the store
|
* @param skipRequest - Skips the request and directly removes the user from the store
|
||||||
*/
|
*/
|
||||||
public readonly logoutCurrentUser = async (skipRequest = false): Promise<void> => {
|
public readonly logoutCurrentUser = async (skipRequest = false): Promise<void> => {
|
||||||
if (!(!isNil(this.currentUser.value) && !isNil(this.currentServer.value))) {
|
if (isNil(this.currentUser.value) || isNil(this.currentServer.value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,21 @@ class RemotePluginSocket {
|
|||||||
* == STATE ==
|
* == STATE ==
|
||||||
*/
|
*/
|
||||||
private readonly _socketUrl = computed(() => {
|
private readonly _socketUrl = computed(() => {
|
||||||
if (
|
if (!(auth.currentUserToken.value
|
||||||
auth.currentUserToken.value
|
|
||||||
&& auth.currentServer.value
|
&& auth.currentServer.value
|
||||||
&& sdk.deviceInfo.id
|
&& sdk.deviceInfo.id
|
||||||
&& sdk.api?.basePath
|
&& sdk.api?.basePath)) {
|
||||||
) {
|
return;
|
||||||
const socketParameters = new URLSearchParams({
|
|
||||||
api_key: auth.currentUserToken.value,
|
|
||||||
deviceId: sdk.deviceInfo.id
|
|
||||||
}).toString();
|
|
||||||
|
|
||||||
return `${sdk.api.basePath}/socket?${socketParameters}`
|
|
||||||
.replace('https:', 'wss:')
|
|
||||||
.replace('http:', 'ws:');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const socketParameters = new URLSearchParams({
|
||||||
|
api_key: auth.currentUserToken.value,
|
||||||
|
deviceId: sdk.deviceInfo.id
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
return `${sdk.api.basePath}/socket?${socketParameters}`
|
||||||
|
.replace('https:', 'wss:')
|
||||||
|
.replace('http:', 'ws:');
|
||||||
});
|
});
|
||||||
|
|
||||||
private readonly _keepAliveMessage = 'KeepAlive';
|
private readonly _keepAliveMessage = 'KeepAlive';
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { remote } from '#/plugins/remote/index.ts';
|
|||||||
export function adminGuard(
|
export function adminGuard(
|
||||||
to: RouteLocationNormalized
|
to: RouteLocationNormalized
|
||||||
): NavigationGuardReturn {
|
): NavigationGuardReturn {
|
||||||
if (!(to.meta.admin && !remote.auth.currentUser.value?.Policy?.IsAdministrator)) {
|
if (!to.meta.admin || remote.auth.currentUser.value?.Policy?.IsAdministrator) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,13 +10,15 @@ import { useSnackbar } from '#/composables/use-snackbar.ts';
|
|||||||
export function validateGuard(
|
export function validateGuard(
|
||||||
to: RouteLocationNormalized
|
to: RouteLocationNormalized
|
||||||
): NavigationGuardReturn {
|
): NavigationGuardReturn {
|
||||||
if (('itemId' in to.params) && isStr(to.params.itemId)) {
|
if (!(('itemId' in to.params) && isStr(to.params.itemId))) {
|
||||||
const check = /[\da-f]{32}/i.test(to.params.itemId);
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!check) {
|
const check = /[\da-f]{32}/i.test(to.params.itemId);
|
||||||
useSnackbar(i18next.t('routeValidationError'), 'error');
|
|
||||||
|
|
||||||
return false;
|
if (!check) {
|
||||||
}
|
useSnackbar(i18next.t('routeValidationError'), 'error');
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -535,30 +535,34 @@ class PlaybackManagerStore extends CommonStore<PlaybackManagerState> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public readonly setNewQueue = (queue: string[]): void => {
|
public readonly setNewQueue = (queue: string[]): void => {
|
||||||
if (this.currentItemId.value) {
|
if (!this.currentItemId.value) {
|
||||||
const newIndex = queue.indexOf(this.currentItemId.value);
|
return;
|
||||||
|
|
||||||
this._state.value.queue = queue;
|
|
||||||
this._state.value.currentItemIndex = newIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newIndex = queue.indexOf(this.currentItemId.value);
|
||||||
|
|
||||||
|
this._state.value.queue = queue;
|
||||||
|
this._state.value.currentItemIndex = newIndex;
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly changeItemPosition = (
|
public readonly changeItemPosition = (
|
||||||
itemId: string | undefined,
|
itemId: string | undefined,
|
||||||
newIndex: number
|
newIndex: number
|
||||||
): void => {
|
): void => {
|
||||||
if (itemId && this.queueHasItem(itemId)) {
|
if (!(itemId && this.queueHasItem(itemId))) {
|
||||||
const newQueue = this._state.value.queue.filter(id => id !== itemId);
|
return;
|
||||||
|
|
||||||
newQueue.splice(newIndex, 0, itemId);
|
|
||||||
this.setNewQueue(newQueue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newQueue = this._state.value.queue.filter(id => id !== itemId);
|
||||||
|
|
||||||
|
newQueue.splice(newIndex, 0, itemId);
|
||||||
|
this.setNewQueue(newQueue);
|
||||||
};
|
};
|
||||||
|
|
||||||
public readonly stop = (): void => {
|
public readonly stop = (): void => {
|
||||||
const sessionId = String(this._state.value.playSessionId ?? '');
|
const sessionId = (this._state.value.playSessionId ?? '');
|
||||||
const time = Number(this.currentTime.value);
|
const time = Number(this.currentTime.value);
|
||||||
const itemId = String(this.currentItem.value?.Id ?? '');
|
const itemId = (this.currentItem.value?.Id ?? '');
|
||||||
const volume = Number(this.currentVolume.value);
|
const volume = Number(this.currentVolume.value);
|
||||||
|
|
||||||
this._reset();
|
this._reset();
|
||||||
|
|||||||
@@ -196,31 +196,31 @@ class PlayerElementStore extends CommonStore<PlayerElementState, 'isStretched' |
|
|||||||
* Applies SSA (SubStation Alpha) subtitles to the media element.
|
* Applies SSA (SubStation Alpha) subtitles to the media element.
|
||||||
*/
|
*/
|
||||||
private readonly _applySsaSubtitles = async (): Promise<void> => {
|
private readonly _applySsaSubtitles = async (): Promise<void> => {
|
||||||
if (
|
if (!(mediaElementRef.value
|
||||||
mediaElementRef.value
|
|
||||||
&& this.currentExternalSubtitleTrack.value
|
&& this.currentExternalSubtitleTrack.value
|
||||||
&& (mediaElementRef.value instanceof HTMLVideoElement)
|
&& (mediaElementRef.value instanceof HTMLVideoElement))) {
|
||||||
) {
|
return;
|
||||||
this._clear();
|
}
|
||||||
|
|
||||||
const trackSrc = this.currentExternalSubtitleTrack.value.src;
|
this._clear();
|
||||||
const subtitleTrackPayload = await this._fetchSubtitleTrack(trackSrc);
|
|
||||||
|
|
||||||
if (subtitleTrackPayload[trackSrc]) {
|
const trackSrc = this.currentExternalSubtitleTrack.value.src;
|
||||||
/**
|
const subtitleTrackPayload = await this._fetchSubtitleTrack(trackSrc);
|
||||||
* video_width works better with ultrawide monitors
|
|
||||||
*/
|
|
||||||
this._asssub = new ASSSUB(
|
|
||||||
subtitleTrackPayload[trackSrc],
|
|
||||||
mediaElementRef.value,
|
|
||||||
{ resampling: 'video_width' }
|
|
||||||
);
|
|
||||||
|
|
||||||
this._cleanups.add(() => {
|
if (subtitleTrackPayload[trackSrc]) {
|
||||||
this._asssub?.destroy();
|
/**
|
||||||
this._asssub = undefined;
|
* video_width works better with ultrawide monitors
|
||||||
});
|
*/
|
||||||
}
|
this._asssub = new ASSSUB(
|
||||||
|
subtitleTrackPayload[trackSrc],
|
||||||
|
mediaElementRef.value,
|
||||||
|
{ resampling: 'video_width' }
|
||||||
|
);
|
||||||
|
|
||||||
|
this._cleanups.add(() => {
|
||||||
|
this._asssub?.destroy();
|
||||||
|
this._asssub = undefined;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -253,19 +253,19 @@ class PlayerElementStore extends CommonStore<PlayerElementState, 'isStretched' |
|
|||||||
* Applies VTT (WebVTT) subtitles to the media element.
|
* Applies VTT (WebVTT) subtitles to the media element.
|
||||||
*/
|
*/
|
||||||
private readonly _applyVttSubtitles = () => {
|
private readonly _applyVttSubtitles = () => {
|
||||||
if (
|
if (!(mediaElementRef.value
|
||||||
mediaElementRef.value
|
&& this.currentExternalSubtitleTrack.value)) {
|
||||||
&& this.currentExternalSubtitleTrack.value
|
return;
|
||||||
) {
|
}
|
||||||
const subtitleTrack = this.currentExternalSubtitleTrack.value;
|
|
||||||
|
|
||||||
/**
|
const subtitleTrack = this.currentExternalSubtitleTrack.value;
|
||||||
* Check if client is able to display custom subtitle track
|
|
||||||
* otherwise show default subtitle track
|
/**
|
||||||
*/
|
* Check if client is able to display custom subtitle track
|
||||||
if (!this._useCustomSubtitleTrack.value && !isNil(mediaElementRef.value.textTracks[subtitleTrack.srcIndex])) {
|
* otherwise show default subtitle track
|
||||||
mediaElementRef.value.textTracks[subtitleTrack.srcIndex].mode = 'showing';
|
*/
|
||||||
}
|
if (!this._useCustomSubtitleTrack.value && !isNil(mediaElementRef.value.textTracks[subtitleTrack.srcIndex])) {
|
||||||
|
mediaElementRef.value.textTracks[subtitleTrack.srcIndex].mode = 'showing';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -84,28 +84,30 @@ export abstract class SyncedStore<
|
|||||||
* Updates CustomPrefs by merging passed in value with existing custom prefs
|
* Updates CustomPrefs by merging passed in value with existing custom prefs
|
||||||
*/
|
*/
|
||||||
private readonly _updateState = async (): Promise<void> => {
|
private readonly _updateState = async (): Promise<void> => {
|
||||||
if (remote.auth.currentUser.value) {
|
if (!remote.auth.currentUser.value) {
|
||||||
/**
|
return;
|
||||||
* Creates a config syncing task, so UI can show that there's a syncing in progress
|
}
|
||||||
*/
|
|
||||||
const syncTaskId = taskManager.startConfigSync();
|
|
||||||
|
|
||||||
try {
|
/**
|
||||||
const newPrefs: DisplayPreferencesDto['CustomPrefs'] = {};
|
* Creates a config syncing task, so UI can show that there's a syncing in progress
|
||||||
|
*/
|
||||||
|
const syncTaskId = taskManager.startConfigSync();
|
||||||
|
|
||||||
for (const key of this._syncedKeys) {
|
try {
|
||||||
newPrefs[String(key)] = this._serializeCustomPref(this._state.value[key]);
|
const newPrefs: DisplayPreferencesDto['CustomPrefs'] = {};
|
||||||
}
|
|
||||||
|
|
||||||
const displayPreferences = await this._fetchDisplayPreferences();
|
for (const key of this._syncedKeys) {
|
||||||
|
newPrefs[String(key)] = this._serializeCustomPref(this._state.value[key]);
|
||||||
displayPreferences.CustomPrefs = newPrefs;
|
|
||||||
await this._updateDisplayPreferences(displayPreferences);
|
|
||||||
} catch {
|
|
||||||
useSnackbar(i18next.t('failedSyncingUserSettings'), 'error');
|
|
||||||
} finally {
|
|
||||||
taskManager.finishTask(syncTaskId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const displayPreferences = await this._fetchDisplayPreferences();
|
||||||
|
|
||||||
|
displayPreferences.CustomPrefs = newPrefs;
|
||||||
|
await this._updateDisplayPreferences(displayPreferences);
|
||||||
|
} catch {
|
||||||
|
useSnackbar(i18next.t('failedSyncingUserSettings'), 'error');
|
||||||
|
} finally {
|
||||||
|
taskManager.finishTask(syncTaskId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -109,37 +109,37 @@ class TaskManagerStore extends CommonStore<TaskManagerState, 'tasks'> {
|
|||||||
* Handle refresh progress update for library items
|
* Handle refresh progress update for library items
|
||||||
*/
|
*/
|
||||||
const refreshProgressAction = async (type: string, data: object) => {
|
const refreshProgressAction = async (type: string, data: object) => {
|
||||||
if (
|
if (!(type === 'RefreshProgress'
|
||||||
type === 'RefreshProgress'
|
|
||||||
&& 'ItemId' in data
|
&& 'ItemId' in data
|
||||||
&& isStr(data.ItemId)
|
&& isStr(data.ItemId)
|
||||||
&& 'Progress' in data
|
&& 'Progress' in data)) {
|
||||||
) {
|
return;
|
||||||
// TODO: Verify all the different tasks that this message may belong to - here we assume libraries.
|
}
|
||||||
|
|
||||||
const progress = Number(data.Progress);
|
// TODO: Verify all the different tasks that this message may belong to - here we assume libraries.
|
||||||
const taskPayload = this.getTask(data.ItemId);
|
|
||||||
|
|
||||||
/**
|
const progress = Number(data.Progress);
|
||||||
* Start task if update its received and it doesn't exist in the store.
|
const taskPayload = this.getTask(data.ItemId);
|
||||||
* Usually when a running task is started somewhere else and the client is accssed later
|
|
||||||
*/
|
|
||||||
if (isNil(taskPayload)) {
|
|
||||||
const item = await apiStore.getItemById(data.ItemId);
|
|
||||||
|
|
||||||
if (item?.Id && item.Name) {
|
/**
|
||||||
this.startTask({
|
* Start task if update its received and it doesn't exist in the store.
|
||||||
type: TaskType.LibraryRefresh,
|
* Usually when a running task is started somewhere else and the client is accssed later
|
||||||
id: item.Id,
|
*/
|
||||||
data: item.Name,
|
if (isNil(taskPayload)) {
|
||||||
progress
|
const item = await apiStore.getItemById(data.ItemId);
|
||||||
});
|
|
||||||
}
|
if (item?.Id && item.Name) {
|
||||||
} else if (progress >= 0 && progress < 100) {
|
this.startTask({
|
||||||
taskPayload.progress = progress;
|
type: TaskType.LibraryRefresh,
|
||||||
} else if (progress >= 0) {
|
id: item.Id,
|
||||||
this.finishTask(data.ItemId);
|
data: item.Name,
|
||||||
|
progress
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else if (progress >= 0 && progress < 100) {
|
||||||
|
taskPayload.progress = progress;
|
||||||
|
} else if (progress >= 0) {
|
||||||
|
this.finishTask(data.ItemId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -498,12 +498,14 @@ export async function getItemSeasonDownloadMap(
|
|||||||
).data.Items ?? [];
|
).data.Items ?? [];
|
||||||
|
|
||||||
for (const episode of episodes) {
|
for (const episode of episodes) {
|
||||||
if (episode.Id && !isNil(episode.Name)) {
|
if (!episode.Id || isNil(episode.Name)) {
|
||||||
const url = getItemDownloadUrl(episode.Id);
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (url) {
|
const url = getItemDownloadUrl(episode.Id);
|
||||||
result.set(episode.Name, url);
|
|
||||||
}
|
if (url) {
|
||||||
|
result.set(episode.Name, url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,11 +531,13 @@ export async function getItemSeriesDownloadMap(
|
|||||||
).data.Items ?? [];
|
).data.Items ?? [];
|
||||||
|
|
||||||
for (const season of seasons) {
|
for (const season of seasons) {
|
||||||
if (season.Id) {
|
if (!season.Id) {
|
||||||
const map = await getItemSeasonDownloadMap(season.Id);
|
continue;
|
||||||
|
|
||||||
result = new Map([...result, ...map]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const map = await getItemSeasonDownloadMap(season.Id);
|
||||||
|
|
||||||
|
result = new Map([...result, ...map]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export function getCodecProfiles(
|
|||||||
|| videoTestElement
|
|| videoTestElement
|
||||||
.canPlayType('video/mp4; codecs="avc1.6e0033"')
|
.canPlayType('video/mp4; codecs="avc1.6e0033"')
|
||||||
.replace(/no/, '')) // TODO: These tests are passing in Safari, but playback is failing
|
.replace(/no/, '')) // TODO: These tests are passing in Safari, but playback is failing
|
||||||
&& (!isApple() || !isWebOS() || !(isEdge() && !isChromiumBased()))
|
&& (!isApple() || !isWebOS() || !isEdge() || isChromiumBased())
|
||||||
) {
|
) {
|
||||||
h264Profiles += '|high 10';
|
h264Profiles += '|high 10';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ export function formatTime(seconds: number): string {
|
|||||||
let minutes = Math.floor(seconds / 60);
|
let minutes = Math.floor(seconds / 60);
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
|
|
||||||
minutes = minutes - hours * 60;
|
minutes -= hours * 60;
|
||||||
seconds = Math.floor(seconds - (minutes * 60 + hours * 60 * 60));
|
seconds = Math.floor(seconds - (minutes * 60 + hours * 60 * 60));
|
||||||
|
|
||||||
return hours
|
return hours
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ function localeTransform(keys: string[], l: string): string | undefined {
|
|||||||
/**
|
/**
|
||||||
* Takes the part before the potential hyphen to try, for instance "fr-FR" in i18n to "fr"
|
* Takes the part before the potential hyphen to try, for instance "fr-FR" in i18n to "fr"
|
||||||
*/
|
*/
|
||||||
} else if (testStrings[0] && keys.includes(testStrings[0])) {
|
}
|
||||||
|
|
||||||
|
if (testStrings[0] && keys.includes(testStrings[0])) {
|
||||||
return `${testStrings[0]} as ${lang}`;
|
return `${testStrings[0]} as ${lang}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ test('isNumber', () => {
|
|||||||
expect(isNumber(Number.MAX_VALUE)).toBe(true);
|
expect(isNumber(Number.MAX_VALUE)).toBe(true);
|
||||||
expect(isNumber(Number.MIN_VALUE)).toBe(true);
|
expect(isNumber(Number.MIN_VALUE)).toBe(true);
|
||||||
expect(isNumber(Number.EPSILON)).toBe(true);
|
expect(isNumber(Number.EPSILON)).toBe(true);
|
||||||
expect(isNumber(Number.POSITIVE_INFINITY)).toBe(true);
|
expect(isNumber(Infinity)).toBe(true);
|
||||||
expect(isNumber(Number.NEGATIVE_INFINITY)).toBe(true);
|
expect(isNumber(Number.NEGATIVE_INFINITY)).toBe(true);
|
||||||
expect(isNumber(Number.NaN)).toBe(true);
|
expect(isNumber(NaN)).toBe(true);
|
||||||
expect(isNumber(() => 0)).toBe(false);
|
expect(isNumber(() => 0)).toBe(false);
|
||||||
expect(isNumber({})).toBe(false);
|
expect(isNumber({})).toBe(false);
|
||||||
expect(isNumber([])).toBe(false);
|
expect(isNumber([])).toBe(false);
|
||||||
@@ -34,9 +34,9 @@ test('isBool', () => {
|
|||||||
expect(isBool(Number.MAX_VALUE)).toBe(false);
|
expect(isBool(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isBool(Number.MIN_VALUE)).toBe(false);
|
expect(isBool(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isBool(Number.EPSILON)).toBe(false);
|
expect(isBool(Number.EPSILON)).toBe(false);
|
||||||
expect(isBool(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isBool(Infinity)).toBe(false);
|
||||||
expect(isBool(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isBool(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isBool(Number.NaN)).toBe(false);
|
expect(isBool(NaN)).toBe(false);
|
||||||
expect(isBool(() => 0)).toBe(false);
|
expect(isBool(() => 0)).toBe(false);
|
||||||
expect(isBool(() => true)).toBe(false);
|
expect(isBool(() => true)).toBe(false);
|
||||||
expect(isBool({})).toBe(false);
|
expect(isBool({})).toBe(false);
|
||||||
@@ -55,9 +55,9 @@ test('isStr', () => {
|
|||||||
expect(isStr(Number.MAX_VALUE)).toBe(false);
|
expect(isStr(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isStr(Number.MIN_VALUE)).toBe(false);
|
expect(isStr(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isStr(Number.EPSILON)).toBe(false);
|
expect(isStr(Number.EPSILON)).toBe(false);
|
||||||
expect(isStr(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isStr(Infinity)).toBe(false);
|
||||||
expect(isStr(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isStr(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isStr(Number.NaN)).toBe(false);
|
expect(isStr(NaN)).toBe(false);
|
||||||
expect(isStr(() => 0)).toBe(false);
|
expect(isStr(() => 0)).toBe(false);
|
||||||
expect(isStr(() => '0')).toBe(false);
|
expect(isStr(() => '0')).toBe(false);
|
||||||
expect(isStr({})).toBe(false);
|
expect(isStr({})).toBe(false);
|
||||||
@@ -76,9 +76,9 @@ test('isFunc', () => {
|
|||||||
expect(isFunc(Number.MAX_VALUE)).toBe(false);
|
expect(isFunc(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isFunc(Number.MIN_VALUE)).toBe(false);
|
expect(isFunc(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isFunc(Number.EPSILON)).toBe(false);
|
expect(isFunc(Number.EPSILON)).toBe(false);
|
||||||
expect(isFunc(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isFunc(Infinity)).toBe(false);
|
||||||
expect(isFunc(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isFunc(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isFunc(Number.NaN)).toBe(false);
|
expect(isFunc(NaN)).toBe(false);
|
||||||
expect(isFunc(() => 0)).toBe(true);
|
expect(isFunc(() => 0)).toBe(true);
|
||||||
expect(isFunc(() => '0')).toBe(true);
|
expect(isFunc(() => '0')).toBe(true);
|
||||||
expect(isFunc({})).toBe(false);
|
expect(isFunc({})).toBe(false);
|
||||||
@@ -97,9 +97,9 @@ test('isUndef', () => {
|
|||||||
expect(isUndef(Number.MAX_VALUE)).toBe(false);
|
expect(isUndef(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isUndef(Number.MIN_VALUE)).toBe(false);
|
expect(isUndef(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isUndef(Number.EPSILON)).toBe(false);
|
expect(isUndef(Number.EPSILON)).toBe(false);
|
||||||
expect(isUndef(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isUndef(Infinity)).toBe(false);
|
||||||
expect(isUndef(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isUndef(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isUndef(Number.NaN)).toBe(false);
|
expect(isUndef(NaN)).toBe(false);
|
||||||
expect(isUndef(() => 0)).toBe(false);
|
expect(isUndef(() => 0)).toBe(false);
|
||||||
expect(isUndef(() => undefined)).toBe(false);
|
expect(isUndef(() => undefined)).toBe(false);
|
||||||
expect(isUndef({})).toBe(false);
|
expect(isUndef({})).toBe(false);
|
||||||
@@ -118,9 +118,9 @@ test('isNull', () => {
|
|||||||
expect(isNull(Number.MAX_VALUE)).toBe(false);
|
expect(isNull(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isNull(Number.MIN_VALUE)).toBe(false);
|
expect(isNull(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isNull(Number.EPSILON)).toBe(false);
|
expect(isNull(Number.EPSILON)).toBe(false);
|
||||||
expect(isNull(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isNull(Infinity)).toBe(false);
|
||||||
expect(isNull(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isNull(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isNull(Number.NaN)).toBe(false);
|
expect(isNull(NaN)).toBe(false);
|
||||||
expect(isNull(() => 0)).toBe(false);
|
expect(isNull(() => 0)).toBe(false);
|
||||||
expect(isNull(() => null)).toBe(false);
|
expect(isNull(() => null)).toBe(false);
|
||||||
expect(isNull({})).toBe(false);
|
expect(isNull({})).toBe(false);
|
||||||
@@ -139,9 +139,9 @@ test('isNil', () => {
|
|||||||
expect(isNil(Number.MAX_VALUE)).toBe(false);
|
expect(isNil(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isNil(Number.MIN_VALUE)).toBe(false);
|
expect(isNil(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isNil(Number.EPSILON)).toBe(false);
|
expect(isNil(Number.EPSILON)).toBe(false);
|
||||||
expect(isNil(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isNil(Infinity)).toBe(false);
|
||||||
expect(isNil(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isNil(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isNil(Number.NaN)).toBe(false);
|
expect(isNil(NaN)).toBe(false);
|
||||||
expect(isNil(() => 0)).toBe(false);
|
expect(isNil(() => 0)).toBe(false);
|
||||||
expect(isNil(() => null)).toBe(false);
|
expect(isNil(() => null)).toBe(false);
|
||||||
expect(isNil(() => undefined)).toBe(false);
|
expect(isNil(() => undefined)).toBe(false);
|
||||||
@@ -161,9 +161,9 @@ test('isObj', () => {
|
|||||||
expect(isObj(Number.MAX_VALUE)).toBe(false);
|
expect(isObj(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isObj(Number.MIN_VALUE)).toBe(false);
|
expect(isObj(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isObj(Number.EPSILON)).toBe(false);
|
expect(isObj(Number.EPSILON)).toBe(false);
|
||||||
expect(isObj(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isObj(Infinity)).toBe(false);
|
||||||
expect(isObj(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isObj(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isObj(Number.NaN)).toBe(false);
|
expect(isObj(NaN)).toBe(false);
|
||||||
expect(isObj(() => 0)).toBe(false);
|
expect(isObj(() => 0)).toBe(false);
|
||||||
expect(isObj(() => ({}))).toBe(false);
|
expect(isObj(() => ({}))).toBe(false);
|
||||||
expect(isObj({})).toBe(true);
|
expect(isObj({})).toBe(true);
|
||||||
@@ -182,9 +182,9 @@ test('isArray', () => {
|
|||||||
expect(isArray(Number.MAX_VALUE)).toBe(false);
|
expect(isArray(Number.MAX_VALUE)).toBe(false);
|
||||||
expect(isArray(Number.MIN_VALUE)).toBe(false);
|
expect(isArray(Number.MIN_VALUE)).toBe(false);
|
||||||
expect(isArray(Number.EPSILON)).toBe(false);
|
expect(isArray(Number.EPSILON)).toBe(false);
|
||||||
expect(isArray(Number.POSITIVE_INFINITY)).toBe(false);
|
expect(isArray(Infinity)).toBe(false);
|
||||||
expect(isArray(Number.NEGATIVE_INFINITY)).toBe(false);
|
expect(isArray(Number.NEGATIVE_INFINITY)).toBe(false);
|
||||||
expect(isArray(Number.NaN)).toBe(false);
|
expect(isArray(NaN)).toBe(false);
|
||||||
expect(isArray(() => 0)).toBe(false);
|
expect(isArray(() => 0)).toBe(false);
|
||||||
expect(isArray(() => [])).toBe(false);
|
expect(isArray(() => [])).toBe(false);
|
||||||
expect(isArray({})).toBe(false);
|
expect(isArray({})).toBe(false);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export default defineConfig(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Don't minify for debug builds
|
// Don't minify for debug builds
|
||||||
...(process.env.TAURI_ENV_DEBUG ? { minify: false } : {}),
|
...(process.env.TAURI_ENV_DEBUG && { minify: false }),
|
||||||
// Produce sourcemaps for debug builds
|
// Produce sourcemaps for debug builds
|
||||||
sourcemap: !!process.env.TAURI_ENV_DEBUG
|
sourcemap: !!process.env.TAURI_ENV_DEBUG
|
||||||
},
|
},
|
||||||
@@ -24,7 +24,7 @@ export default defineConfig(
|
|||||||
// Tauri expects a fixed port, fail if that port is not available
|
// Tauri expects a fixed port, fail if that port is not available
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
// if the host Tauri is expecting is set, use it
|
// if the host Tauri is expecting is set, use it
|
||||||
...(host ? { host } : {})
|
...(host && { host })
|
||||||
},
|
},
|
||||||
// Env variables starting with the item of `envPrefix` will be exposed in tauri's source code through `import.meta.env`.
|
// Env variables starting with the item of `envPrefix` will be exposed in tauri's source code through `import.meta.env`.
|
||||||
envPrefix: ['VITE_', 'TAURI_ENV_*']
|
envPrefix: ['VITE_', 'TAURI_ENV_*']
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ async function readSelectedFileAsBase64(): Promise<string | undefined> {
|
|||||||
|
|
||||||
useEventListener(reader, 'load', () => {
|
useEventListener(reader, 'load', () => {
|
||||||
const result = reader.result as string;
|
const result = reader.result as string;
|
||||||
const base64FileContent = result.split(',')[1];
|
const base64FileContent = result.split(',', 2)[1];
|
||||||
|
|
||||||
if (!base64FileContent) {
|
if (!base64FileContent) {
|
||||||
reject(new Error('Failed to read file content'));
|
reject(new Error('Failed to read file content'));
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ useResizeObserver(el, (entries) => {
|
|||||||
const entry = entries[0];
|
const entry = entries[0];
|
||||||
|
|
||||||
height.value = toPx(entry!.contentRect.height);
|
height.value = toPx(entry!.contentRect.height);
|
||||||
});
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
useLayoutStyle(() => ({ 'padding-bottom': height.value }));
|
useLayoutStyle(() => ({ 'padding-bottom': height.value }));
|
||||||
|
|||||||
@@ -217,19 +217,21 @@ const visibleItems = computed<InternalItem[]>((previous) => {
|
|||||||
const visibleItemsLength = computed(() => visibleItems.value.length);
|
const visibleItemsLength = computed(() => visibleItems.value.length);
|
||||||
const scrollParents = computed(() => rootRef.value && getScrollParents(rootRef.value));
|
const scrollParents = computed(() => rootRef.value && getScrollParents(rootRef.value));
|
||||||
const scrollTargets = computed(() => {
|
const scrollTargets = computed(() => {
|
||||||
if (scrollParents.value) {
|
if (!scrollParents.value) {
|
||||||
const { vertical, horizontal } = scrollParents.value;
|
return;
|
||||||
|
|
||||||
/**
|
|
||||||
* If the scrolling parent is the doc root, use window instead as using
|
|
||||||
* document root might not work properly.
|
|
||||||
*/
|
|
||||||
return (
|
|
||||||
vertical === horizontal ? [vertical] : [vertical, horizontal]
|
|
||||||
).map(parent =>
|
|
||||||
(parent === document.documentElement ? globalThis : parent)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { vertical, horizontal } = scrollParents.value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the scrolling parent is the doc root, use window instead as using
|
||||||
|
* document root might not work properly.
|
||||||
|
*/
|
||||||
|
return (
|
||||||
|
vertical === horizontal ? [vertical] : [vertical, horizontal]
|
||||||
|
).map(parent =>
|
||||||
|
(parent === document.documentElement ? globalThis : parent)
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,10 +265,12 @@ const populateCache = (() => {
|
|||||||
* old data might be pushed instead, so we avoid it here.
|
* old data might be pushed instead, so we avoid it here.
|
||||||
*/
|
*/
|
||||||
globalThis.requestAnimationFrame(() => {
|
globalThis.requestAnimationFrame(() => {
|
||||||
if (cache.size !== 0) {
|
if (cache.size === 0) {
|
||||||
cache.set(offset, values);
|
return;
|
||||||
workerUpdates.value++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cache.set(offset, values);
|
||||||
|
workerUpdates.value++;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,28 +282,29 @@ const populateCache = (() => {
|
|||||||
* to the worker when scrolling fast. We cache 2 times the buffer length
|
* to the worker when scrolling fast. We cache 2 times the buffer length
|
||||||
*/
|
*/
|
||||||
return function (): void {
|
return function (): void {
|
||||||
if (!isUndef(resizeMeasurement.value)
|
if (!(!isUndef(resizeMeasurement.value)
|
||||||
&& Number.isFinite(bufferLength.value)
|
&& Number.isFinite(bufferLength.value)
|
||||||
&& Number.isFinite(bufferOffset.value)
|
&& Number.isFinite(bufferOffset.value))) {
|
||||||
) {
|
return;
|
||||||
const area = bufferLength.value * 2;
|
}
|
||||||
const start = Math.max(1, bufferOffset.value - area);
|
|
||||||
const finish = bufferOffset.value + area;
|
|
||||||
|
|
||||||
/**
|
const area = bufferLength.value * 2;
|
||||||
* We always populate 0 first, so there's no blank space shown at the beginning
|
const start = Math.max(1, bufferOffset.value - area);
|
||||||
* or when scrolling to top after a resize in the bottom area.
|
const finish = bufferOffset.value + area;
|
||||||
*/
|
|
||||||
if (!cache.has(0)) {
|
|
||||||
void setCache(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = finish; i >= start && !cache.has(i); i--) {
|
/**
|
||||||
/**
|
* We always populate 0 first, so there's no blank space shown at the beginning
|
||||||
* Fire all the operations concurrently, no need to await them
|
* or when scrolling to top after a resize in the bottom area.
|
||||||
*/
|
*/
|
||||||
void setCache(i);
|
if (!cache.has(0)) {
|
||||||
}
|
void setCache(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = finish; i >= start && !cache.has(i); i--) {
|
||||||
|
/**
|
||||||
|
* Fire all the operations concurrently, no need to await them
|
||||||
|
*/
|
||||||
|
void setCache(i);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
@@ -315,16 +320,18 @@ useEventListener(scrollTargets, 'scroll', () => {
|
|||||||
* Tracks if the scroll must be pointed at an specific element
|
* Tracks if the scroll must be pointed at an specific element
|
||||||
*/
|
*/
|
||||||
watch(() => scrollTo, () => {
|
watch(() => scrollTo, () => {
|
||||||
if (!isNil(rootRef.value)
|
if (!(!isNil(rootRef.value)
|
||||||
&& !isUndef(scrollTo)
|
&& !isUndef(scrollTo)
|
||||||
&& !isUndef(resizeMeasurement.value)
|
&& !isUndef(resizeMeasurement.value)
|
||||||
&& !isNil(scrollParents.value)
|
&& !isNil(scrollParents.value)
|
||||||
&& scrollTo > 0
|
&& scrollTo > 0
|
||||||
&& scrollTo < itemsLength.value) {
|
&& scrollTo < itemsLength.value)) {
|
||||||
const { target, top, left } = getScrollToInfo(scrollParents.value, rootRef.value, resizeMeasurement.value, scrollTo);
|
return;
|
||||||
|
|
||||||
target.scrollTo({ top, left, behavior: 'smooth' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { target, top, left } = getScrollToInfo(scrollParents.value, rootRef.value, resizeMeasurement.value, scrollTo);
|
||||||
|
|
||||||
|
target.scrollTo({ top, left, behavior: 'smooth' });
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -153,12 +153,14 @@ export function JBundleChunking(): Plugin {
|
|||||||
* Split i18next resources into separate chunks
|
* Split i18next resources into separate chunks
|
||||||
*/
|
*/
|
||||||
name: (id) => {
|
name: (id) => {
|
||||||
if (id.includes('virtual:') || id.includes('i18next/resources')) {
|
if (!(id.includes('virtual:') || id.includes('i18next/resources'))) {
|
||||||
const targetPath = basename(id.split('/').at(-1)!);
|
return;
|
||||||
const isIndex = targetPath === 'resources';
|
|
||||||
|
|
||||||
return isIndex ? 'localization' : `localization/strings/${targetPath}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const targetPath = basename(id.split('/').at(-1)!);
|
||||||
|
const isIndex = targetPath === 'resources';
|
||||||
|
|
||||||
|
return isIndex ? 'localization' : `localization/strings/${targetPath}`;
|
||||||
},
|
},
|
||||||
priority: 8
|
priority: 8
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user