itube-specs 0.0.876 → 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="playMode = 'cycle'"
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="playMode = 'random'"
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 = useState<'cycle' | 'random'>('playlistMode', () => 'cycle');
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));
@@ -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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.876",
4
+ "version": "0.0.877",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {