itube-specs 0.0.339 → 0.0.340

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,186 @@
1
+ <template>
2
+ <div class="s-playlist-add__decorate _to-sm" v-if="isPlaylistAdd"/>
3
+ <!-- Add-->
4
+ <transition mode="out-in">
5
+ <SPopup
6
+ v-if="isPlaylistAdd && step === 'add'"
7
+ v-model="isPlaylistAdd"
8
+ sheet
9
+ transparent-backdrop
10
+ @close="closePopup"
11
+ >
12
+ <template #title>{{ $t('playlist.add') }}</template>
13
+
14
+ <div
15
+ class="s-playlist-add__wrapper"
16
+ :class="{'_loading': loadingUserPlaylists || loadingPostVideo}"
17
+ >
18
+ <SVideoMiniCard
19
+ v-if="videoCard"
20
+ class="s-playlist-add__mini-card"
21
+ :card="videoCard"
22
+ />
23
+ <template
24
+ v-if="playlistsItems && playlistsItems.length"
25
+ >
26
+ <SSelect
27
+ name="add-playlist"
28
+ v-model="selectValue"
29
+ :items="playlistsItems"
30
+ :placeholder="$t('choose_playlist')"
31
+ />
32
+ <p class="s-playlist-add__text">{{ $t('playlist.create_new') }}</p>
33
+ </template>
34
+ <button
35
+ type="button"
36
+ class="s-playlist-add__new"
37
+ @click="step = 'new'"
38
+ >
39
+ <SIcon
40
+ class="s-playlist-add__new-icon"
41
+ name="follow"
42
+ size="24"
43
+ />
44
+ {{ $t('playlist.new_playlist') }}
45
+ </button>
46
+ </div>
47
+
48
+ <template #footer>
49
+ <div class="s-playlist-add__buttons">
50
+ <SButton
51
+ wide
52
+ :disabled="loadingUserPlaylists || loadingPostVideo"
53
+ theme="secondary"
54
+ @click="closePopup"
55
+ >{{ $t('cancel') }}
56
+ </SButton>
57
+ <SButton
58
+ wide
59
+ :disabled="loadingUserPlaylists || loadingPostVideo"
60
+ @click="onSaveClick"
61
+ >{{ $t('add') }}
62
+ </SButton>
63
+ </div>
64
+ </template>
65
+ </SPopup>
66
+ </transition>
67
+ <!-- Add-->
68
+
69
+ <!-- New-->
70
+ <transition mode="out-in">
71
+ <SPopup
72
+ v-if="isPlaylistAdd && step === 'new'"
73
+ v-model="isPlaylistAdd"
74
+ sheet
75
+ transparent-backdrop
76
+ back
77
+ @close="closePopup"
78
+ @back="backPopup"
79
+ >
80
+ <template #title>{{ $t('playlist.new_playlist') }}</template>
81
+ <SPlaylistNew
82
+ @close="closePopup"
83
+ @post="postPlaylist"
84
+ :loading-post-playlist="loadingPostPlaylist"
85
+ />
86
+ <slot></slot>
87
+ </SPopup>
88
+ </transition>
89
+ <!-- New-->
90
+ </template>
91
+
92
+ <script setup lang="ts">
93
+ import type { IPlaylistVideoFormType, ISelectItem, IPlaylistShort, IPlaylistCard, PaginatedResponse } from '../../types';
94
+
95
+ const props = defineProps<{
96
+ loadingPostPlaylist: boolean
97
+ loadingUserPlaylists: boolean
98
+ loadingPostVideo: boolean
99
+ userPlaylists: PaginatedResponse<IPlaylistCard> | null
100
+ }>()
101
+
102
+ const route = useRoute();
103
+
104
+ const emit = defineEmits<{
105
+ (eventName: 'post-playlist', eventValue: IPlaylistShort): void
106
+ (eventName: 'back-to-add'): void
107
+ (eventName: 'execute-user-playlists'): void
108
+ (eventName: 'save-video-to-playlist', eventValue: {
109
+ form: IPlaylistVideoFormType
110
+ id: string
111
+ }): void
112
+ }>()
113
+
114
+ const { videoCard, closePlaylistAdd, isPlaylistAdd, selectValue, step } = usePlaylistAdd();
115
+
116
+ const playlistsItems = computed<ISelectItem[] | undefined>(() => {
117
+ return props.userPlaylists?.items?.map((item) => ({
118
+ title: item.name,
119
+ value: item.id,
120
+ }))
121
+ })
122
+
123
+ const id = computed(() => String(route.params['playlistId']));
124
+ const activePlaylist = computed(() => playlistsItems.value?.find(item => item.value === selectValue.value)?.value);
125
+ const playlistId = computed(() => activePlaylist.value ? [activePlaylist.value] : id.value);
126
+
127
+ emit('execute-user-playlists');
128
+
129
+ async function closePopup() {
130
+ step.value = 'add';
131
+ isPlaylistAdd.value = false;
132
+ closePlaylistAdd();
133
+ }
134
+
135
+ async function postPlaylist(event: IPlaylistShort) {
136
+ emit('post-playlist', event);
137
+ }
138
+
139
+ async function backPopup() {
140
+ step.value = 'add';
141
+ }
142
+
143
+ const postVideoForm = computed(() => ({
144
+ playlistId: activePlaylist.value ? [activePlaylist.value] : undefined,
145
+ videoId: videoCard.value?.id ?? '',
146
+ VideoGUID: videoCard.value?.guid ?? '',
147
+ videoMd5: videoCard.value?.md5 ?? '',
148
+ thumbNum: videoCard.value?.thumbNum ?? 0,
149
+ name: videoCard.value?.title ?? '',
150
+ duration: videoCard.value?.duration ?? 0,
151
+ }));
152
+
153
+ const { setErrorState, snackbarText, snackbarTheme } = useSnackbar();
154
+
155
+ async function onSaveClick() {
156
+ try {
157
+ emit('save-video-to-playlist', {
158
+ form: postVideoForm.value as IPlaylistVideoFormType,
159
+ id: String(playlistId.value)
160
+ })
161
+ closePopup();
162
+ snackbarTheme.value = 'success';
163
+ snackbarText.value = 'video_added';
164
+ } catch (error) {
165
+ setErrorState(error)
166
+ console.log(error, 'error add video to playlist')
167
+ }
168
+ }
169
+
170
+ watch(
171
+ playlistsItems,
172
+ (items) => {
173
+ if (!items?.length) return;
174
+
175
+ const routeId = String(route.params['playlistId'] ?? '');
176
+ const fromRoute = items.find(item => item.value === routeId);
177
+ if (fromRoute) {
178
+ selectValue.value = fromRoute.value;
179
+ return;
180
+ }
181
+
182
+ selectValue.value = items[0].value;
183
+ },
184
+ { immediate: true }
185
+ );
186
+ </script>
@@ -0,0 +1,77 @@
1
+ <template>
2
+ <transition mode="out-in">
3
+ <SPopup
4
+ v-model="isPlaylistEditOpen"
5
+ v-if="isPlaylistEditOpen && currentStep === EPlaylistStep.DeleteVideo"
6
+ sheet
7
+ transparent-backdrop
8
+ >
9
+ <template #title>{{ $t('playlist.delete_video') }}</template>
10
+
11
+ <div
12
+ class="s-playlist-delete-video__wrapper"
13
+ :class="{'_loading': loadingAll}"
14
+ >
15
+ <SVideoMiniCard v-if="deletedVideo" :card="deletedVideo"/>
16
+ <p class="s-playlist-delete-video__text">{{ $t('playlist.confirm_delete_video') }}</p>
17
+ </div>
18
+ <template #footer>
19
+ <div class="s-playlist-delete-video__buttons">
20
+ <SButton
21
+ :disabled="loadingAll"
22
+ wide
23
+ theme="secondary"
24
+ @click="closePlaylistEdit"
25
+ >{{ $t('cancel') }}
26
+ </SButton>
27
+ <SButton
28
+ :disabled="loadingAll"
29
+ wide
30
+ @click="onBinClick"
31
+ >{{ $t('playlist.delete') }}
32
+ </SButton>
33
+ </div>
34
+ </template>
35
+
36
+ </SPopup>
37
+ </transition>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { EPlaylistStep } from '../../runtime';
42
+
43
+ const { isPlaylistEditOpen, closePlaylistEdit, currentStep, selectedPlaylist, deletedVideo } = usePlaylistEdit();
44
+ const { snackbarText, snackbarTheme } = useSnackbar();
45
+
46
+ const props = defineProps<{
47
+ loadingDeleteVideo: boolean
48
+ loadingPlaylistVideos: boolean
49
+ }>()
50
+
51
+ const route = useRoute();
52
+
53
+ const emit = defineEmits<{
54
+ (eventName: 'delete-video', eventValue: {
55
+ playlistId: string
56
+ deletedVideoId: string
57
+ }): void
58
+ }>()
59
+
60
+ const playlistId = computed(() => route.params[ 'playlistId' ] ? String(route.params[ 'playlistId' ]) : selectedPlaylist.value?.id || '');
61
+
62
+ async function onBinClick() {
63
+ try {
64
+ emit('delete-video', {
65
+ playlistId: playlistId.value,
66
+ deletedVideoId: deletedVideo.value?.id || ''
67
+ })
68
+ snackbarTheme.value = 'success';
69
+ snackbarText.value = 'video_deleted';
70
+ closePlaylistEdit();
71
+ } catch {
72
+ console.log('error delete')
73
+ }
74
+ }
75
+
76
+ const loadingAll = computed(() => props.loadingDeleteVideo || props.loadingPlaylistVideos)
77
+ </script>
@@ -0,0 +1,211 @@
1
+ <template>
2
+ <div
3
+ class="s-playlist-edit__decorate"
4
+ v-if="isPlaylistEditOpen"
5
+ />
6
+
7
+ <!-- Delete-->
8
+ <transition mode="out-in">
9
+ <SPopup
10
+ v-model="isPlaylistEditOpen"
11
+ v-if="isPlaylistEditOpen && currentStep === EPlaylistStep.Delete"
12
+ sheet
13
+ transparent-backdrop
14
+ >
15
+ <template #title>{{ $t('playlist.delete_playlist') }}</template>
16
+
17
+ <div
18
+ class="s-playlist-edit__wrapper"
19
+ :class="{'_loading': loadingAll}"
20
+ >
21
+ <SIcon
22
+ class="s-playlist-edit__icon"
23
+ name="trash-bin"
24
+ size="40"
25
+ />
26
+ <p class="s-playlist-edit__text">{{ $t('playlist.confirm_text', { name: `${selectedPlaylist?.name}` }) }}</p>
27
+ </div>
28
+ <template #footer>
29
+ <div class="s-playlist-edit__buttons">
30
+ <SButton
31
+ :disabled="loadingAll"
32
+ wide
33
+ theme="secondary"
34
+ @click="closePlaylistEdit"
35
+ >{{ $t('cancel') }}
36
+ </SButton>
37
+ <SButton
38
+ :disabled="loadingAll"
39
+ wide
40
+ @click="onDeleteClick"
41
+ >{{ $t('playlist.delete') }}
42
+ </SButton>
43
+ </div>
44
+ </template>
45
+
46
+ </SPopup>
47
+ </transition>
48
+ <!-- Delete-->
49
+
50
+ <!-- Edit-->
51
+ <transition mode="out-in">
52
+ <SPopup
53
+ v-model="isPlaylistEditOpen"
54
+ v-if="isPlaylistEditOpen && currentStep === EPlaylistStep.Edit && data"
55
+ transparent-backdrop
56
+ >
57
+ <template #title>{{ $t('playlist.edit_playlist') }}</template>
58
+
59
+ <template #fixedContent>
60
+ <div class="s-playlist-edit__subheader-wrapper">
61
+ <SInput
62
+ :label="$t('playlist.playlist')"
63
+ v-model="inputValue"
64
+ />
65
+ <SPlaylistPrivateToggle v-model="toggleValue"/>
66
+ </div>
67
+ </template>
68
+
69
+ <div
70
+ v-if="data && data.items.length"
71
+ class="s-playlist-edit__grid"
72
+ :class="{'_loading': loadingAll}"
73
+ >
74
+ <div
75
+ v-for="(card, index) in data.items"
76
+ class="s-playlist-edit__card-wrapper"
77
+ :key="`playlist-edit-${index}`"
78
+ >
79
+ <SVideoMiniCard
80
+ class="s-playlist-edit__card"
81
+ :card="card"
82
+ />
83
+ <button
84
+ class="s-playlist-edit__delete"
85
+ type="button"
86
+ @click="openPlaylistEdit(EPlaylistStep.DeleteVideo, true, selectedPlaylist, card)"
87
+ >
88
+ <SIcon name="trash-bin" size="24"/>
89
+ </button>
90
+ </div>
91
+ </div>
92
+
93
+ <template #footer>
94
+ <div class="s-playlist-edit__buttons">
95
+ <SButton
96
+ wide
97
+ :disabled="loadingAll"
98
+ theme="secondary"
99
+ @click="closePlaylistEdit"
100
+ >{{ $t('cancel') }}
101
+ </SButton>
102
+ <SButton
103
+ :disabled="loadingAll"
104
+ wide
105
+ @click="onSaveClick"
106
+ >{{ $t('save') }}
107
+ </SButton>
108
+ </div>
109
+ </template>
110
+ <!-- Edit-->
111
+
112
+ </SPopup>
113
+ </transition>
114
+ </template>
115
+
116
+ <script setup lang="ts">
117
+ import { EPlaylistStep, EPlaylistType, ELanguage, EAsyncData } from '../../runtime';
118
+ import type { IVideoCard, PaginatedResponse, IPlaylistData } from '../../types';
119
+
120
+ const props = defineProps<{
121
+ loadingPlaylists: boolean
122
+ loadingRefresh: boolean
123
+ loadingEditVideos: boolean
124
+ loadingPlaylistDelete: boolean
125
+ }>()
126
+
127
+ const emit = defineEmits<{
128
+ (eventName: 'delete-playlist', eventValue: string): void
129
+ (eventName: 'open-edit', eventValue: string): void
130
+ (eventName: 'save-edit', eventValue: {
131
+ id: string
132
+ playlistName: string
133
+ playlistType: EPlaylistType
134
+ }): void
135
+ }>()
136
+
137
+ const inputValue = ref('');
138
+ const toggleValue = ref(false);
139
+
140
+ const { isPlaylistEditOpen, closePlaylistEdit, currentStep, selectedPlaylist, openPlaylistEdit } = usePlaylistEdit();
141
+ const { snackbarText, snackbarTheme } = useSnackbar();
142
+
143
+ const route = useRoute();
144
+ const id = computed(() => route.params[ 'playlistId' ] ? String(route.params[ 'playlistId' ]) : selectedPlaylist.value?.id);
145
+
146
+ const videosStateKey = computed(() =>
147
+ id.value ? `data-${EAsyncData.GetPlaylistsVideo}-${id.value}` : null
148
+ );
149
+
150
+ const data = computed<PaginatedResponse<IVideoCard> | null>(() => {
151
+ return useState<PaginatedResponse<IVideoCard> | null>(
152
+ videosStateKey.value || '',
153
+ () => null
154
+ ).value
155
+ });
156
+
157
+ const playlistsData = useState<IPlaylistData>(`data-${EAsyncData.PlaylistById}-${id.value}`);
158
+
159
+ async function onDeleteClick() {
160
+ try {
161
+ emit('delete-playlist', id.value || '')
162
+ snackbarTheme.value = 'success';
163
+ snackbarText.value = 'playlist_deleted';
164
+ closePlaylistEdit();
165
+ } catch {
166
+ console.log('error delete')
167
+ }
168
+ }
169
+
170
+ async function onSaveClick() {
171
+ try {
172
+ const playlistType = toggleValue.value ? EPlaylistType.Private : EPlaylistType.Public;
173
+
174
+ emit('save-edit', {
175
+ id: id.value || '',
176
+ playlistName: inputValue.value,
177
+ playlistType: playlistType,
178
+ })
179
+
180
+ if (playlistsData.value) {
181
+ playlistsData.value.name = inputValue.value ? inputValue.value : playlistsData.value.name;
182
+ playlistsData.value.playlistType = playlistType;
183
+ }
184
+ closePlaylistEdit();
185
+ snackbarTheme.value= 'success';
186
+ snackbarText.value = 'changes_soon';
187
+ } catch {
188
+ console.log('error save')
189
+ }
190
+ }
191
+
192
+ watch(
193
+ () => [isPlaylistEditOpen.value, currentStep.value, id.value],
194
+ ([open, step, playlistId]) => {
195
+ if (!open || step !== EPlaylistStep.Edit || !playlistId) return;
196
+
197
+ emit('open-edit', String(playlistId))
198
+
199
+ inputValue.value = selectedPlaylist.value?.name || '';
200
+ },
201
+ { immediate: true }
202
+ );
203
+
204
+ watch(() => selectedPlaylist.value?.playlistType, (newType) => {
205
+ if (newType !== undefined && newType !== null) {
206
+ toggleValue.value = newType === EPlaylistType.Private;
207
+ }
208
+ });
209
+
210
+ const loadingAll = computed(() => props.loadingRefresh || props.loadingEditVideos || props.loadingPlaylists || props.loadingPlaylistDelete);
211
+ </script>
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div class="s-playlist-like">
3
+ <button
4
+ class="s-playlist-like__control"
5
+ :class="{'--active': isLiked}"
6
+ type="button"
7
+ :title="t('like')"
8
+ @click="onReactionClick('like')"
9
+ >
10
+ <SIcon class="s-playlist-like__control-icon" name="thumbs-up" size="16"/>
11
+ {{ formatNumber(Number(resultLikes)) }}
12
+ </button>
13
+ <div
14
+ v-if="bar"
15
+ class="s-playlist-like__bar"
16
+ >
17
+ <div
18
+ class="s-playlist-like__bar-likes"
19
+ :style="{ width: likePercent + '%' }"
20
+ />
21
+ <div
22
+ class="s-playlist-like__bar-dislikes"
23
+ :style="{ width: dislikePercent + '%' }"
24
+ />
25
+ </div>
26
+ <button
27
+ class="s-playlist-like__control"
28
+ :class="{'--active': isDisliked}"
29
+ type="button"
30
+ :title="t('dislike')"
31
+ @click="onReactionClick('dislike')"
32
+ >
33
+ <SIcon class="s-playlist-like__control-icon" name="thumbs-down" size="16"/>
34
+ {{ formatNumber(Number(resultDislikes)) }}
35
+ </button>
36
+ </div>
37
+ </template>
38
+
39
+ <script setup lang="ts">
40
+ import { formatNumber } from '../../runtime';
41
+
42
+ const { t } = useI18n();
43
+
44
+ const props = defineProps<{
45
+ playlistId: string
46
+ likes: number
47
+ dislikes: number
48
+ bar?: boolean
49
+ }>()
50
+
51
+ const emit = defineEmits<{
52
+ (eventName: 'like', eventValue: string): void
53
+ (eventName: 'dislike', eventValue: string): void
54
+ }>()
55
+
56
+ const likeStatus = ref<'like' | 'dislike' | null>(null);
57
+ const isLiked = computed(() => likeStatus.value === 'like');
58
+ const isDisliked = computed(() => likeStatus.value === 'dislike');
59
+ const resultLikes = ref(props.likes);
60
+ const resultDislikes = ref(props.dislikes);
61
+
62
+ onMounted(() => {
63
+ const stored = localStorage.getItem(props.playlistId);
64
+ if (stored === 'like' || stored === 'dislike') {
65
+ likeStatus.value = stored;
66
+ if (stored === 'like') resultLikes.value += 1;
67
+ if (stored === 'dislike') resultDislikes.value += 1;
68
+ }
69
+ });
70
+
71
+ async function onReactionClick(type: 'like' | 'dislike') {
72
+ if (likeStatus.value !== type) {
73
+ likeStatus.value = type;
74
+ localStorage.setItem(props.playlistId, type);
75
+
76
+ const isLike = type === 'like';
77
+
78
+ if (isLike) {
79
+ if (resultLikes.value < 1000) resultLikes.value += 1;
80
+ if (resultDislikes.value > 0) resultDislikes.value -= 1;
81
+ emit('like', props.playlistId);
82
+ } else {
83
+ if (resultDislikes.value < 1000) resultDislikes.value += 1;
84
+ if (resultLikes.value > 0) resultLikes.value -= 1;
85
+ emit('dislike', props.playlistId);
86
+ }
87
+ }
88
+ }
89
+
90
+ const likes = computed(() => resultLikes.value || 0);
91
+ const dislikes = computed(() => resultDislikes.value || 0);
92
+ const total = computed(() => likes.value + dislikes.value || 1);
93
+
94
+ const likePercent = computed(() => (likes.value / total.value) * 100);
95
+ const dislikePercent = computed(() => (dislikes.value / total.value) * 100);
96
+ </script>
@@ -0,0 +1,56 @@
1
+ <template>
2
+ <div class="s-playlist-new" :class="{'_loading': loadingPostPlaylist}">
3
+ <SInput
4
+ v-model="newTitleValue"
5
+ :label="$t('title')"
6
+ />
7
+ <SPlaylistPrivateToggle v-model="isPrivate" />
8
+ <div class="s-playlist-new__buttons">
9
+ <SButton
10
+ wide
11
+ theme="secondary"
12
+ @click="closePopup"
13
+ >{{ $t('cancel') }}
14
+ </SButton>
15
+ <SButton
16
+ wide
17
+ @click="onSaveClick"
18
+ >{{ $t('save') }}
19
+ </SButton>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { EPlaylistType } from '../../runtime';
26
+ import type { IPlaylistShort } from '../../types';
27
+
28
+ defineProps<{
29
+ loadingPostPlaylist: boolean
30
+ }>()
31
+
32
+ const emit = defineEmits<{
33
+ (eventName: 'close'): void
34
+ (eventName: 'post', eventValue: IPlaylistShort): void
35
+ }>()
36
+
37
+ const { closePlaylistAdd } = usePlaylistAdd();
38
+ const newTitleValue = ref('');
39
+ const isPrivate = ref(false);
40
+
41
+ function closePopup() {
42
+ closePlaylistAdd();
43
+ emit('close');
44
+ }
45
+
46
+ async function onSaveClick() {
47
+ try {
48
+ emit('post', {
49
+ name: newTitleValue.value,
50
+ type: isPrivate.value ? EPlaylistType.Private : EPlaylistType.Public
51
+ });
52
+ } catch(error) {
53
+ console.log(error, 'error create playlist');
54
+ }
55
+ }
56
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="s-playlist-private-toggle">
3
+ <SToggle
4
+ class="s-playlist-private-toggle__toggler"
5
+ :model-value="modelValue"
6
+ @update:modelValue="(event: Event) => emit('update:modelValue', event)"
7
+ />
8
+ <p class="s-playlist-private-toggle__text">{{ $t('playlist.private_playlist')}}</p>
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ defineProps<{
14
+ modelValue: boolean
15
+ }>()
16
+
17
+ const emit = defineEmits<{
18
+ (eventName: 'update:modelValue', value: Event): void
19
+ }>()
20
+ </script>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "itube-specs",
3
3
  "type": "module",
4
- "version": "0.0.339",
4
+ "version": "0.0.340",
5
5
  "main": "./nuxt.config.ts",
6
6
  "types": "./types/index.d.ts",
7
7
  "scripts": {