itube-specs 0.0.892 → 0.0.895

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.
@@ -0,0 +1,366 @@
1
+ <!-- Библиотека: «продолжить просмотр», коллекции и подписки. Данные пока заглушка -->
2
+ <template>
3
+ <div class="library _page _container">
4
+ <UiBreadcrumbs
5
+ :items="breadcrumbs"
6
+ class="_breadcrumbs"
7
+ />
8
+
9
+ <div class="library__header">
10
+ <h1 class="library__title _title">{{ t('navigation.library') }}</h1>
11
+
12
+ <UiToggle
13
+ v-model="saveHistory"
14
+ class="library__save"
15
+ left
16
+ >{{ t('history_text') }}</UiToggle>
17
+ </div>
18
+
19
+ <section
20
+ v-if="continueWatching.length > 0"
21
+ class="library__section"
22
+ >
23
+ <header class="library__section-header">
24
+ <h2 class="library__heading">
25
+ <span class="library__heading-icon">
26
+ <UiIcon
27
+ name="history"
28
+ size="16"
29
+ />
30
+ </span>
31
+ {{ t('library.continue_watching') }}
32
+ </h2>
33
+
34
+ <UiLink
35
+ :to="generateLink('/history')"
36
+ theme="secondary"
37
+ >
38
+ {{ t('library.view_all_history') }}
39
+ <UiIcon
40
+ name="arrow-right"
41
+ size="16"
42
+ />
43
+ </UiLink>
44
+ </header>
45
+
46
+ <div class="library__continue">
47
+ <CardHistory
48
+ v-for="item in continueWatching"
49
+ :key="`continue-${item.guid}`"
50
+ :card="item"
51
+ loading="eager"
52
+ priority
53
+ remaining
54
+ @share="onShare"
55
+ @remove="onRemove"
56
+ />
57
+ </div>
58
+ </section>
59
+
60
+ <section class="library__section">
61
+ <header class="library__section-header">
62
+ <h2 class="library__heading">
63
+ <span class="library__heading-icon">
64
+ <UiIcon
65
+ name="layers"
66
+ size="16"
67
+ />
68
+ </span>
69
+ {{ t('library.collections') }}
70
+ </h2>
71
+ </header>
72
+
73
+ <ul class="library__grid">
74
+ <li
75
+ v-for="item in collections"
76
+ :key="item.title"
77
+ >
78
+ <LibraryItem
79
+ :to="item.to"
80
+ :icon="item.icon"
81
+ :title="item.title"
82
+ :subtitle="item.subtitle"
83
+ />
84
+ </li>
85
+ </ul>
86
+ </section>
87
+
88
+ <section class="library__section">
89
+ <header class="library__section-header">
90
+ <h2 class="library__heading">
91
+ <span class="library__heading-icon">
92
+ <UiIcon
93
+ name="plus"
94
+ size="16"
95
+ />
96
+ </span>
97
+ {{ t('library.following') }}
98
+ </h2>
99
+
100
+ <UiBtn theme="secondary">
101
+ {{ t('library.whats_new', { count: newTotal }) }}
102
+ <UiIcon
103
+ name="arrow-right"
104
+ size="16"
105
+ />
106
+ </UiBtn>
107
+ </header>
108
+
109
+ <ul class="library__grid">
110
+ <li
111
+ v-for="item in following"
112
+ :key="item.title"
113
+ >
114
+ <LibraryItem
115
+ :to="item.to"
116
+ :icon="item.icon"
117
+ :title="item.title"
118
+ :subtitle="item.subtitle"
119
+ :badge="item.badge"
120
+ :badge-note="item.badgeNote"
121
+ :badge-theme="item.badgeTheme"
122
+ />
123
+ </li>
124
+ </ul>
125
+ </section>
126
+
127
+ <ShareMain
128
+ v-model="isShareOpen"
129
+ :url="shareUrl"
130
+ >
131
+ <template #card>
132
+ <CardVideoMini
133
+ v-if="shareCard"
134
+ :card="shareCard"
135
+ />
136
+ </template>
137
+ </ShareMain>
138
+ </div>
139
+ </template>
140
+
141
+ <script setup lang="ts">
142
+ import { getHistoryStub } from '../../stubs/history';
143
+ import type { HistoryStubCard } from '../../stubs/history';
144
+ import { libraryStub } from '../../stubs/library';
145
+ import type { IBreadcrumbItem, IVideoCard } from '../../types';
146
+
147
+ /** Карточка-ссылка библиотеки в разметке страницы. */
148
+ type LibraryLink = {
149
+ to: string
150
+ icon: string
151
+ title: string
152
+ subtitle?: string
153
+ badge?: string
154
+ badgeNote?: string
155
+ badgeTheme?: 'accent' | 'bordered'
156
+ };
157
+
158
+ const CONTINUE_LIMIT = 4;
159
+
160
+ const { t } = useI18n();
161
+
162
+ const { generateLink } = useGenerateLink();
163
+
164
+ const saveHistory = ref(true);
165
+
166
+ const removedGuids = ref<string[]>([]);
167
+
168
+ /** Недосмотренные ролики: прогресс есть, но до конца не дошли. */
169
+ const continueWatching = computed(() => getHistoryStub()
170
+ .filter(item => item.progress > 0 && item.progress < 100)
171
+ .filter(item => !removedGuids.value.includes(item.guid))
172
+ .slice(0, CONTINUE_LIMIT));
173
+
174
+ const newTotal = computed(() => libraryStub.newModels + libraryStub.newChannels + libraryStub.newPlaylists);
175
+
176
+ const collections = computed<LibraryLink[]>(() => [
177
+ {
178
+ to: generateLink('/favorites'),
179
+ icon: 'heart-stroke',
180
+ title: t('favorites'),
181
+ subtitle: t('plural.videos', libraryStub.favorites),
182
+ },
183
+ {
184
+ to: `${generateLink('/favorites')}?tab=watch-later`,
185
+ icon: 'clock',
186
+ title: t('watch_later'),
187
+ subtitle: t('plural.videos', libraryStub.watchLater),
188
+ },
189
+ {
190
+ to: generateLink('/playlists'),
191
+ icon: 'playlist',
192
+ title: t('my_playlists'),
193
+ subtitle: t('library.playlists_visibility', {
194
+ privateCount: libraryStub.privatePlaylists,
195
+ publicCount: libraryStub.publicPlaylists,
196
+ }),
197
+ },
198
+ {
199
+ to: generateLink('/history'),
200
+ icon: 'history',
201
+ title: t('history'),
202
+ subtitle: t('library.watched_today', { count: libraryStub.watchedToday }),
203
+ },
204
+ ]);
205
+
206
+ const following = computed<LibraryLink[]>(() => [
207
+ {
208
+ to: generateLink('/library/models'),
209
+ icon: 'user',
210
+ title: t('models'),
211
+ subtitle: t('plural.models', libraryStub.models),
212
+ badge: t('library.new_count', { count: libraryStub.newModels }),
213
+ },
214
+ {
215
+ to: generateLink('/library/channels'),
216
+ icon: 'play-circle',
217
+ title: t('channels'),
218
+ subtitle: t('plural.channels', libraryStub.channels),
219
+ badge: t('library.new_count', { count: libraryStub.newChannels }),
220
+ },
221
+ {
222
+ to: generateLink('/library/categories'),
223
+ icon: 'grid',
224
+ title: t('categories'),
225
+ subtitle: t('plural.categories', libraryStub.categories),
226
+ badge: `+${libraryStub.categoryVideosPerWeek}`,
227
+ badgeNote: t('library.this_week'),
228
+ badgeTheme: 'bordered',
229
+ },
230
+ {
231
+ to: generateLink('/playlists'),
232
+ icon: 'playlist',
233
+ title: t('playlists'),
234
+ subtitle: t('plural.playlists', libraryStub.playlists),
235
+ badge: t('library.new_count', { count: libraryStub.newPlaylists }),
236
+ },
237
+ ]);
238
+
239
+ function onRemove(card: HistoryStubCard) {
240
+ removedGuids.value = [...removedGuids.value, card.guid];
241
+ }
242
+
243
+ const isShareOpen = ref(false);
244
+ const shareCard = ref<IVideoCard | null>(null);
245
+
246
+ const shareUrl = computed(() => shareCard.value ? generateLink(`/videos/${shareCard.value.id}`) : '');
247
+
248
+ function onShare(card: IVideoCard) {
249
+ shareCard.value = card;
250
+ isShareOpen.value = true;
251
+ }
252
+
253
+ useNoindex();
254
+
255
+ const { brandName } = useAppConfig();
256
+
257
+ useHead(() => ({
258
+ title: `${t('navigation.library')} — ${brandName}`,
259
+ }));
260
+
261
+ const breadcrumbs = [
262
+ {
263
+ name: `${t('navigation.main')}`,
264
+ to: '/',
265
+ },
266
+ {
267
+ name: `${t('navigation.library')}`,
268
+ },
269
+ ] as IBreadcrumbItem[];
270
+ </script>
271
+
272
+ <style lang="scss">
273
+ .library__header {
274
+ display: flex;
275
+ flex-wrap: wrap;
276
+ align-items: center;
277
+ justify-content: space-between;
278
+ column-gap: 16px;
279
+ row-gap: 12px;
280
+ margin-bottom: 24px;
281
+ }
282
+
283
+ .library__title._title {
284
+ margin-bottom: 0;
285
+ }
286
+
287
+ .library__save .ui-toggle__text {
288
+ @include font-sm;
289
+
290
+ color: $page-library-toggle-text;
291
+ }
292
+
293
+ .library__section {
294
+ margin-bottom: 48px;
295
+ }
296
+
297
+ .library__section-header {
298
+ display: flex;
299
+ flex-wrap: wrap;
300
+ align-items: center;
301
+ justify-content: space-between;
302
+ column-gap: 16px;
303
+ row-gap: 12px;
304
+ margin-bottom: 16px;
305
+ }
306
+
307
+ .library__heading {
308
+ @include font-xl;
309
+
310
+ display: flex;
311
+ align-items: center;
312
+ column-gap: 8px;
313
+ margin: 0;
314
+ font-weight: 600;
315
+ color: $page-library-heading;
316
+ }
317
+
318
+ .library__heading-icon {
319
+ display: grid;
320
+ place-content: center;
321
+ flex-shrink: 0;
322
+ width: 24px;
323
+ height: 24px;
324
+ color: $page-library-heading-icon;
325
+ background-color: $page-library-heading-icon-bg;
326
+ border-radius: $radius-md;
327
+ }
328
+
329
+ .library__continue {
330
+ display: grid;
331
+ gap: 16px;
332
+ grid-template-columns: 1fr;
333
+
334
+ @include from-br(xm) {
335
+ grid-template-columns: repeat(2, 1fr);
336
+ }
337
+
338
+ @include from-br(sm) {
339
+ gap: 24px;
340
+ grid-template-columns: repeat(3, 1fr);
341
+ }
342
+
343
+ @include from-br(md) {
344
+ grid-template-columns: repeat(4, 1fr);
345
+ }
346
+ }
347
+
348
+ .library__grid {
349
+ display: grid;
350
+ gap: 16px;
351
+ margin: 0;
352
+ padding: 0;
353
+ list-style: none;
354
+ grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
355
+
356
+ @include from-br(sm) {
357
+ gap: 24px;
358
+ grid-template-columns: repeat(4, 1fr);
359
+ }
360
+ }
361
+
362
+ /* Карточка тянется на всю высоту ряда — иначе соседи с двухстрочным тайтлом разной высоты. */
363
+ .library__grid > li {
364
+ display: grid;
365
+ }
366
+ </style>
@@ -0,0 +1,63 @@
1
+ <!-- Подписки на моделей: аватар, теги, лента последних роликов. Данные пока заглушка -->
2
+ <template>
3
+ <div class="_page _container">
4
+ <UiBreadcrumbs
5
+ :items="breadcrumbs"
6
+ class="_breadcrumbs"
7
+ />
8
+
9
+ <LibraryFollows
10
+ :items="items"
11
+ :title="t('library.my_models')"
12
+ icon="user"
13
+ />
14
+ </div>
15
+ </template>
16
+
17
+ <script setup lang="ts">
18
+ import { getLibraryModelsStub } from '../../stubs/library-follows';
19
+ import { convertString, formatDate } from '../../runtime';
20
+ import type { IBreadcrumbItem, ILibraryFeedItem } from '../../types';
21
+
22
+ const { t } = useI18n();
23
+ const { generateLink } = useGenerateLink();
24
+
25
+ const items = computed<ILibraryFeedItem[]>(() => getLibraryModelsStub().map(model => ({
26
+ guid: model.guid,
27
+ title: model.title,
28
+ to: generateLink(`/models/${convertString().toSlug(model.title)}`),
29
+ avatarUrl: model.avatarUrl,
30
+ country: model.country,
31
+ tags: model.tags.map(tag => convertString().toChips(tag, 'tag')),
32
+ newCount: model.newCount,
33
+ meta: [
34
+ { icon: 'video-camera', text: String(model.videosCount) },
35
+ { text: `${model.age} ${t('yo')}` },
36
+ { icon: 'date', text: t('library.updated_at', { date: formatDate(model.lastVideoAddedAt) }) },
37
+ ],
38
+ videosCount: model.videosCount,
39
+ videos: model.videos,
40
+ })));
41
+
42
+ useNoindex();
43
+
44
+ const { brandName } = useAppConfig();
45
+
46
+ useHead(() => ({
47
+ title: `${t('library.my_models')} — ${brandName}`,
48
+ }));
49
+
50
+ const breadcrumbs = [
51
+ {
52
+ name: `${t('navigation.main')}`,
53
+ to: '/',
54
+ },
55
+ {
56
+ name: `${t('navigation.library')}`,
57
+ to: '/library',
58
+ },
59
+ {
60
+ name: `${t('library.my_models')}`,
61
+ },
62
+ ] as IBreadcrumbItem[];
63
+ </script>