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.
- package/components/playlist/s-playlist-add.vue +186 -0
- package/components/playlist/s-playlist-delete-video.vue +77 -0
- package/components/playlist/s-playlist-edit.vue +211 -0
- package/components/playlist/s-playlist-like.vue +96 -0
- package/components/playlist/s-playlist-new.vue +56 -0
- package/components/playlist/s-playlist-private-toggle.vue +20 -0
- package/package.json +1 -1
|
@@ -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>
|