diff --git a/zero.Backoffice.UI/app/editor/fields/toggle.vue b/zero.Backoffice.UI/app/editor/fields/toggle.vue index e4930629..e7e2098c 100644 --- a/zero.Backoffice.UI/app/editor/fields/toggle.vue +++ b/zero.Backoffice.UI/app/editor/fields/toggle.vue @@ -1,5 +1,5 @@  diff --git a/zero.Backoffice.UI/app/modules/media/components/field-videopicker.vue b/zero.Backoffice.UI/app/modules/media/components/field-videopicker.vue new file mode 100644 index 00000000..f2605a5f --- /dev/null +++ b/zero.Backoffice.UI/app/modules/media/components/field-videopicker.vue @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/media/components/ui-videopicker.vue b/zero.Backoffice.UI/app/modules/media/components/ui-videopicker.vue new file mode 100644 index 00000000..25d22534 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/media/components/ui-videopicker.vue @@ -0,0 +1,171 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/media/overlays/videopicker.vue b/zero.Backoffice.UI/app/modules/media/overlays/videopicker.vue new file mode 100644 index 00000000..c6677b06 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/media/overlays/videopicker.vue @@ -0,0 +1,197 @@ + + + + + + \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/media/plugin.ts b/zero.Backoffice.UI/app/modules/media/plugin.ts index b53aa60e..6dfc3970 100644 --- a/zero.Backoffice.UI/app/modules/media/plugin.ts +++ b/zero.Backoffice.UI/app/modules/media/plugin.ts @@ -7,9 +7,11 @@ export default { install(app: ZeroPluginOptions) { app.vue.component('ui-mediapicker', defineAsyncComponent(() => import('./components/ui-mediapicker.vue'))); + app.vue.component('ui-videopicker', defineAsyncComponent(() => import('./components/ui-videopicker.vue'))); app.fieldType('media', defineAsyncComponent(() => import('./components/field-mediapicker.vue'))); app.fieldType('image', defineAsyncComponent(() => import('./components/field-imagepicker.vue'))); + app.fieldType('video', defineAsyncComponent(() => import('./components/field-videopicker.vue'))); app.route({ name: 'media', path: '/media/:parentId?', component: () => import('./pages/overview/overview.vue'), props: true }); app.route({ name: 'media-edit', path: '/media/edit/:id?', component: () => import('./pages/detail/detail.vue'), props: true }); @@ -35,6 +37,12 @@ declare module 'zero/schemas' * @param {MediaBaseFieldOptions} [options] - Custom options */ image(options?: MediaBaseFieldOptions): ZeroEditorField; + + /** + * Renders an video picker + * @param {PickerFieldOptions} [options] - Custom options + */ + video(options?: PickerFieldOptions): ZeroEditorField; } export type MediaTypeFor = 'all' | 'images' | 'videos' | 'documents'; diff --git a/zero.Backoffice.UI/app/modules/media/videoparser.ts b/zero.Backoffice.UI/app/modules/media/videoparser.ts new file mode 100644 index 00000000..2f230a62 --- /dev/null +++ b/zero.Backoffice.UI/app/modules/media/videoparser.ts @@ -0,0 +1,110 @@ + +export const YOUTUBE_REGEX = /youtu(?:\.be|be\.com)\/(?:.*v(?:\/|=)|(?:.*\/)?)([a-zA-Z0-9-_]+)/gim; +export const VIMEO_REGEX = /vimeo\.com\/(\d+)($|\/)/gim; + + +/** + * Get video ID from an URL. + * This currently works for YouTube and Vimeo links. + * @param {string} url + */ +export let getVideoId = url => +{ + if (!url) + { + return null; + } + + if (url.indexOf('vimeo.com') > -1) + { + let matches = VIMEO_REGEX.exec(url); + return matches && matches[1]; + } + else if (url.indexOf('youtu') > -1) + { + let matches = YOUTUBE_REGEX.exec(url); + return matches && matches[1]; + } + + return null; +}; + + +/** + * Get metadata for a vimeo ID. + * @param {string} id + */ +export let getVimeoMetadata = async id => +{ + let result = { + id, + url: `https://vimeo.com/${id}`, + success: false + }; + + let response = await fetch(`https://vimeo.com/api/v2/video/${id}.json`); + + if (response.ok) + { + let data = await response.json(); + result.data = data[0]; + result.image = result.data.thumbnail_large; + result.title = result.data.title; + result.description = result.data.description; + result.success = true; + } + + return result; +}; + + +/** + * Get metdata for a YouTube ID. + * @param {string} url + */ +export let getYoutubeMetadata = async id => +{ + const apiKey = 'AIzaSyD720TVsYEil4PTESJ9jD0Xijd0zldExmc'; // TODO v3 make configurable + + let result = { + id, + image: `https://i3.ytimg.com/vi/${id}/maxresdefault.jpg`, + url: `https://www.youtube.com/watch?v=${id}`, + success: true + }; + + if (apiKey) + { + result.success = false; + + let response = await fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${id}&key=${apiKey}`); + + if (response.ok) + { + let data = await response.json(); + + if (data && data.items && data.items.length) + { + result.data = data.items[0].snippet; + + let thumbs = Object.values(result.data.thumbnails); + let thumb = thumbs[thumbs.length - 1]; + result.image = thumb.url; + result.title = result.data.title; + result.description = result.data.description; + result.success = true; + } + } + + return result; + } + else + { + return { + id, + image: `https://i3.ytimg.com/vi/${id}/maxresdefault.jpg`, + url: `https://www.youtube.com/watch?v=${id}`, + success: true + }; + } +}; \ No newline at end of file diff --git a/zero.Backoffice.UI/app/modules/settings/demo.ts b/zero.Backoffice.UI/app/modules/settings/demo.ts index 28147a9b..66b3ce26 100644 --- a/zero.Backoffice.UI/app/modules/settings/demo.ts +++ b/zero.Backoffice.UI/app/modules/settings/demo.ts @@ -4,6 +4,9 @@ import { formatDate } from '../../utils/dates'; const editor = new ZeroEditor('demo'); +editor.field('categoryIds').categoryPicker({ limit: 20, pickChannel: true }); +editor.field('video').video(); + editor.field('rte', { label: 'Number' }).rte(); editor.field('rte').output({ html: false }); editor.field('link').linkPicker(); diff --git a/zero.Backoffice.UI/app/modules/settings/demo.vue b/zero.Backoffice.UI/app/modules/settings/demo.vue index e5fd7da9..b76fe6f0 100644 --- a/zero.Backoffice.UI/app/modules/settings/demo.vue +++ b/zero.Backoffice.UI/app/modules/settings/demo.vue @@ -26,7 +26,9 @@ productId1: null, productId2: [], productId3: null, - link: null + link: null, + categoryIds: [], + video: null }, route: 'demo', disabled: false diff --git a/zero.Backoffice.UI/app/modules/settings/plugin.ts b/zero.Backoffice.UI/app/modules/settings/plugin.ts index 7a34010c..3ea2a96d 100644 --- a/zero.Backoffice.UI/app/modules/settings/plugin.ts +++ b/zero.Backoffice.UI/app/modules/settings/plugin.ts @@ -6,7 +6,7 @@ export default { install(app: ZeroPluginOptions) { app.route({ name: 'settings', path: '/settings', component: () => import('./settings.vue') }); - //app.route({ name: 'demo', path: '/settings/demo', component: () => import('./demo.vue') }); + app.route({ name: 'demo', path: '/settings/demo', component: () => import('./demo.vue') }); app.schema('demo', () => import('./demo')); } diff --git a/zero.Backoffice.UI/app/modules/settings/settings.vue b/zero.Backoffice.UI/app/modules/settings/settings.vue index 0e841d3f..b0a180f8 100644 --- a/zero.Backoffice.UI/app/modules/settings/settings.vue +++ b/zero.Backoffice.UI/app/modules/settings/settings.vue @@ -45,17 +45,17 @@ this.groups = useUiStore().settingGroups; this.appCount = useAppStore().applications.length; - //if (!this.groups[1].items.find(x => x.alias === 'demo')) - //{ - // this.groups[1].items.push({ - // alias: "demo", - // description: "Demo all editor components", - // icon: "fth-box", - // isPlugin: true, - // name: "Components", - // url: "/settings/demo" - // }); - //} + if (!this.groups[1].items.find(x => x.alias === 'demo')) + { + this.groups[1].items.push({ + alias: "demo", + description: "Demo all editor components", + icon: "fth-box", + isPlugin: true, + name: "Components", + url: "/settings/demo" + }); + } } } diff --git a/zero.Backoffice/Resources/Localization/zero.en-us.json b/zero.Backoffice/Resources/Localization/zero.en-us.json index 8f477fbe..5d2fa743 100644 --- a/zero.Backoffice/Resources/Localization/zero.en-us.json +++ b/zero.Backoffice/Resources/Localization/zero.en-us.json @@ -658,6 +658,14 @@ "html": "Media upload", "youtube": "YouTube", "vimeo": "Vimeo" + }, + "fields": { + "provider": "Provider", + "videoUrl": "URL", + "videoId": "Pick video", + "title": "Title", + "previewImageId": "Poster image", + "foundParsed": "Parsed video ID is {id}" } }, diff --git a/zero.Backoffice/Services/Resources/ResourceService.cs b/zero.Backoffice/Services/Resources/ResourceService.cs index 908aaa01..a1f34ecd 100644 --- a/zero.Backoffice/Services/Resources/ResourceService.cs +++ b/zero.Backoffice/Services/Resources/ResourceService.cs @@ -80,9 +80,6 @@ public class ResourceService : IResourceService catch { } } - Logger.LogInformation("translation path [0]: " + Path.Combine(AppContext.BaseDirectory, path)); - Logger.LogInformation("translation path [1]: " + fullpath); - if (!File.Exists(fullpath)) { return items;