itube-specs 0.0.875 → 0.0.877
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
:class="{'--active': playMode === 'cycle'}"
|
|
59
59
|
class="player-playlist__play-button"
|
|
60
60
|
type="button"
|
|
61
|
-
@click="
|
|
61
|
+
@click="setPlayMode('cycle')"
|
|
62
62
|
>
|
|
63
63
|
<UiIcon
|
|
64
64
|
name="cycle"
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
:class="{'--active': playMode === 'random'}"
|
|
70
70
|
class="player-playlist__play-button"
|
|
71
71
|
type="button"
|
|
72
|
-
@click="
|
|
72
|
+
@click="setPlayMode('random')"
|
|
73
73
|
>
|
|
74
74
|
<UiIcon
|
|
75
75
|
name="random"
|
|
@@ -188,7 +188,7 @@ const userProfileData = computed(() => useState<IProfileData>('profileData').val
|
|
|
188
188
|
const isOwner = computed(() => isAuthorized.value && !!userProfileData.value?.username && userProfileData.value.username === props.user);
|
|
189
189
|
|
|
190
190
|
const slug = useSlug();
|
|
191
|
-
const playMode
|
|
191
|
+
const { mode: playMode, setMode: setPlayMode } = usePlaylistMode();
|
|
192
192
|
|
|
193
193
|
const currentIndex = computed(() => {
|
|
194
194
|
const idx = visibleItems.value.findIndex(item => String(item.id) === String(slug.value));
|
|
@@ -8,8 +8,41 @@
|
|
|
8
8
|
wide
|
|
9
9
|
size="l"
|
|
10
10
|
theme="warning"
|
|
11
|
-
@click="
|
|
11
|
+
@click="isConfirmOpen = true"
|
|
12
12
|
>{{ t('profile.delete_profile') }}</UiBtn>
|
|
13
|
+
|
|
14
|
+
<transition mode="out-in">
|
|
15
|
+
<UiPopup
|
|
16
|
+
v-if="isConfirmOpen"
|
|
17
|
+
v-model="isConfirmOpen"
|
|
18
|
+
sheet
|
|
19
|
+
>
|
|
20
|
+
<template #title>{{ $t('profile.delete_profile') }}</template>
|
|
21
|
+
|
|
22
|
+
<p class="profile-delete__confirm">{{ $t('confirm_delete_profile') }}</p>
|
|
23
|
+
|
|
24
|
+
<template #footer>
|
|
25
|
+
<div class="profile-delete__buttons">
|
|
26
|
+
<UiBtn
|
|
27
|
+
:disabled="loading"
|
|
28
|
+
wide
|
|
29
|
+
size="l"
|
|
30
|
+
theme="secondary"
|
|
31
|
+
@click="isConfirmOpen = false"
|
|
32
|
+
>{{ $t('cancel') }}
|
|
33
|
+
</UiBtn>
|
|
34
|
+
<UiBtn
|
|
35
|
+
:disabled="loading"
|
|
36
|
+
wide
|
|
37
|
+
size="l"
|
|
38
|
+
theme="warning"
|
|
39
|
+
@click="deleteProfile"
|
|
40
|
+
>{{ $t('yes') }}
|
|
41
|
+
</UiBtn>
|
|
42
|
+
</div>
|
|
43
|
+
</template>
|
|
44
|
+
</UiPopup>
|
|
45
|
+
</transition>
|
|
13
46
|
</div>
|
|
14
47
|
</template>
|
|
15
48
|
|
|
@@ -21,12 +54,14 @@ const { signOut } = useUser(AuthorizationApiService);
|
|
|
21
54
|
const { showSuccess } = useSnackbar();
|
|
22
55
|
|
|
23
56
|
const loading = ref(false);
|
|
57
|
+
const isConfirmOpen = ref(false);
|
|
24
58
|
|
|
25
59
|
async function deleteProfile() {
|
|
26
60
|
try {
|
|
27
61
|
loading.value = true;
|
|
28
62
|
await AuthorizationApiService.deleteProfile();
|
|
29
|
-
|
|
63
|
+
isConfirmOpen.value = false;
|
|
64
|
+
showSuccess('profile_deleted');
|
|
30
65
|
signOut();
|
|
31
66
|
} catch (err) {
|
|
32
67
|
console.log(err, 'error delete profile');
|
|
@@ -59,4 +94,20 @@ async function deleteProfile() {
|
|
|
59
94
|
.profile-delete__button {
|
|
60
95
|
height: 50px;
|
|
61
96
|
}
|
|
97
|
+
|
|
98
|
+
.profile-delete__confirm {
|
|
99
|
+
@include font-sm;
|
|
100
|
+
|
|
101
|
+
text-align: center;
|
|
102
|
+
max-width: 400px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.profile-delete__buttons {
|
|
106
|
+
display: grid;
|
|
107
|
+
gap: 8px;
|
|
108
|
+
|
|
109
|
+
@include from-br(sm) {
|
|
110
|
+
grid-template-columns: 1fr 1fr;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
62
113
|
</style>
|
|
@@ -196,7 +196,7 @@ async function deleteAvatar() {
|
|
|
196
196
|
form.value.avatar = '';
|
|
197
197
|
await getProfile(true);
|
|
198
198
|
avatarPreview.value = '';
|
|
199
|
-
showSuccess(
|
|
199
|
+
showSuccess('avatar_deleted');
|
|
200
200
|
} finally {
|
|
201
201
|
deletingAvatar.value = false;
|
|
202
202
|
}
|
|
@@ -34,6 +34,12 @@ export function useNextVideo({ data, suggestedVideos, basePath }: UseNextVideoPa
|
|
|
34
34
|
const cardKey = computed<'url' | 'id'>(() => featureFlags.FetchByUrlEnabled ? 'url' : 'id');
|
|
35
35
|
const currentCardId = computed(() => String(route.query.view || data()?.[cardKey.value] || ''));
|
|
36
36
|
|
|
37
|
+
// Режим воспроизведения плейлиста (тумблеры cycle/random в PlayerPlaylist).
|
|
38
|
+
// Random работает только на странице плейлиста — на обычной странице видео related
|
|
39
|
+
// всегда идёт по порядку.
|
|
40
|
+
const { mode: playMode } = usePlaylistMode();
|
|
41
|
+
const isRandom = computed(() => !!basePath() && playMode.value === 'random');
|
|
42
|
+
|
|
37
43
|
const currentItems = computed<IVideoCard[]>(() => suggestedVideos()?.items ?? []);
|
|
38
44
|
const currentPage = computed(() => suggestedVideos()?.currentPage ?? 1);
|
|
39
45
|
const allPages = computed(() => suggestedVideos()?.allPages ?? 1);
|
|
@@ -51,12 +57,27 @@ export function useNextVideo({ data, suggestedVideos, basePath }: UseNextVideoPa
|
|
|
51
57
|
nextPageItems.value = [];
|
|
52
58
|
}, { immediate: true });
|
|
53
59
|
|
|
60
|
+
// Случайный ролик в режиме random. Индекс фиксируем в ref и перекатываем при смене
|
|
61
|
+
// текущего ролика/списка/режима — чтобы ховер-превью и реальный переход вели на один
|
|
62
|
+
// и тот же ролик. Только на клиенте: Math.random в SSR даст рассинхрон гидрации.
|
|
63
|
+
// Текущий ролик в playlistUpcoming уже исключён, так что попасть в себя нельзя.
|
|
64
|
+
const randomIndex = ref(0);
|
|
65
|
+
|
|
66
|
+
if (import.meta.client) {
|
|
67
|
+
watch([currentCardId, currentItems, isRandom], () => {
|
|
68
|
+
const length = currentItems.value.length;
|
|
69
|
+
randomIndex.value = length > 1 ? Math.floor(Math.random() * length) : 0;
|
|
70
|
+
}, { immediate: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
// Следующий ролик: за текущим в текущем листе; на конце листа — первый следующей
|
|
55
74
|
// страницы (если догружена); на самом конце всех страниц — первый ролик вообще.
|
|
56
75
|
const nextVideo = computed<IVideoCard | undefined>(() => {
|
|
57
76
|
const items = currentItems.value;
|
|
58
77
|
if (!items.length) return undefined;
|
|
59
78
|
|
|
79
|
+
if (isRandom.value) return items[randomIndex.value] ?? items[0];
|
|
80
|
+
|
|
60
81
|
const idx = currentIndex.value;
|
|
61
82
|
if (idx === -1) return items[0];
|
|
62
83
|
if (idx + 1 < items.length) return items[idx + 1];
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type PlaylistMode = 'cycle' | 'random';
|
|
2
|
+
|
|
3
|
+
const STORAGE_KEY = 'playlist-mode';
|
|
4
|
+
|
|
5
|
+
function isPlaylistMode(value: unknown): value is PlaylistMode {
|
|
6
|
+
return value === 'cycle' || value === 'random';
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Режим воспроизведения плейлиста (cycle/random). Состояние общее для тумблеров в
|
|
11
|
+
* PlayerPlaylist и логики next в useNextVideo, переживает перезагрузку — хранится в
|
|
12
|
+
* localStorage.
|
|
13
|
+
*
|
|
14
|
+
* Восстанавливаем в onMounted, а не в setup: на сервере localStorage нет, а правка
|
|
15
|
+
* значения до гидрации разошлась бы с отрендеренной разметкой (--active на тумблере).
|
|
16
|
+
*/
|
|
17
|
+
export function usePlaylistMode() {
|
|
18
|
+
const mode = useState<PlaylistMode>('playlistMode', () => 'cycle');
|
|
19
|
+
|
|
20
|
+
onMounted(() => {
|
|
21
|
+
const saved = localStorage.getItem(STORAGE_KEY);
|
|
22
|
+
if (isPlaylistMode(saved)) mode.value = saved;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function setMode(value: PlaylistMode): void {
|
|
26
|
+
mode.value = value;
|
|
27
|
+
if (import.meta.client) localStorage.setItem(STORAGE_KEY, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { mode, setMode };
|
|
31
|
+
}
|
package/package.json
CHANGED
package/pages/user/profile.vue
CHANGED
|
@@ -108,16 +108,6 @@ const items = computed<Array<IDataItem>>(() => {
|
|
|
108
108
|
text: data.value.gender?.title ?? '',
|
|
109
109
|
icon: 'double-user'
|
|
110
110
|
},
|
|
111
|
-
{
|
|
112
|
-
title: t('profile.gender'),
|
|
113
|
-
text: data.value.gender?.title ?? '',
|
|
114
|
-
icon: 'double-user'
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
title: t('profile.gender'),
|
|
118
|
-
text: data.value.gender?.title ?? '',
|
|
119
|
-
icon: 'double-user'
|
|
120
|
-
},
|
|
121
111
|
];
|
|
122
112
|
});
|
|
123
113
|
|