Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat,fix,refactor&perf: offline navigation, full item reactivity, simplifications and bug fixes #2201

Merged
merged 33 commits into from
Jan 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
033f6c0
feat: create initial impl of useBaseItem composable
ferferga Jan 3, 2024
b0a1f6d
feat: add useApi composable
ferferga Jan 3, 2024
8a5cad0
feat: finish api composables and create apistore
ferferga Jan 4, 2024
19abe6e
refactor: deprecate userLibraries store
ferferga Jan 4, 2024
395fc12
refactor: deprecate the itemStore
ferferga Jan 4, 2024
d22d4ca
refactor: finish deprecation of itemStore and start migrating compone…
ferferga Jan 4, 2024
908cf26
refactor: remove suspenseview
ferferga Jan 4, 2024
a496cbc
fix: reference errors
ferferga Jan 4, 2024
786f70e
feat: handle item deletion on apistore
ferferga Jan 4, 2024
3f304ad
feat: allow undefined for api, methodName on composable, skipCache
ferferga Jan 4, 2024
addc617
refactor: migrate markplayedbutton and relateditems to new composable
ferferga Jan 4, 2024
b1e95bb
refactor: add userId automatically in composables, improve typing
ferferga Jan 5, 2024
b747fe6
fix: markplayedbutton and likebutton in endless loading and loops
ferferga Jan 5, 2024
c38acb2
perf: remove useless structuredClone
ferferga Jan 5, 2024
5eccfca
refactor: extract sortBy into defaultSortOrder
ferferga Jan 5, 2024
fde063a
refactor: remove router import from auth plugin
ferferga Jan 5, 2024
d256bcd
feat: handle offline state in apis composable
ferferga Jan 6, 2024
f52b64f
feat: handle loading state in api composables instead of axios
ferferga Jan 7, 2024
a3089b0
fix: correct type signature for *QueryResult responses
ferferga Jan 7, 2024
b2f4d4e
chore: disable vite chunk warnings
ferferga Jan 7, 2024
9c25eea
refactor: improve splashscreen
ferferga Jan 7, 2024
0b8dbe2
refactor: use useBaseItem composable in library pages
ferferga Jan 7, 2024
83e4c40
perf: optimize baseitem adding performance
ferferga Jan 7, 2024
06c5545
feat(composables): always return previous data when there is a reques…
ferferga Jan 8, 2024
0793b4e
refactor: migrate search and some settings pages to api composables
ferferga Jan 8, 2024
cf1dd63
fix: properly block navigation when changing pages
ferferga Jan 8, 2024
593f4e1
refactor: migrate playback related stuff and add lazy loading to libr…
ferferga Jan 8, 2024
8101a9b
fix(composables/api): offlineparams promise
ferferga Jan 8, 2024
1208961
refactor: simplify previous request handling and error state
ferferga Jan 9, 2024
db2747e
perf: separate reactive items into different collections
ferferga Jan 9, 2024
27044a1
feat: show wait cursor while loading
ferferga Jan 9, 2024
5f8eb6b
fix: appropiate callback flush timers for watchers
ferferga Jan 9, 2024
6c9fdf4
fix: login with incorrect credentials stuck in endless loop
ferferga Jan 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@
"mode": "auto"
}
],
"emmet.includeLanguages": {
"vue": "html"
},
"vue.autoInsert.dotValue": true,
"vue.server.fullCompletionList": true,
"sonarlint.output.showAnalyzerLogs": true,
Expand Down
3 changes: 2 additions & 1 deletion frontend/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const commonTSAndVueConfig = {
// TODO: Investigate why this rule reports false positives
'@typescript-eslint/no-misused-promises': 'off',
'no-secrets/no-secrets': 'error',
'@typescript-eslint/consistent-type-exports': 'error'
'@typescript-eslint/consistent-type-exports': 'error',
'@typescript-eslint/no-redundant-type-constituents': 'off'
}
};

Expand Down
48 changes: 2 additions & 46 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,12 @@
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-capable" content="yes">
<style>
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}

/**
* Light color scheme for splashscreen
*
*/

.light {
background: linear-gradient(
280deg,
#4f5b62,
#62727b,
#718792,
#819ca9,
#8eacbb
);
background: #f2f2f2;
}

/**
* Dark color scheme for splashscreen
*
*/

.dark {
background: linear-gradient(
280deg,
#111827,
#1c2741,
#332d41,
#4a2532,
#381e73,
#244415
);
}

@media (prefers-reduced-motion) {
.animation {
animation: gradient 30s linear infinite;
}
background: #111827;
}

.splashBackground {
Expand All @@ -72,7 +29,6 @@
width: 100%;
height: 100%;
background-size: 400% 400%;
animation: gradient 2s linear infinite;
z-index: 99999999;
}

Expand Down
6 changes: 2 additions & 4 deletions frontend/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@
"confirmPassword": "Confirm new password",
"connect": "Connect",
"contentType": "Content type",
"continueListening": "Continue listening",
"continueWatching": "Continue watching",
"copyPrompt": "Copy the following text into the clipboard?",
"copyStreamURL": "Copy Stream URL",
Expand Down Expand Up @@ -110,7 +109,6 @@
"endsAt": "Ends at {time}",
"eps": "EPs",
"failedSettingDisplayPreferences": "Unable to update display preferences.",
"failedToRefreshItems": "Failed to refresh items",
"favorite": "Favorite",
"features": "Features",
"filter": "Filter",
Expand Down Expand Up @@ -145,6 +143,7 @@
"lastActive": "Last active",
"lastActivityDate": "Last seen {value}",
"latestLibrary": "Latest {libraryName}",
"lazyLoading": "Showing {value} items. Loading more...",
"libraries": "Libraries",
"librariesSettingsDescription": "Manage libraries and their metadata",
"libraryAccess": "Library access",
Expand Down Expand Up @@ -239,6 +238,7 @@
"notifications": "Notifications",
"notificationsSettingsDescription": "Manage and configure notification sent by this server",
"numberTracks": "{number} tracks",
"offlineCantDoThisWillRetryWhenOnline": "You can't perform this action because you've lost the connection to the server. It will be retried once it's recovered.",
"operatingSystem": "Operating system",
"originalTitle": "Original title",
"overview": "Overview",
Expand Down Expand Up @@ -367,8 +367,6 @@
"transcodingAndStreaming": "Transcoding & streaming",
"transcodingSettingsDescription": "Manage how this server handles transcoding and streaming to clients",
"type": "Type",
"unableGetRelated": "Unable to get related items",
"unableToTogglePlayed": "Unable to update the watch status",
"unauthorized": "You're not authorized to access this page",
"unblockTag": "Unblock tag",
"undefined": "Undefined",
Expand Down
94 changes: 93 additions & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,101 @@
<template>
<Backdrop />
<VApp>
<RootView />
<RouterView v-slot="{ Component, route }">
<TransitionView>
<Suspense @resolve="apploaded = true">
<div
:key="route.meta.layout"
style="transform-origin: center"
class="h-100">
<component
:is="getLayoutComponent(route.meta.layout)"
:key="route.meta.layout">
<TransitionView>
<Suspense suspensible>
<div
:key="route.path"
style="transform-origin: center"
class="h-100">
<component
:is="Component"
:key="route.path" />
</div>
</Suspense>
</TransitionView>
</component>
</div>
</Suspense>
</TransitionView>
</RouterView>
<Snackbar />
<ConfirmDialog />
</VApp>
<PlayerElement />
</template>

<script setup lang="ts">
import DefaultLayout from '@/layouts/default.vue';
import FullPageLayout from '@/layouts/fullpage.vue';
import ServerLayout from '@/layouts/server.vue';
import { whenever } from '@vueuse/core';
import { ref, type Component as VueComponent } from 'vue';
import type { RouteMeta } from 'vue-router/auto';

const apploaded = ref(false);

/**
* - SPLASHSCREEN REMOVAL -
*
* Without window.setTimeout and window.requestAnimationFrame, the
* splash screen gets frozen an small (but noticeable) amount of time.
*
* Once we reach this point, all the async dependencies will be completely loaded and mounted,
* so we add a loadFinished class (defined in index.html) that fires the defined transition
* in the HTML markup to give a nice effect while hiding the splashscreen
*/
const stop = whenever(apploaded, () => {
window.setTimeout(() => {
window.requestAnimationFrame(() => {
const splashDOM = document.querySelector('.splashBackground');

if (!splashDOM) {
throw new Error('could not locate splash div in DOM');
}

splashDOM.addEventListener(
'transitionend',
() => {
window.setTimeout(() => {
window.requestAnimationFrame(() => {
splashDOM.remove();
stop();
});
});
},
{ once: true }
);

splashDOM.classList.add('loadFinished');
});
});
});

/**
* Return the appropiate layout component according to the route's meta.layout property
*/
function getLayoutComponent(layout: RouteMeta['layout']): VueComponent {
switch (layout) {
case 'fullpage': {
return FullPageLayout as VueComponent;
}
case 'server': {
return ServerLayout as VueComponent;
}
default: {
return DefaultLayout;
}
}

}
</script>
7 changes: 2 additions & 5 deletions frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
* {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
cursor: var(--j-client-cursor);
}

html {
Expand All @@ -27,15 +28,11 @@ html.no-forced-scrollbar {
bottom: 0;
}

.hide-pointer {
cursor: none;
}

.pointer {
cursor: pointer;
}

.cursor-none {
.no-cursor {
cursor: none;
}

Expand Down
35 changes: 13 additions & 22 deletions frontend/src/components/Buttons/LikeButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
</template>

<script setup lang="ts">
import { remote } from '@/plugins/remote';
import { useApi } from '@/composables/apis';
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { getUserLibraryApi } from '@jellyfin/sdk/lib/utils/api/user-library-api';
import IMdiHeart from 'virtual:icons/mdi/heart';
Expand All @@ -21,32 +21,23 @@ const props = withDefaults(
size: 'small'
}
);
const loading = ref(false);
/**
* We use the composables to handle when there's no connection to the server
*
* The websocket will automatically update the item in the store, so no need
* to do manual modification here
*/
const methodToExecute = ref<'markFavoriteItem' | 'unmarkFavoriteItem' | undefined>();
const { loading } = await useApi(getUserLibraryApi, methodToExecute, { skipCache: { request: true }, globalLoading: false })(() => ({
itemId: props.item.Id ?? ''
}));

const isFavorite = computed({
get() {
return props.item.UserData?.IsFavorite ?? false;
},
async set(newValue) {
try {
if (!props.item.Id) {
throw new Error('Item has no Id');
}

loading.value = true;

await (newValue
? remote.sdk.newUserApi(getUserLibraryApi).markFavoriteItem({
userId: remote.auth.currentUserId ?? '',
itemId: props.item.Id
})
: remote.sdk.newUserApi(getUserLibraryApi).unmarkFavoriteItem({
userId: remote.auth.currentUserId ?? '',
itemId: props.item.Id
}));
} catch {} finally {
loading.value = false;
}
set(newValue) {
methodToExecute.value = newValue ? 'markFavoriteItem' : 'unmarkFavoriteItem';
}
});
</script>
50 changes: 19 additions & 31 deletions frontend/src/components/Buttons/MarkPlayedButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,41 @@
v-if="canMarkWatched(item)"
:color="isPlayed ? 'primary' : undefined"
:icon="IMdiCheck"
:loading="loading"
size="small"
@click.stop.prevent="togglePlayed" />
@click.stop.prevent="isPlayed = !isPlayed" />
</template>

<script setup lang="ts">
import { useSnackbar } from '@/composables/use-snackbar';
import { remote } from '@/plugins/remote';
import { useApi } from '@/composables/apis';
import { canMarkWatched } from '@/utils/items';
import type { BaseItemDto } from '@jellyfin/sdk/lib/generated-client';
import { getPlaystateApi } from '@jellyfin/sdk/lib/utils/api/playstate-api';
import IMdiCheck from 'virtual:icons/mdi/check';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { computed, ref } from 'vue';

const props = defineProps<{
item: BaseItemDto;
}>();

const isPlayed = ref(props.item.UserData?.Played || false);

const { t } = useI18n();
const methodToExecute = ref<'markPlayedItem' | 'markUnplayedItem' | undefined>();

/**
* Toggles the played state of the given item
* We use the composables to handle when there's no connection to the server
*
* The websocket will automatically update the item in the store, so no need
* to do manual modification here
*/
async function togglePlayed(): Promise<void> {
try {
if (!props.item.Id) {
throw new Error('Item has no Id');
}
const { loading } = await useApi(getPlaystateApi, methodToExecute, { skipCache: { request: true }, globalLoading: false })(() => ({
itemId: props.item.Id ?? ''
}));

if (isPlayed.value) {
isPlayed.value = false;
await remote.sdk.newUserApi(getPlaystateApi).markUnplayedItem({
userId: remote.auth.currentUserId ?? '',
itemId: props.item.Id
});
} else {
isPlayed.value = true;
await remote.sdk.newUserApi(getPlaystateApi).markPlayedItem({
userId: remote.auth.currentUserId ?? '',
itemId: props.item.Id
});
}
} catch {
useSnackbar(t('unableToTogglePlayed'), 'error');
isPlayed.value = !isPlayed.value;
const isPlayed = computed({
get() {
return props.item.UserData?.Played;
},
set(newValue) {
methodToExecute.value = newValue ? 'markPlayedItem' : 'markUnplayedItem';
}
}
});
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
icon
:size="props.size"
:disabled="!playbackManager.nextItem"
@click="playbackManager.setNextTrack">
@click="playbackManager.setNextItem">
<VIcon :size="size">
<IMdiSkipNext />
</VIcon>
Expand Down
Loading
Loading