@sigmaott/base-library-next 2.1.9

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 (71) hide show
  1. package/README.md +1 -0
  2. package/locales/en.yaml +289 -0
  3. package/locales/vi.yaml +294 -0
  4. package/nuxt.config.ts +18 -0
  5. package/package.json +33 -0
  6. package/public/routes.json +34 -0
  7. package/src/api/axios.ts +3 -0
  8. package/src/api/index.ts +86 -0
  9. package/src/api-client-library/.openapi-generator/FILES +20 -0
  10. package/src/api-client-library/.openapi-generator/VERSION +1 -0
  11. package/src/api-client-library/.openapi-generator-ignore +23 -0
  12. package/src/api-client-library/api/health-api.ts +119 -0
  13. package/src/api-client-library/api/presets-api.ts +599 -0
  14. package/src/api-client-library/api/profiles-api.ts +676 -0
  15. package/src/api-client-library/api.ts +20 -0
  16. package/src/api-client-library/base.ts +72 -0
  17. package/src/api-client-library/common.ts +150 -0
  18. package/src/api-client-library/configuration.ts +101 -0
  19. package/src/api-client-library/git_push.sh +57 -0
  20. package/src/api-client-library/index.ts +18 -0
  21. package/src/api-client-library/models/create-preset-dto.ts +223 -0
  22. package/src/api-client-library/models/create-profile-dto.ts +45 -0
  23. package/src/api-client-library/models/health-controller-get-health200-response-info-value.ts +32 -0
  24. package/src/api-client-library/models/health-controller-get-health200-response.ts +51 -0
  25. package/src/api-client-library/models/health-controller-get-health503-response.ts +51 -0
  26. package/src/api-client-library/models/index.ts +7 -0
  27. package/src/api-client-library/models/update-preset-dto.ts +223 -0
  28. package/src/api-client-library/models/update-profile-dto.ts +45 -0
  29. package/src/components/MediaSelection.vue +40 -0
  30. package/src/components/PresetModify.vue +154 -0
  31. package/src/components/PresetTable.vue +114 -0
  32. package/src/components/ProfileAllList.vue +137 -0
  33. package/src/components/ProfileFormModal.vue +79 -0
  34. package/src/components/ProfileModify.vue +152 -0
  35. package/src/components/ProfileTable.vue +68 -0
  36. package/src/components/WatermarkDraggableItem.vue +88 -0
  37. package/src/components/channel/ConfigWatermarkItem.vue +239 -0
  38. package/src/components/channel/WatermarkPreview.vue +19 -0
  39. package/src/components/common/Vue3DraggableResizable/Container.vue +71 -0
  40. package/src/components/common/Vue3DraggableResizable/index.vue +1327 -0
  41. package/src/components/common/Vue3DraggableResizable/utils/dom.js +63 -0
  42. package/src/components/common/Vue3DraggableResizable/utils/fns.js +37 -0
  43. package/src/components/common/VueDraggableResizable/dom.js +63 -0
  44. package/src/components/common/VueDraggableResizable/fns.js +37 -0
  45. package/src/components/common/VueDraggableResizable/index.vue +958 -0
  46. package/src/components/preset/ConfigItem.vue +956 -0
  47. package/src/components/profile/ConfigItem.vue +765 -0
  48. package/src/components/profile/TableColumns.vue +137 -0
  49. package/src/components/shared/AudioInfoViewer.vue +101 -0
  50. package/src/components/shared/MediaInfoViewer.vue +249 -0
  51. package/src/components/shared/MediaInfoViewerSmall.vue +105 -0
  52. package/src/components/shared/PopoverProfile.vue +17 -0
  53. package/src/components/shared/VideoInfoViewer.vue +136 -0
  54. package/src/components/shared/fileSizeFilter.ts +26 -0
  55. package/src/composables/preset.ts +141 -0
  56. package/src/public/apple-touch-icon-180x180.png +0 -0
  57. package/src/public/build-time.json +1 -0
  58. package/src/public/favicon.ico +0 -0
  59. package/src/public/favicon.svg +15 -0
  60. package/src/public/logo.png +0 -0
  61. package/src/public/logo.svg +9 -0
  62. package/src/public/maskable-icon-512x512.png +0 -0
  63. package/src/public/pwa-192x192.png +0 -0
  64. package/src/public/pwa-512x512.png +0 -0
  65. package/src/public/pwa-64x64.png +0 -0
  66. package/src/public/routes.json +87 -0
  67. package/src/utils/common.ts +175 -0
  68. package/src/utils/config.ts +19 -0
  69. package/src/utils/preset.ts +353 -0
  70. package/src/utils/profile.ts +30 -0
  71. package/tsconfig.json +3 -0
@@ -0,0 +1,136 @@
1
+ <script setup lang="ts">
2
+ import { fileSizeFilter } from './fileSizeFilter'
3
+
4
+ defineProps<{ video: any }>()
5
+ </script>
6
+
7
+ <template>
8
+ <div class="item-list-row flex justify-center">
9
+ <ul class="flex flex-wrap justify-center">
10
+ <li title="Name">
11
+ <el-icon v-if="video.codec !== 'copy'" class="text-primary" name="video-camera-solid">
12
+ <div class="i-ep:camera-filled text-primary" />
13
+ </el-icon>&nbsp;
14
+ <el-icon v-if="video.codec === 'copy'" class="text-indigo-500">
15
+ <div class="i-ep:document-copy text-indigo-500" />
16
+ </el-icon>
17
+ <span v-if="video.codec === 'copy'">&nbsp;</span>
18
+ <span class="item__name" :title="video.name">{{
19
+ (video && video.name) || '-'
20
+ }}</span>
21
+ </li>
22
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
23
+ <li title="Codec">
24
+ <el-icon>
25
+ <div class="i-ep:grid text-indigo-500" />
26
+ </el-icon>&nbsp;
27
+ <span>{{ video.codec }}</span>
28
+ </li>
29
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
30
+ <li title="Resolution">
31
+ <el-icon name="crop">
32
+ <div class="i-ep:crop text-pink-600" />
33
+ </el-icon>&nbsp;
34
+ <span>{{ video.width }}x{{ video.height }}</span>
35
+ </li>
36
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
37
+ <li title="Bitrate">
38
+ <el-icon>
39
+ <div class="i-ep:scale-to-original text-teal-600" />
40
+ </el-icon>&nbsp;
41
+ <span>{{ video.bitrate ? fileSizeFilter(video.bitrate) : fileSizeFilter(video.videoBitrate) }}/s</span>
42
+ </li>
43
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
44
+ <li title="FPS">
45
+ <el-icon>
46
+ <div class="i-ep:sort text-green-600" />
47
+ </el-icon>&nbsp;
48
+ <span>{{ video.fps }} fps</span>
49
+ </li>
50
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
51
+ <li title="CBR">
52
+ <el-icon>
53
+ <div class="i-ep:switch-button" :class="!!video.cbr ? 'text-success' : 'text-gray-400'" />
54
+ </el-icon>
55
+ &nbsp;
56
+ <span> cbr </span>
57
+ </li>
58
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
59
+ <li title="HDR">
60
+ <el-icon>
61
+ <div class="i-carbon:distribute-horizontal-right text-success" :class="video.hdr ? 'text-success' : 'text-gray-400'" />
62
+ </el-icon>
63
+ &nbsp;
64
+ <span> hdr </span>
65
+ </li>
66
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
67
+ <li title="Pixel format">
68
+ <el-icon>
69
+ <div class="i-ep:picture" :class="!video.pixelFormat || 'text-indigo-500'" />
70
+ </el-icon>&nbsp;
71
+ <span>{{ video.pixelFormat || '-' }}</span>
72
+ </li>
73
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
74
+ <li title="Scale type">
75
+ <el-icon>
76
+ <div class="i-ep:scale-to-original" :class="!video.scaleType || 'text-pink-600'" />
77
+ </el-icon>&nbsp;
78
+ <span>{{ video.scaleType || '-' }}</span>
79
+ </li>
80
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
81
+ <li title="Constant quality">
82
+ <el-icon>
83
+ <div class="i-ep:star-filled" :class="!video.cq || 'text-teal-600'" />
84
+ </el-icon>&nbsp;
85
+ <span>{{ video.cq || '-' }}</span>
86
+ </li>
87
+ <span class="text-gray-400">&nbsp;|&nbsp;</span>
88
+ <li title="Interlaced mode">
89
+ <el-icon>
90
+ <div class="i-ep:switch-button" :class="video.interlaced ? 'text-success' : 'text-gray-400'" />
91
+ </el-icon>&nbsp;
92
+ <span> interlaced </span>
93
+ </li>
94
+ </ul>
95
+ <div v-if="$slots && $slots.anchor" class="anchor">
96
+ <slot name="anchor" />
97
+ </div>
98
+ </div>
99
+ </template>
100
+
101
+ <style lang="scss" scoped>
102
+ .item-list-row {
103
+ margin: 5px;
104
+ padding: 5px;
105
+ position: relative;
106
+ border: 1px solid rgba(64, 158, 255, 0.4);
107
+ border-radius: 4px;
108
+
109
+ ul {
110
+ li {
111
+ white-space: nowrap;
112
+ display: flex;
113
+ align-items: center;
114
+ }
115
+
116
+ .item__name {
117
+ max-width: 85px;
118
+ overflow: hidden;
119
+ white-space: nowrap;
120
+ text-overflow: ellipsis;
121
+ }
122
+ }
123
+
124
+ .anchor {
125
+ padding: 5px;
126
+ margin: -5px -5px -5px 5px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ border-left: 1px solid;
130
+ border-color: inherit;
131
+ background-color: rgba(64, 158, 255, 0.2);
132
+ border-top-right-radius: inherit;
133
+ border-bottom-right-radius: inherit;
134
+ }
135
+ }
136
+ </style>
@@ -0,0 +1,26 @@
1
+ export function fileSizeFilter(size: number) {
2
+ if (Number.isNaN(size))
3
+ size = 0
4
+
5
+ if (size < 1024)
6
+ return `${size} Kb`
7
+
8
+ size /= 1024
9
+
10
+ if (size < 1024)
11
+ return `${size.toFixed(2)} KB`
12
+
13
+ size /= 1024
14
+
15
+ if (size < 1024)
16
+ return `${size.toFixed(2)} MB`
17
+
18
+ size /= 1024
19
+
20
+ if (size < 1024)
21
+ return `${size.toFixed(2)} GB`
22
+
23
+ size /= 1024
24
+
25
+ return `${size.toFixed(2)} TB`
26
+ }
@@ -0,0 +1,141 @@
1
+ import { presetsApi, profilesApi } from '../api'
2
+ import type { CreatePresetDto, CreateProfileDto, UpdatePresetDto, UpdateProfileDto } from '../api-client-library'
3
+
4
+ export function usePresetMutation() {
5
+ const { t } = useI18n()
6
+ const queryClient = useQueryClient()
7
+
8
+ const createPresetMutation = useMutation(
9
+ async (createPresetDto: CreatePresetDto) =>
10
+ (await presetsApi.presetsControllerCreate({ createPresetDto })).data,
11
+ {
12
+ onSettled() {
13
+ queryClient.invalidateQueries([QueryKeys.LibraryPreset])
14
+ },
15
+ },
16
+ )
17
+
18
+ const updatePresetMutation = useMutation(
19
+ async ({ id, updatePresetDto }: { id: string; updatePresetDto: UpdatePresetDto }) =>
20
+ (await presetsApi.presetsControllerUpdate({ id, updatePresetDto })).data,
21
+ {
22
+ onSettled() {
23
+ queryClient.invalidateQueries([QueryKeys.LibraryPreset])
24
+ },
25
+ },
26
+ )
27
+
28
+ // const deleteSession = useMutation(
29
+ // async (id: string) => (await sessionsApi.sessionControllerDelete({ id })).data,
30
+ // {
31
+ // onSettled: () => {
32
+ // queryClient.invalidateQueries([QueryKeys.SESSION])
33
+ // },
34
+ // },
35
+ // )
36
+
37
+ // const renameSession = useMutation(
38
+ // async ({ id, renameSessionDto }: { id: string; renameSessionDto: RenameSessionDto }) => (await sessionsApi.sessionControllerRename({ id, renameSessionDto })).data,
39
+ // {
40
+ // onSettled: () => {
41
+ // queryClient.invalidateQueries([QueryKeys.SESSION])
42
+ // },
43
+ // onSuccess() {
44
+ // ElMessage({
45
+ // type: 'success',
46
+ // message: t('LiveChannel.message.sessionSuccessfullyRename'),
47
+ // })
48
+ // },
49
+ // },
50
+ // )
51
+
52
+ // function handleDeleteSession(id: string) {
53
+ // ElMessageBox.confirm(
54
+ // t('LiveChannel.message.confirmDeleteSession'),
55
+ // `${t('LiveChannel.label.deleteSession')}?`,
56
+ // {
57
+ // confirmButtonText: t('Actions.delete'),
58
+ // cancelButtonText: t('Actions.cancel'),
59
+ // type: 'warning',
60
+ // },
61
+ // )
62
+ // .then(async () => {
63
+ // await deleteSession.mutateAsync(id)
64
+ // ElMessage({
65
+ // message: t('LiveChannel.message.successDelete'),
66
+ // type: 'success',
67
+ // })
68
+ // })
69
+ // }
70
+
71
+ // function handleFinishSession(id: string, cb?: () => void) {
72
+ // ElMessageBox.confirm(
73
+ // t('LiveChannel.message.confirmFinishSession'),
74
+ // t('LiveChannel.message.warning'),
75
+ // {
76
+ // confirmButtonText: t('Actions.ok'),
77
+ // cancelButtonText: t('Actions.cancel'),
78
+ // type: 'warning',
79
+ // },
80
+ // )
81
+ // .then(async () => {
82
+ // await finishSession.mutateAsync(id)
83
+ // if (cb)
84
+ // cb()
85
+ // ElMessage({
86
+ // type: 'success',
87
+ // message: t('LiveChannel.message.sessionSuccessfullyTerminated'),
88
+ // })
89
+ // })
90
+ // .catch(() => {})
91
+ // }
92
+
93
+ const isMutationLoading = computed(() =>
94
+ createPresetMutation.isLoading.value
95
+ || updatePresetMutation.isLoading.value,
96
+ )
97
+
98
+ return {
99
+ createPresetMutation,
100
+ updatePresetMutation,
101
+ isMutationLoading,
102
+ }
103
+ }
104
+
105
+ export function useProfileMutation() {
106
+ const { t } = useI18n()
107
+ const queryClient = useQueryClient()
108
+
109
+ const createProfileMutation
110
+ = useMutation(
111
+ async (createProfileDto: CreateProfileDto) =>
112
+ (await profilesApi.httpProfilesControllerCreate({ createProfileDto })).data,
113
+ {
114
+ onSettled() {
115
+ queryClient.invalidateQueries([QueryKeys.LibraryProfile])
116
+ },
117
+ },
118
+ )
119
+
120
+ const updateProfileMutation
121
+ = useMutation(
122
+ async ({ id, updateProfileDto }: { id: string; updateProfileDto: UpdateProfileDto }) =>
123
+ (await profilesApi.httpProfilesControllerUpdate({ updateProfileDto, id })).data,
124
+ {
125
+ onSettled() {
126
+ queryClient.invalidateQueries([QueryKeys.LibraryProfile])
127
+ },
128
+ },
129
+ )
130
+
131
+ const isMutationLoading = computed(() =>
132
+ createProfileMutation.isLoading.value
133
+ || updateProfileMutation.isLoading.value,
134
+ )
135
+
136
+ return {
137
+ createProfileMutation,
138
+ updateProfileMutation,
139
+ isMutationLoading,
140
+ }
141
+ }
@@ -0,0 +1 @@
1
+ 1752806726088
Binary file
@@ -0,0 +1,15 @@
1
+ <svg version="1.1" id="Group_1_copy_11" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
2
+ y="0px" viewBox="0 0 286.3 286.3" style="enable-background:new 0 0 286.3 286.3;" xml:space="preserve">
3
+ <style type="text/css">
4
+ .st0{fill:#D71921;}
5
+ </style>
6
+ <g>
7
+ <path class="st0" d="M171.6,139.6l-35.9-23.7c-5.2-3.4-12,0.3-12,6.5v47.4c0,6.2,6.9,9.9,12,6.5l35.9-23.7
8
+ C176.3,149.5,176.3,142.7,171.6,139.6z"/>
9
+ <path class="st0" d="M282.5,126.4h-12.4h-32.4h-23C206,95,177.4,71.9,143.3,71.9S80.6,95,72.1,126.4H48.8c8-45,47.1-79.1,94.4-79.1
10
+ c35.6,0,66.7,19.4,83.3,48.3h48.8c-19.5-54-71.3-92.6-132-92.6C65.6,2.8,2.8,65.7,2.8,143.2c0,7,0.5,13.9,1.5,20.6h11.9h33.2h22.3
11
+ c8.1,32.1,37.1,56,71.6,56s63.6-23.9,71.6-56h21.9c-9.4,43.1-47.8,75.4-93.7,75.4c-34,0-63.9-17.7-81-44.5H12.6
12
+ c20.5,52,71.2,88.9,130.6,88.9c77.5,0,140.3-62.8,140.3-140.3C283.5,137.5,283.1,131.9,282.5,126.4z M143.3,197.4
13
+ c-28.4,0-51.6-23.1-51.6-51.6c0-28.4,23.1-51.6,51.6-51.6s51.6,23.1,51.6,51.6C194.9,174.2,171.8,197.4,143.3,197.4z"/>
14
+ </g>
15
+ </svg>
Binary file
@@ -0,0 +1,9 @@
1
+ <svg width="192" height="192" viewBox="0 0 192 192" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
2
+ <rect width="192" height="192" fill="url(#pattern0)"/>
3
+ <defs>
4
+ <pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
5
+ <use xlink:href="#image0_8_2" transform="scale(0.0034965)"/>
6
+ </pattern>
7
+ <image id="image0_8_2" width="286" height="286" xlink:href="data:image/png;base64,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"/>
8
+ </defs>
9
+ </svg>
Binary file
Binary file
Binary file
@@ -0,0 +1,87 @@
1
+ [
2
+ {
3
+ "name": "channels",
4
+ "path": "/channels",
5
+ "meta": {
6
+ "title": "Channels",
7
+ "key": "SSAI.channels",
8
+ "icon": "i-carbon:airplay",
9
+ "requiresAuth": false,
10
+ "keepAlive": true,
11
+ "notAsMenu": false,
12
+ "order": 1
13
+ }
14
+ },
15
+ {
16
+ "name": "ads",
17
+ "path": "/ads",
18
+ "meta": {
19
+ "title": "Ads List",
20
+ "key": "SSAI.adsList",
21
+ "icon": "i-carbon:layers",
22
+ "requiresAuth": false,
23
+ "keepAlive": true,
24
+ "notAsMenu": false,
25
+ "order": 2
26
+ }
27
+ },
28
+ {
29
+ "name": "statistic",
30
+ "path": "/statistic",
31
+ "meta": {
32
+ "title": "Statistic",
33
+ "key": "SSAI.Statistic",
34
+ "icon": "i-carbon:analytics",
35
+ "requiresAuth": false,
36
+ "keepAlive": false,
37
+ "notAsMenu": false,
38
+ "order": 3
39
+ },
40
+ "children": [
41
+ {
42
+ "name": "statistic-index-monitor-system",
43
+ "path": "/statistic/monitor-system",
44
+ "meta": {
45
+ "title": "Monitor System",
46
+ "key": "SSAI.statistic.monitorSystem",
47
+ "icon": "i-carbon:ibm-cloud-pak-system",
48
+ "requiresAuth": false,
49
+ "keepAlive": true
50
+ }
51
+ },
52
+ {
53
+ "name": "statistic-index-fillrate",
54
+ "path": "/statistic/fillrate",
55
+ "meta": {
56
+ "title": "Fill Rate",
57
+ "key": "SSAI.statistic.fillRate",
58
+ "icon": "i-carbon:bare-metal-server",
59
+ "requiresAuth": false,
60
+ "keepAlive": true
61
+ }
62
+ },
63
+ {
64
+ "name": "statistic-index-event-chart",
65
+ "path": "/statistic/event-chart",
66
+ "meta": {
67
+ "title": "Event Chart",
68
+ "key": "SSAI.statistic.eventChart",
69
+ "icon": "i-carbon:chart-column",
70
+ "requiresAuth": false,
71
+ "keepAlive": true
72
+ }
73
+ },
74
+ {
75
+ "name": "statistic-index-data-report",
76
+ "path": "/statistic/data-report",
77
+ "meta": {
78
+ "title": "Data Report",
79
+ "key": "SSAI.statistic.dataReport",
80
+ "icon": "i-carbon:report-data",
81
+ "requiresAuth": false,
82
+ "keepAlive": true
83
+ }
84
+ }
85
+ ]
86
+ }
87
+ ]
@@ -0,0 +1,175 @@
1
+ import { objectMap } from '@antfu/utils'
2
+
3
+ export function emptyStr2Undefined(obj: Record<string, any>): Record<string, any> {
4
+ return objectMap(obj, (k, v) => !v ? undefined : [k, v])
5
+ }
6
+
7
+ /**
8
+ * Generate rules for validation form that contain fields are sent to api
9
+ * @param {Object} laws
10
+ */
11
+ export function genApiValidationRules(laws) {
12
+ const required = laws?.required || []
13
+ const props = laws?.properties || {}
14
+ const rules = {}
15
+
16
+ function generateRequiredRule(desc) {
17
+ return {
18
+ required: required.includes(desc.field),
19
+ message: 'Shouldn\'t be empty',
20
+ trigger: ['change', 'blur'],
21
+ }
22
+ }
23
+
24
+ function rulesForNumber(rule) {
25
+ const rules = []
26
+
27
+ const required = generateRequiredRule(rule)
28
+ rules.push(required)
29
+
30
+ function isNumber(value) {
31
+ return typeof value === 'number'
32
+ }
33
+
34
+ function isValidMin(value, min) {
35
+ return value >= min
36
+ }
37
+
38
+ function isValidMax(value, max) {
39
+ return value <= max
40
+ }
41
+
42
+ function validate(currentRule, value, callback) {
43
+ if (!isNumber(value)) {
44
+ if (value)
45
+ callback(new Error('Should be a number'))
46
+
47
+ else
48
+ callback()
49
+
50
+ return
51
+ }
52
+
53
+ if ((rule.minimum || rule.minimum === 0) && !isValidMin(value, rule.minimum)) {
54
+ callback(new Error(`Should be greater than or equal ${rule.minimum}`))
55
+ return
56
+ }
57
+ if (rule.maximum && !isValidMax(value, rule.maximum)) {
58
+ callback(new Error(`Should be less than or equal ${rule.maximum}`))
59
+ return
60
+ }
61
+ if (rule.enum && Array.isArray(rule.enum) && !rule.enum.includes(value)) {
62
+ callback(new Error(`Must be one of values: ${rule.num.join(', ')}`))
63
+ return
64
+ }
65
+ callback()
66
+ }
67
+
68
+ const tmpRule = {
69
+ validator: validate,
70
+ trigger: ['blur', 'change'],
71
+ }
72
+ rules.push(tmpRule)
73
+
74
+ return rules
75
+ }
76
+
77
+ function rulesForString(rule) {
78
+ const rules = []
79
+
80
+ const required = generateRequiredRule(rule)
81
+ rules.push(required)
82
+
83
+ function validate(currentRule, value, callback) {
84
+ if (rule.length && !value.length === rule.length)
85
+ return callback(new Error(`${rule.label} must be ${rule.length} characters`))
86
+
87
+ if (rule.minLength && value.length < rule.minLength)
88
+ return callback(new Error(`Min ${rule.minLength} character(s)`))
89
+
90
+ if (rule.maxLength && value.length > rule.maxLength)
91
+ return callback(new Error(`Max ${rule.maxLength} character(s)`))
92
+
93
+ if (rule.pattern) {
94
+ const pattern = rule.pattern
95
+ const patternStr = pattern.slice(1, -1)
96
+ const regex = new RegExp(patternStr)
97
+ if (!regex.test(value))
98
+ return callback(new Error(`Invalid format ${rule.pattern}`))
99
+ }
100
+ if (rule.enum && Array.isArray(rule.enum)) {
101
+ if (value && !rule.enum.includes(value))
102
+ return callback(new Error(`Must be one of values: ${rule.enum.join(', ')}`))
103
+ }
104
+ return callback()
105
+ }
106
+
107
+ rules.push({
108
+ validator: validate,
109
+ trigger: ['change', 'blur'],
110
+ })
111
+
112
+ return rules
113
+ }
114
+
115
+ function rulesForBoolean(rule) {
116
+ const rules = []
117
+
118
+ const required = generateRequiredRule(rule)
119
+ rules.push(required)
120
+
121
+ function isBoolean(value) {
122
+ return typeof value === 'boolean'
123
+ }
124
+
125
+ function validate(currentRule, value, callback) {
126
+ if (!isBoolean(value))
127
+ callback(new Error('Should be a boolean value'))
128
+
129
+ else
130
+ callback()
131
+ }
132
+
133
+ const tmpRule = {
134
+ validator: validate,
135
+ trigger: ['change', 'blur'],
136
+ }
137
+ rules.push(tmpRule)
138
+
139
+ return rules
140
+ }
141
+
142
+ for (const field in props) {
143
+ const desc = props[field]
144
+ if (desc.$ref) {
145
+ // Check if rule is a reference
146
+ continue
147
+ }
148
+ else {
149
+ desc.field = field
150
+ if (desc.title)
151
+ desc.title = field
152
+
153
+ const fieldType = desc.type
154
+
155
+ switch (fieldType) {
156
+ case 'string': {
157
+ const rule = rulesForString(desc)
158
+ rules[field] = rule
159
+ break
160
+ }
161
+ case 'number': {
162
+ const rule = rulesForNumber(desc)
163
+ rules[field] = rule
164
+ break
165
+ }
166
+ case 'boolean': {
167
+ const rule = rulesForBoolean(desc)
168
+ rules[field] = rule
169
+ break
170
+ }
171
+ }
172
+ }
173
+ }
174
+ return rules
175
+ }
@@ -0,0 +1,19 @@
1
+ export const VIDEO_CODECS = {
2
+ H264: 'h264',
3
+ HEVC: 'hevc',
4
+ COPY: 'copy',
5
+ }
6
+
7
+ export const AUDIO_CODECS = {
8
+ AAC: 'aac',
9
+ AC3: 'ac3',
10
+ EAC3: 'eac3',
11
+ MP2: 'mp2',
12
+ COPY: 'copy',
13
+ }
14
+
15
+ export const DATA_CODECS = {
16
+ SCTE35: 'scte35',
17
+ ID3: 'id3',
18
+ COPY: 'copy',
19
+ }