@meeovi/layer-lists 1.0.2

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.
Files changed (47) hide show
  1. package/app/components/features/archived.vue +64 -0
  2. package/app/components/features/bookmarks.vue +64 -0
  3. package/app/components/features/lists.vue +61 -0
  4. package/app/components/features/starred.vue +64 -0
  5. package/app/components/lists/ListItemCard.vue +190 -0
  6. package/app/components/lists/add-bookmark.vue +52 -0
  7. package/app/components/lists/add-list-item.vue +88 -0
  8. package/app/components/lists/add-list.vue +57 -0
  9. package/app/components/lists/lists.vue +6 -0
  10. package/app/components/lists/listsettings.vue +145 -0
  11. package/app/components/lists/update-bookmark.vue +267 -0
  12. package/app/components/lists/update-list.vue +192 -0
  13. package/app/components/media/MediaPlayer.vue +302 -0
  14. package/app/components/partials/addtolist.vue +233 -0
  15. package/app/components/partials/createListBtn.vue +95 -0
  16. package/app/components/partials/listBtn.vue +35 -0
  17. package/app/components/related/list.vue +33 -0
  18. package/app/components/related/relatedlists.vue +43 -0
  19. package/app/components/tasks/TaskItem.vue +204 -0
  20. package/app/composables/bookmarks/createBookmark.js +30 -0
  21. package/app/composables/bookmarks/deleteBookmark.js +15 -0
  22. package/app/composables/bookmarks/updateBookmark.js +15 -0
  23. package/app/composables/config.ts +17 -0
  24. package/app/composables/content/uploadFiles.js +41 -0
  25. package/app/composables/globals/useDirectusForm.ts +1 -0
  26. package/app/composables/lists/createList.js +25 -0
  27. package/app/composables/lists/deleteList.js +14 -0
  28. package/app/composables/lists/updateList.js +20 -0
  29. package/app/composables/lists/useBookmarks.js +69 -0
  30. package/app/composables/lists/useLists.js +120 -0
  31. package/app/composables/lists/usePlaylist.js +64 -0
  32. package/app/composables/lists/useSaved.js +29 -0
  33. package/app/composables/lists/useTasks.js +86 -0
  34. package/app/composables/lists/useWishlist.js +51 -0
  35. package/app/composables/providers/atproto.ts +156 -0
  36. package/app/composables/providers/directus.ts +49 -0
  37. package/app/composables/providers/memory.ts +88 -0
  38. package/app/composables/registry.ts +13 -0
  39. package/app/composables/types.ts +35 -0
  40. package/app/composables/useLists.ts +20 -0
  41. package/app/composables/utils/transforms.ts +42 -0
  42. package/app/composables/utils/validation.ts +21 -0
  43. package/app/pages/lists/bookmark/[id].vue +76 -0
  44. package/app/pages/lists/index.vue +152 -0
  45. package/app/pages/lists/list/[...slug].vue +233 -0
  46. package/nuxt.config.ts +11 -0
  47. package/package.json +26 -0
@@ -0,0 +1,43 @@
1
+ <template>
2
+ <div>
3
+ <v-sheet class="mx-auto sliderLists row align-items-stretch items-row justify-content-center">
4
+ <v-toolbar color="transparent">
5
+ <v-toolbar-title>Public Lists</v-toolbar-title>
6
+ </v-toolbar>
7
+ <v-slide-group v-model="model" class="pa-4" selected-class="bg-success" show-arrows>
8
+ <v-slide-group-item v-slot="{ isSelected, toggle, selectedClass }" v-for="(list, index) in lists" :key="index">
9
+ <listCard :class="['ma-4', selectedClass]" :list="list" v-if="isSelected" @click="toggle" />
10
+ </v-slide-group-item>
11
+ </v-slide-group>
12
+ </v-sheet>
13
+ </div>
14
+ </template>
15
+
16
+ <script setup>
17
+ import {
18
+ ref
19
+ } from 'vue'
20
+ import listCard from '~/components/related/list.vue'
21
+
22
+ const {
23
+ $directus,
24
+ $readItems
25
+ } = useNuxtApp()
26
+
27
+ const model = ref(null)
28
+
29
+ const {
30
+ data: lists
31
+ } = await useAsyncData('lists', () => {
32
+ return $directus.request($readItems('lists', {
33
+ fields: ['*', {
34
+ '*': ['*']
35
+ }],
36
+ filter: {
37
+ status: {
38
+ _eq: 'Public'
39
+ }
40
+ },
41
+ }))
42
+ })
43
+ </script>
@@ -0,0 +1,204 @@
1
+ <template>
2
+ <v-card
3
+ :class="{ 'task-completed': task.completed }"
4
+ class="task-item mb-2"
5
+ variant="outlined"
6
+ >
7
+ <v-card-text class="py-2">
8
+ <div class="d-flex align-center">
9
+ <v-checkbox
10
+ :model-value="task.completed"
11
+ @update:model-value="toggleComplete"
12
+ hide-details
13
+ class="me-3"
14
+ />
15
+
16
+ <div class="grow">
17
+ <div
18
+ :class="{ 'text-decoration-line-through text-medium-emphasis': task.completed }"
19
+ class="task-title"
20
+ >
21
+ {{ task.title }}
22
+ </div>
23
+
24
+ <div v-if="task.description" class="text-body-2 text-medium-emphasis mt-1">
25
+ {{ task.description }}
26
+ </div>
27
+
28
+ <div class="d-flex align-center mt-2 flex-wrap ga-2">
29
+ <v-chip
30
+ v-if="task.priority !== 'medium'"
31
+ :color="getPriorityColor(task.priority)"
32
+ size="x-small"
33
+ variant="tonal"
34
+ >
35
+ {{ task.priority }}
36
+ </v-chip>
37
+
38
+ <v-chip
39
+ v-if="task.due_date"
40
+ :color="getDueDateColor(task.due_date)"
41
+ size="x-small"
42
+ variant="outlined"
43
+ prepend-icon="mdi-calendar"
44
+ >
45
+ {{ formatDueDate(task.due_date) }}
46
+ </v-chip>
47
+
48
+ <v-chip
49
+ v-for="label in task.labels"
50
+ :key="label"
51
+ size="x-small"
52
+ variant="outlined"
53
+ >
54
+ {{ label }}
55
+ </v-chip>
56
+ </div>
57
+
58
+ <!-- Subtasks -->
59
+ <div v-if="task.subtasks?.length" class="mt-3">
60
+ <div class="text-caption text-medium-emphasis mb-1">
61
+ Subtasks ({{ completedSubtasks }}/{{ task.subtasks.length }})
62
+ </div>
63
+ <div class="subtasks">
64
+ <div
65
+ v-for="subtask in task.subtasks"
66
+ :key="subtask.id"
67
+ class="d-flex align-center py-1"
68
+ >
69
+ <v-checkbox
70
+ :model-value="subtask.completed"
71
+ @update:model-value="toggleSubtask(subtask.id)"
72
+ hide-details
73
+ density="compact"
74
+ class="me-2"
75
+ />
76
+ <span
77
+ :class="{ 'text-decoration-line-through text-medium-emphasis': subtask.completed }"
78
+ class="text-body-2"
79
+ >
80
+ {{ subtask.title }}
81
+ </span>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+
87
+ <div class="task-actions">
88
+ <v-menu>
89
+ <template v-slot:activator="{ props }">
90
+ <v-btn
91
+ icon="mdi-dots-vertical"
92
+ variant="text"
93
+ size="small"
94
+ v-bind="props"
95
+ />
96
+ </template>
97
+
98
+ <v-list>
99
+ <v-list-item @click="$emit('edit', task)">
100
+ <template v-slot:prepend>
101
+ <v-icon icon="mdi-pencil" />
102
+ </template>
103
+ <v-list-item-title>Edit</v-list-item-title>
104
+ </v-list-item>
105
+
106
+ <v-list-item @click="$emit('duplicate', task)">
107
+ <template v-slot:prepend>
108
+ <v-icon icon="mdi-content-copy" />
109
+ </template>
110
+ <v-list-item-title>Duplicate</v-list-item-title>
111
+ </v-list-item>
112
+
113
+ <v-divider />
114
+
115
+ <v-list-item @click="$emit('delete', task)" class="text-error">
116
+ <template v-slot:prepend>
117
+ <v-icon icon="mdi-delete" color="error" />
118
+ </template>
119
+ <v-list-item-title>Delete</v-list-item-title>
120
+ </v-list-item>
121
+ </v-list>
122
+ </v-menu>
123
+ </div>
124
+ </div>
125
+ </v-card-text>
126
+ </v-card>
127
+ </template>
128
+
129
+ <script setup>
130
+ import { format, isToday, isTomorrow, isPast } from 'date-fns'
131
+
132
+ const props = defineProps({
133
+ task: {
134
+ type: Object,
135
+ required: true
136
+ }
137
+ })
138
+
139
+ const emit = defineEmits(['update', 'edit', 'duplicate', 'delete'])
140
+
141
+ const completedSubtasks = computed(() => {
142
+ return props.task.subtasks?.filter(st => st.completed).length || 0
143
+ })
144
+
145
+ const toggleComplete = (completed) => {
146
+ emit('update', { ...props.task, completed })
147
+ }
148
+
149
+ const toggleSubtask = (subtaskId) => {
150
+ const subtasks = props.task.subtasks.map(st =>
151
+ st.id === subtaskId ? { ...st, completed: !st.completed } : st
152
+ )
153
+ emit('update', { ...props.task, subtasks })
154
+ }
155
+
156
+ const getPriorityColor = (priority) => {
157
+ const colors = {
158
+ low: 'blue',
159
+ medium: 'orange',
160
+ high: 'red'
161
+ }
162
+ return colors[priority] || 'grey'
163
+ }
164
+
165
+ const getDueDateColor = (dueDate) => {
166
+ if (!dueDate) return 'grey'
167
+
168
+ const date = new Date(dueDate)
169
+ if (isPast(date) && !isToday(date)) return 'error'
170
+ if (isToday(date)) return 'warning'
171
+ if (isTomorrow(date)) return 'info'
172
+ return 'success'
173
+ }
174
+
175
+ const formatDueDate = (dueDate) => {
176
+ if (!dueDate) return ''
177
+
178
+ const date = new Date(dueDate)
179
+ if (isToday(date)) return 'Today'
180
+ if (isTomorrow(date)) return 'Tomorrow'
181
+ if (isPast(date)) return `Overdue (${format(date, 'MMM d')})`
182
+ return format(date, 'MMM d')
183
+ }
184
+ </script>
185
+
186
+ <style scoped>
187
+ .task-completed {
188
+ opacity: 0.7;
189
+ }
190
+
191
+ .task-item {
192
+ transition: all 0.2s ease;
193
+ }
194
+
195
+ .task-item:hover {
196
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
197
+ }
198
+
199
+ .subtasks {
200
+ border-left: 2px solid rgba(var(--v-theme-primary), 0.2);
201
+ padding-left: 12px;
202
+ margin-left: 8px;
203
+ }
204
+ </style>
@@ -0,0 +1,30 @@
1
+ // composables/createWebsite.js
2
+ import { createItem } from '@directus/sdk';
3
+
4
+ export default async function createWebsite(websiteData) {
5
+ const route = useRoute();
6
+
7
+ const id = route.params.id;
8
+ const { $directus } = useNuxtApp();
9
+
10
+ try {
11
+ const website = await $directus.request(createItem('websites', [
12
+ {
13
+ name: websiteData.name,
14
+ note: websiteData.note,
15
+ status: websiteData.status,
16
+ type: websiteData.type,
17
+ image: websiteData.image,
18
+ icon: websiteData.icon,
19
+ slug: websiteData.slug,
20
+ coverFile: null,
21
+ username: websiteData.username,
22
+ }
23
+ ]));
24
+ return website;
25
+ } catch (error) {
26
+ console.error('Error creating website:', error);
27
+ throw error;
28
+ }
29
+ }
30
+
@@ -0,0 +1,15 @@
1
+ // composables/deleteWebsite.js
2
+ import { deleteItem } from '@directus/sdk';
3
+
4
+ export default async function deleteWebsite(websiteId) {
5
+ const { $directus } = useNuxtApp();
6
+
7
+ try {
8
+ $directus.request(deleteItem('websites', websiteId));
9
+ console.log('Bookmark deleted successfully');
10
+ } catch (error) {
11
+ console.error('Error deleting bookmark:', error);
12
+ throw error;
13
+ }
14
+ }
15
+
@@ -0,0 +1,15 @@
1
+ // composables/updatePost.js
2
+ import { updateItem } from '@directus/sdk';
3
+
4
+ export default async function updatePost(websiteId, websiteData) {
5
+ const { $directus } = useNuxtApp();
6
+
7
+ try {
8
+ const website = await $directus.request(updateItem('websites', websiteId));
9
+ return website;
10
+ } catch (error) {
11
+ console.error('Error updating bookmark:', error);
12
+ throw error;
13
+ }
14
+ }
15
+
@@ -0,0 +1,17 @@
1
+ export interface ListsConfig {
2
+ provider: string
3
+ baseUrl?: string
4
+ apiKey?: string
5
+ }
6
+
7
+ let config: ListsConfig = {
8
+ provider: 'memory'
9
+ }
10
+
11
+ export function setListsConfig(newConfig: Partial<ListsConfig>) {
12
+ config = { ...config, ...newConfig }
13
+ }
14
+
15
+ export function getListsConfig(): ListsConfig {
16
+ return config
17
+ }
@@ -0,0 +1,41 @@
1
+ import { uploadFiles } from '@meeovi/directus-client';
2
+
3
+ export default async function uploadFile({ imageFile, documentFile, videoFile, audioFile }) {
4
+ const { $directus } = useNuxtApp();
5
+ const uploadedFiles = {};
6
+
7
+ try {
8
+ if (imageFile) {
9
+ const formDataImage = new FormData();
10
+ formDataImage.append('file', imageFile);
11
+ const uploadedImage = await $directus.request(uploadFiles(formDataImage));
12
+ uploadedFiles.imageId = uploadedImage.id;
13
+ }
14
+
15
+ if (documentFile) {
16
+ const formDataDocument = new FormData();
17
+ formDataDocument.append('file', documentFile);
18
+ const uploadedDocument = await $directus.request(uploadFiles(formDataDocument));
19
+ uploadedFiles.documentId = uploadedDocument.id;
20
+ }
21
+
22
+ if (videoFile) {
23
+ const formDataVideo = new FormData();
24
+ formDataVideo.append('file', videoFile);
25
+ const uploadedVideo = await $directus.request(uploadFiles(formDataVideo));
26
+ uploadedFiles.videoId = uploadedVideo.id;
27
+ }
28
+
29
+ if (audioFile) {
30
+ const formDataAudio = new FormData();
31
+ formDataAudio.append('file', audioFile);
32
+ const uploadedAudio = await $directus.request(uploadFiles(formDataAudio));
33
+ uploadedFiles.audioId = uploadedAudio.id;
34
+ }
35
+
36
+ return uploadedFiles;
37
+ } catch (error) {
38
+ console.error('Error uploading files:', error);
39
+ throw error;
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ export * from '#shared/app/composables/globals/useDirectusForm'
@@ -0,0 +1,25 @@
1
+ // composables/createList.js
2
+ import { createItem } from '@directus/sdk';
3
+
4
+ export default async function createList(listData) {
5
+ const { $directus } = useNuxtApp();
6
+ const user = useSupabaseUser();
7
+
8
+ const data = {
9
+ name: listData.name,
10
+ type: listData.type,
11
+ status: listData.status,
12
+ description: listData.description,
13
+ image: listData.image,
14
+ user: user.value.id,
15
+ };
16
+
17
+ try {
18
+ const list = await $directus.request(createItem('lists', data));
19
+ return list;
20
+ } catch (error) {
21
+ console.error('Error creating list:', error);
22
+ throw error;
23
+ }
24
+ }
25
+
@@ -0,0 +1,14 @@
1
+ // composables/cms/lists/deleteList.js
2
+ import { deleteItem } from '@directus/sdk';
3
+
4
+ export default async function deleteList(listId) {
5
+ const { $directus } = useNuxtApp();
6
+
7
+ try {
8
+ await $directus.request(deleteItem('lists', listId));
9
+ return true;
10
+ } catch (error) {
11
+ console.error('Error deleting list:', error);
12
+ throw error;
13
+ }
14
+ }
@@ -0,0 +1,20 @@
1
+ // composables/updateList.js
2
+ import { updateItem } from '@directus/sdk';
3
+
4
+ export default async function updateList(listId, listData) {
5
+ const { $directus } = useNuxtApp();
6
+
7
+ try {
8
+ const list = await $directus.request(updateItem('lists', listId, listData));
9
+ return list;
10
+ } catch (error) {
11
+ console.error('Error updating list:', {
12
+ error: error.message,
13
+ statusCode: error.status,
14
+ data: error.data,
15
+ listId,
16
+ listData
17
+ });
18
+ throw error;
19
+ }
20
+ }
@@ -0,0 +1,69 @@
1
+ export const useBookmarks = () => {
2
+ const { createList, addToList, getUserLists, removeFromList } = useLists()
3
+
4
+ const createBookmarkList = async (name = 'Bookmarks', description = '') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'bookmarks',
9
+ visibility: 'private'
10
+ })
11
+ }
12
+
13
+ const addBookmark = async (listId, bookmarkData) => {
14
+ const { url, title, description, tags, favicon } = bookmarkData
15
+
16
+ return await addToList(listId, {
17
+ type: 'bookmark',
18
+ title,
19
+ url,
20
+ description,
21
+ favicon,
22
+ tags: tags || [],
23
+ date_added: new Date().toISOString(),
24
+ read: false,
25
+ archived: false
26
+ })
27
+ }
28
+
29
+ const getUserBookmarkLists = async () => {
30
+ return await getUserLists('bookmarks')
31
+ }
32
+
33
+ const removeBookmark = async (itemId) => {
34
+ return await removeFromList(itemId)
35
+ }
36
+
37
+ const markAsRead = async (itemId) => {
38
+ const { updateListItem } = useLists()
39
+ return await updateListItem(itemId, {
40
+ 'content.read': true,
41
+ 'content.date_read': new Date().toISOString()
42
+ })
43
+ }
44
+
45
+ const archiveBookmark = async (itemId) => {
46
+ const { updateListItem } = useLists()
47
+ return await updateListItem(itemId, {
48
+ 'content.archived': true,
49
+ 'content.date_archived': new Date().toISOString()
50
+ })
51
+ }
52
+
53
+ const addTagsToBookmark = async (itemId, newTags) => {
54
+ const { updateListItem } = useLists()
55
+ return await updateListItem(itemId, {
56
+ 'content.tags': newTags
57
+ })
58
+ }
59
+
60
+ return {
61
+ createBookmarkList,
62
+ addBookmark,
63
+ getUserBookmarkLists,
64
+ removeBookmark,
65
+ markAsRead,
66
+ archiveBookmark,
67
+ addTagsToBookmark
68
+ }
69
+ }
@@ -0,0 +1,120 @@
1
+ import { createItem, readItems, readItem, updateItem, deleteItem } from '@directus/sdk'
2
+
3
+ export const useLists = () => {
4
+ const { $directus, $readItem } = useNuxtApp()
5
+
6
+ // Cache current user to avoid repeated requests
7
+ let _currentUser = null
8
+
9
+ async function ensureCurrentUser() {
10
+ if (_currentUser) return _currentUser
11
+ try {
12
+ const resp = await $directus.request($readItem('users', 'me'))
13
+ // Directus responses sometimes wrap in { data } or return raw object
14
+ _currentUser = resp?.data || resp || null
15
+ return _currentUser
16
+ } catch (e) {
17
+ return null
18
+ }
19
+ }
20
+
21
+ const createList = async (listData) => {
22
+ const user = await ensureCurrentUser()
23
+ if (!user) throw new Error('Not logged in')
24
+
25
+ const list = await $directus.request(
26
+ createItem('lists', {
27
+ ...listData,
28
+ user_created: user.id,
29
+ date_created: new Date().toISOString()
30
+ })
31
+ )
32
+ return list
33
+ }
34
+
35
+ const updateList = async (listId, updates) => {
36
+ return await $directus.request(
37
+ updateItem('lists', listId, {
38
+ ...updates,
39
+ date_updated: new Date().toISOString()
40
+ })
41
+ )
42
+ }
43
+
44
+ const deleteList = async (listId) => {
45
+ return await $directus.request(deleteItem('lists', listId))
46
+ }
47
+
48
+ const getUserLists = async (type = null) => {
49
+ const user = await ensureCurrentUser()
50
+ if (!user) return []
51
+
52
+ const filter = { user_created: { _eq: user.id } }
53
+ if (type) filter.type = { _eq: type }
54
+
55
+ return await $directus.request(
56
+ readItems('lists', {
57
+ filter,
58
+ sort: ['-date_updated', 'name'],
59
+ fields: ['*', 'items.*', 'items.content.*']
60
+ })
61
+ )
62
+ }
63
+
64
+ const getPublicLists = async (type = null) => {
65
+ const filter = { visibility: { _eq: 'public' } }
66
+ if (type) filter.type = { _eq: type }
67
+
68
+ return await $directus.request(
69
+ readItems('lists', {
70
+ filter,
71
+ sort: ['-date_updated'],
72
+ fields: ['*', 'user_created.first_name', 'user_created.last_name']
73
+ })
74
+ )
75
+ }
76
+
77
+ const getListById = async (listId) => {
78
+ return await $directus.request(
79
+ readItem('lists', listId, {
80
+ fields: ['*', 'items.*', 'items.content.*', 'user_created.first_name', 'user_created.last_name']
81
+ })
82
+ )
83
+ }
84
+
85
+ const addToList = async (listId, contentData) => {
86
+ return await $directus.request(
87
+ createItem('list_items', {
88
+ list: listId,
89
+ content: contentData,
90
+ date_created: new Date().toISOString()
91
+ })
92
+ )
93
+ }
94
+
95
+ const removeFromList = async (itemId) => {
96
+ return await $directus.request(deleteItem('list_items', itemId))
97
+ }
98
+
99
+ const updateListItem = async (itemId, updates) => {
100
+ return await $directus.request(
101
+ updateItem('list_items', itemId, {
102
+ ...updates,
103
+ date_updated: new Date().toISOString()
104
+ })
105
+ )
106
+ }
107
+
108
+ return {
109
+ createList,
110
+ updateList,
111
+ deleteList,
112
+ getUserLists,
113
+ getPublicLists,
114
+ getListById,
115
+ addToList,
116
+ removeFromList,
117
+ updateListItem
118
+ }
119
+ }
120
+
@@ -0,0 +1,64 @@
1
+ export const usePlaylist = () => {
2
+ const { createList, addToList, getUserLists, getListById } = useLists()
3
+
4
+ const createPlaylist = async (name, description = '', visibility = 'private') => {
5
+ return await createList({
6
+ name,
7
+ description,
8
+ type: 'playlist',
9
+ visibility,
10
+ settings: {
11
+ autoplay: false,
12
+ shuffle: false,
13
+ repeat: 'none'
14
+ }
15
+ })
16
+ }
17
+
18
+ const addMediaToPlaylist = async (playlistId, mediaData) => {
19
+ const { url, title, duration, type, thumbnail } = mediaData
20
+
21
+ if (!['audio', 'video'].includes(type)) {
22
+ throw new Error('Only audio and video files are supported in playlists')
23
+ }
24
+
25
+ return await addToList(playlistId, {
26
+ type: 'media',
27
+ title,
28
+ url,
29
+ media_type: type,
30
+ duration,
31
+ thumbnail,
32
+ metadata: {
33
+ artist: mediaData.artist || '',
34
+ album: mediaData.album || '',
35
+ genre: mediaData.genre || ''
36
+ }
37
+ })
38
+ }
39
+
40
+ const getUserPlaylists = async () => {
41
+ return await getUserLists('playlist')
42
+ }
43
+
44
+ const getPlaylistWithMedia = async (playlistId) => {
45
+ const playlist = await getListById(playlistId)
46
+ if (playlist.type !== 'playlist') {
47
+ throw new Error('Not a playlist')
48
+ }
49
+ return playlist
50
+ }
51
+
52
+ const updatePlaylistSettings = async (playlistId, settings) => {
53
+ const { updateList } = useLists()
54
+ return await updateList(playlistId, { settings })
55
+ }
56
+
57
+ return {
58
+ createPlaylist,
59
+ addMediaToPlaylist,
60
+ getUserPlaylists,
61
+ getPlaylistWithMedia,
62
+ updatePlaylistSettings
63
+ }
64
+ }