14
14
<slot name =" activator" v-bind =" scope" />
15
15
</template >
16
16
17
- <dialog-category-form :camp =" camp" :is-new =" true" :category =" entityData" />
17
+ <template #moreActions >
18
+ <CopyCategoryInfoDialog @closed =" refreshCopyCategorySource" >
19
+ <template #activator =" { on } " >
20
+ <v-btn v-show =" clipboardPermission === 'prompt'" v-on =" on" >
21
+ <v-icon left >mdi-information-outline</v-icon >
22
+ {{
23
+ $tc('components.campAdmin.dialogCategoryCreate.copyPasteCategoryOrActivity')
24
+ }}
25
+ </v-btn >
26
+ </template >
27
+ </CopyCategoryInfoDialog >
28
+ </template >
29
+
30
+ <div v-if =" hasCopyCategorySource" >
31
+ <div class =" mb-8" >
32
+ <div v-if =" !clipboardAccessDenied" >
33
+ {{ $tc('components.campAdmin.dialogCategoryCreate.clipboard') }}
34
+ <div style =" float : right " >
35
+ <small >
36
+ <a
37
+ href =" #"
38
+ style =" color : inherit ; text-decoration : none "
39
+ @click =" clearClipboard"
40
+ >
41
+ {{ $tc('components.campAdmin.dialogCategoryCreate.clearClipboard') }}
42
+ </a >
43
+ </small >
44
+ </div >
45
+ </div >
46
+ <v-list-item
47
+ class =" ec-copy-source rounded-xl blue-grey lighten-5 blue-grey--text text--darken-4 mt-1"
48
+ >
49
+ <v-list-item-avatar >
50
+ <v-icon color =" blue-grey" >mdi-clipboard-check-outline</v-icon >
51
+ </v-list-item-avatar >
52
+ <v-list-item-content >
53
+ <v-list-item-title >
54
+ <CategoryChip :category =" copyCategorySourceCategory" class =" mx-1" dense />
55
+ {{ copyCategorySource.title }}
56
+ </v-list-item-title >
57
+ <v-list-item-subtitle >
58
+ {{ copyCategorySource.camp().title }}
59
+ </v-list-item-subtitle >
60
+ </v-list-item-content >
61
+ <v-list-item-action >
62
+ <e-checkbox
63
+ v-model =" copyContent"
64
+ :label =" $tc('components.campAdmin.dialogCategoryCreate.copyContent')"
65
+ />
66
+ </v-list-item-action >
67
+ </v-list-item >
68
+ </div >
69
+ </div >
70
+ <dialog-category-form :camp =" camp" :is-new =" true" :category =" entityData" >
71
+ <template v-if =" clipboardAccessDenied " #textFieldTitleAppend >
72
+ <PopoverPrompt
73
+ v-model =" copyCategorySourceUrlShowPopover"
74
+ icon =" mdi-content-paste"
75
+ :title =" $tc('components.campAdmin.dialogCategoryCreate.pasteCategory')"
76
+ >
77
+ <template #activator =" scope " >
78
+ <v-btn
79
+ :title =" $tc('components.campAdmin.dialogCategoryCreate.pasteCategory')"
80
+ text
81
+ class =" v-btn--has-bg"
82
+ height =" 56"
83
+ v-on =" scope.on"
84
+ >
85
+ <v-progress-circular v-if =" copyCategorySourceUrlLoading" indeterminate />
86
+ <v-icon v-else >mdi-content-paste</v-icon >
87
+ </v-btn >
88
+ </template >
89
+ {{ $tc('components.campAdmin.dialogCategoryCreate.copySourceInfo') }}
90
+ <e-text-field
91
+ v-model =" copyCategorySourceUrl"
92
+ :label ="
93
+ $tc('components.campAdmin.dialogCategoryCreate.copyCategoryOrActivity')
94
+ "
95
+ style =" margin-bottom : 12px "
96
+ autofocus
97
+ />
98
+ </PopoverPrompt >
99
+ </template >
100
+ </dialog-category-form >
18
101
</dialog-form >
19
102
</template >
20
103
@@ -23,10 +106,17 @@ import { categoryRoute } from '@/router.js'
23
106
import DialogForm from ' @/components/dialog/DialogForm.vue'
24
107
import DialogBase from ' @/components/dialog/DialogBase.vue'
25
108
import DialogCategoryForm from ' ./DialogCategoryForm.vue'
109
+ import PopoverPrompt from ' ../prompt/PopoverPrompt.vue'
110
+ import router from ' ../../router.js'
111
+ import CategoryChip from ' ../generic/CategoryChip.vue'
112
+ import CopyCategoryInfoDialog from ' ../category/CopyCategoryInfoDialog.vue'
26
113
27
114
export default {
28
115
name: ' DialogCategoryCreate' ,
29
116
components: {
117
+ CopyCategoryInfoDialog,
118
+ CategoryChip,
119
+ PopoverPrompt,
30
120
DialogCategoryForm,
31
121
DialogForm,
32
122
},
@@ -39,30 +129,155 @@ export default {
39
129
entityProperties: [' camp' , ' short' , ' name' , ' color' , ' numberingStyle' ],
40
130
embeddedCollections: [' preferredContentTypes' ],
41
131
entityUri: ' /categories' ,
132
+ clipboardPermission: ' unknown' ,
133
+ copyCategorySource: null ,
134
+ copyCategorySourceUrl: null ,
135
+ copyCategorySourceUrlLoading: false ,
136
+ copyCategorySourceUrlShowPopover: false ,
42
137
}
43
138
},
139
+ computed: {
140
+ clipboardAccessDenied () {
141
+ return (
142
+ this .clipboardPermission === ' unaccessable' ||
143
+ this .clipboardPermission === ' denied'
144
+ )
145
+ },
146
+ hasCopyCategorySource () {
147
+ return this .copyCategorySource != null && this .copyCategorySource ._meta .self != null
148
+ },
149
+ copyContent: {
150
+ get () {
151
+ return this .entityData .copyCategorySource != null
152
+ },
153
+ set (val ) {
154
+ if (val) {
155
+ this .entityData .copyCategorySource = this .copyCategorySource ._meta .self
156
+ this .entityData .short = this .copyCategorySourceCategory .short
157
+ this .entityData .name = this .copyCategorySourceCategory .name
158
+ this .entityData .color = this .copyCategorySourceCategory .color
159
+ this .entityData .numberingStyle = this .copyCategorySourceCategory .numberingStyle
160
+ } else {
161
+ this .entityData .copyCategorySource = null
162
+ }
163
+ },
164
+ },
165
+ copyCategorySourceCategory () {
166
+ if (! this .hasCopyCategorySource ) return null
167
+ return this .copyCategorySource .short
168
+ ? this .copyCategorySource
169
+ : this .copyCategorySource .category ()
170
+ },
171
+ },
44
172
watch: {
45
173
showDialog : function (showDialog ) {
46
174
if (showDialog) {
175
+ this .refreshCopyCategorySource ()
47
176
this .setEntityData ({
48
177
camp: this .camp ._meta .self ,
49
178
short: ' ' ,
50
179
name: ' ' ,
51
180
color: ' #000000' ,
52
181
numberingStyle: ' 1' ,
182
+ copyCategorySource: null ,
53
183
})
54
184
} else {
55
185
// clear form on exit
56
186
this .clearEntityData ()
187
+ this .copyCategorySource = null
188
+ this .copyCategorySourceUrl = null
57
189
}
58
190
},
191
+ copyCategorySourceUrl : function (url ) {
192
+ this .copyCategorySourceUrlLoading = true
193
+
194
+ this .getCopyCategorySource (url).then (
195
+ (categoryOrActivityProxy ) => {
196
+ if (categoryOrActivityProxy != null ) {
197
+ categoryOrActivityProxy ._meta .load .then (
198
+ async (categoryOrActivity ) => {
199
+ if (! categoryOrActivity .short ) {
200
+ await categoryOrActivity .category ()._meta .load
201
+ }
202
+ this .copyCategorySource = categoryOrActivity
203
+ this .copyContent = true
204
+ this .copyCategorySourceUrlLoading = false
205
+ },
206
+ () => {
207
+ this .copyCategorySourceUrlLoading = false
208
+ }
209
+ )
210
+ } else {
211
+ this .copyCategorySource = null
212
+ this .copyContent = false
213
+ this .copyCategorySourceUrlLoading = false
214
+ }
215
+
216
+ // if Paste-Popover is shown, close it now
217
+ if (this .copyCategorySourceUrlShowPopover ) {
218
+ this .$nextTick (() => {
219
+ this .copyCategorySourceUrlShowPopover = false
220
+ })
221
+ }
222
+ },
223
+ () => {
224
+ this .copyCategorySourceUrlLoading = false
225
+ }
226
+ )
227
+ },
59
228
},
60
229
methods: {
61
230
async createCategory () {
62
231
const createdCategory = await this .create ()
63
232
await this .api .reload (this .camp .categories ())
64
233
this .$router .push (categoryRoute (this .camp , createdCategory, { new: true }))
65
234
},
235
+ refreshCopyCategorySource () {
236
+ navigator .permissions .query ({ name: ' clipboard-read' }).then (
237
+ (p ) => {
238
+ this .clipboardPermission = p .state
239
+ this .copyCategorySource = null
240
+
241
+ if (p .state === ' granted' ) {
242
+ navigator .clipboard
243
+ .readText ()
244
+ .then (async (url ) => {
245
+ this .copyCategorySource = await (
246
+ await this .getCopyCategorySource (url)
247
+ )? ._meta .load
248
+ })
249
+ .catch (() => {
250
+ this .clipboardPermission = ' unaccessable'
251
+ console .warn (' clipboard permission not requestable' )
252
+ })
253
+ }
254
+ },
255
+ () => {
256
+ this .clipboardPermission = ' unaccessable'
257
+ console .warn (' clipboard permission not requestable' )
258
+ }
259
+ )
260
+ },
261
+ async getCopyCategorySource (url ) {
262
+ if (url? .startsWith (window .location .origin )) {
263
+ url = url .substring (window .location .origin .length )
264
+ const match = router .matcher .match (url)
265
+
266
+ if (match .name === ' activity' ) {
267
+ const scheduleEntry = await this .api
268
+ .get ()
269
+ .scheduleEntries ({ id: match .params [' scheduleEntryId' ] })
270
+ return await scheduleEntry .activity ()
271
+ } else if (match .name === ' admin/activity/category' ) {
272
+ return await this .api .get ().categories ({ id: match .params [' categoryId' ] })
273
+ }
274
+ }
275
+ return null
276
+ },
277
+ async clearClipboard () {
278
+ await navigator .clipboard .writeText (' ' )
279
+ this .refreshCopyCategorySource ()
280
+ },
66
281
},
67
282
}
68
283
< / script>
0 commit comments