@saooti/octopus-sdk 41.10.9 → 41.11.0-beta2
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/CHANGELOG.md +27 -0
- package/index.ts +9 -10
- package/package.json +7 -6
- package/src/api/index.ts +9 -0
- package/src/api/mediathequeApi.ts +75 -0
- package/src/components/composable/useRights.ts +316 -144
- package/src/components/display/RightsIndicator.vue +215 -0
- package/src/components/misc/ClassicAlert.vue +15 -10
- package/src/components/misc/ClassicNav.vue +5 -0
- package/src/components/misc/ClassicPopover.vue +34 -65
- package/src/components/misc/ClassicTabs.vue +57 -0
- package/src/locale/de.json +6 -1
- package/src/locale/en.json +6 -1
- package/src/locale/es.json +6 -1
- package/src/locale/fr.json +7 -1
- package/src/locale/it.json +6 -1
- package/src/locale/sl.json +6 -1
- package/src/stores/FilterStore.ts +0 -23
- package/src/stores/class/cartouchier/cartouchier.ts +2 -0
- package/src/stores/class/general/media.ts +2 -0
- package/src/stores/class/radio/mix.ts +2 -0
- package/src/stores/class/radio/playlistMedia.ts +2 -0
- package/tests/components/composable/useRights.spec.ts +328 -1
- package/tests/components/display/RightsIndicator.spec.ts +345 -0
- package/tests/components/misc/ClassicPopover.spec.ts +64 -51
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div v-if="displayIndicator">
|
|
3
|
+
<div v-if="!text">
|
|
4
|
+
<LockIcon
|
|
5
|
+
:id="iconId"
|
|
6
|
+
:class="{ invisible: hasAccess }"
|
|
7
|
+
aria-hidden="true"
|
|
8
|
+
fill-color="var(--octopus-primary)"
|
|
9
|
+
/>
|
|
10
|
+
<ClassicPopover
|
|
11
|
+
v-if="!hasAccess"
|
|
12
|
+
:target="iconId"
|
|
13
|
+
only-mouse
|
|
14
|
+
>
|
|
15
|
+
{{ message }}
|
|
16
|
+
</ClassicPopover>
|
|
17
|
+
</div>
|
|
18
|
+
<ClassicAlert
|
|
19
|
+
v-else-if="!hasAccess"
|
|
20
|
+
type="info"
|
|
21
|
+
>
|
|
22
|
+
<template #icon>
|
|
23
|
+
<LockIcon
|
|
24
|
+
aria-hidden="true"
|
|
25
|
+
fill-color="var(--octopus-primary)"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|
|
28
|
+
{{ message }}
|
|
29
|
+
</ClassicAlert>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import { Mix } from '../../stores/class/radio/mix';
|
|
35
|
+
import { PlaylistMedia } from '../../stores/class/radio/playlistMedia';
|
|
36
|
+
import { Podcast } from '../../stores/class/general/podcast';
|
|
37
|
+
import { Cartouchier } from '../../stores/class/cartouchier/cartouchier';
|
|
38
|
+
import { Media } from "../../stores/class/general/media";
|
|
39
|
+
import { computed, getCurrentInstance } from 'vue';
|
|
40
|
+
import { useI18n } from 'vue-i18n';
|
|
41
|
+
import LockIcon from 'vue-material-design-icons/Lock.vue';
|
|
42
|
+
import { ActionRight, useRights } from '../composable/useRights';
|
|
43
|
+
import ClassicAlert from '../misc/ClassicAlert.vue';
|
|
44
|
+
import ClassicPopover from '../misc/ClassicPopover.vue';
|
|
45
|
+
import { state } from '../../stores/ParamSdkStore';
|
|
46
|
+
import { useAuthStore } from '../../stores/AuthStore';
|
|
47
|
+
import { storeToRefs } from 'pinia';
|
|
48
|
+
|
|
49
|
+
const rights = useRights();
|
|
50
|
+
const { t, te } = useI18n();
|
|
51
|
+
const { isAuthenticated } = storeToRefs(useAuthStore());
|
|
52
|
+
|
|
53
|
+
type Action = 'create'|'edit'|'delete'|'any';
|
|
54
|
+
|
|
55
|
+
interface PropsBase {
|
|
56
|
+
/** Type of action to check */
|
|
57
|
+
action: Action;
|
|
58
|
+
/** Display reason as text instead of only tooltip */
|
|
59
|
+
text?: boolean;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
type NeverEntities = {
|
|
63
|
+
podcast?: never;
|
|
64
|
+
cartouchier?: never;
|
|
65
|
+
mix?: never;
|
|
66
|
+
playlistMedia?: never;
|
|
67
|
+
media?: never;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
interface PropsPodcast extends PropsBase, Omit<NeverEntities, 'podcast'> {
|
|
71
|
+
podcast: Podcast|boolean;
|
|
72
|
+
}
|
|
73
|
+
interface PropsCartouchier extends PropsBase, Omit<NeverEntities, 'cartouchier'> {
|
|
74
|
+
cartouchier: Cartouchier|boolean;
|
|
75
|
+
}
|
|
76
|
+
interface PropsMix extends PropsBase, Omit<NeverEntities, 'mix'> {
|
|
77
|
+
mix: Mix|boolean;
|
|
78
|
+
}
|
|
79
|
+
interface PropsPlaylistMedia extends PropsBase, Omit<NeverEntities, 'playlistMedia'> {
|
|
80
|
+
playlistMedia: PlaylistMedia|boolean;
|
|
81
|
+
}
|
|
82
|
+
interface PropsMedia extends PropsBase, Omit<NeverEntities, 'media'> {
|
|
83
|
+
media: Media|boolean;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const props = defineProps<PropsPodcast|PropsCartouchier|PropsMix|PropsPlaylistMedia|PropsMedia>();
|
|
87
|
+
|
|
88
|
+
type Rights = ReturnType<typeof useRights>;
|
|
89
|
+
type ActionSegment = 'Create'|'Edit'|'Delete';
|
|
90
|
+
type EntitySegment = 'Podcast'|'Cartouchier'|'Mix'|'PlaylistMedia'|'Media';
|
|
91
|
+
type RightMethodKey = `get${ActionSegment}${EntitySegment}Right` & keyof Rights;
|
|
92
|
+
type RightEntity = Podcast|Cartouchier|Mix|PlaylistMedia|Media|boolean|undefined;
|
|
93
|
+
|
|
94
|
+
const actionSegmentMap: Record<Exclude<Action, 'any'>, ActionSegment> = {
|
|
95
|
+
create: 'Create',
|
|
96
|
+
edit: 'Edit',
|
|
97
|
+
delete: 'Delete',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Whether to display the indicator
|
|
102
|
+
* It is not shown on podcastmaker, or for unidentified users
|
|
103
|
+
*/
|
|
104
|
+
const displayIndicator = computed((): boolean => {
|
|
105
|
+
return !state.generalParameters.podcastmaker && isAuthenticated.value;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The entity on which rights are checked
|
|
110
|
+
*/
|
|
111
|
+
const entity = computed((): [EntitySegment, RightEntity]|null => {
|
|
112
|
+
let arg: RightEntity;
|
|
113
|
+
let entitySegment: EntitySegment|undefined;
|
|
114
|
+
|
|
115
|
+
if ('podcast' in props && props.podcast) {
|
|
116
|
+
entitySegment = 'Podcast';
|
|
117
|
+
arg = props.podcast;
|
|
118
|
+
} else if ('cartouchier' in props && props.cartouchier) {
|
|
119
|
+
entitySegment = 'Cartouchier';
|
|
120
|
+
arg = props.cartouchier;
|
|
121
|
+
} else if ('mix' in props && props.mix) {
|
|
122
|
+
entitySegment = 'Mix';
|
|
123
|
+
arg = props.mix;
|
|
124
|
+
} else if ('playlistMedia' in props && props.playlistMedia) {
|
|
125
|
+
entitySegment = 'PlaylistMedia';
|
|
126
|
+
arg = props.playlistMedia;
|
|
127
|
+
} else if ('media' in props && props.media) {
|
|
128
|
+
entitySegment = 'Media';
|
|
129
|
+
arg = props.media;
|
|
130
|
+
} else {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return [entitySegment, arg];
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* The current rights for the given entity
|
|
139
|
+
*/
|
|
140
|
+
const actionRight = computed((): ActionRight => {
|
|
141
|
+
const data = entity.value;
|
|
142
|
+
if (!data) {
|
|
143
|
+
return ActionRight.DeniedNoRight;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const [entitySegment, arg] = data;
|
|
147
|
+
|
|
148
|
+
if (props.action === 'any') {
|
|
149
|
+
const checks: ActionSegment[] = typeof arg === 'object' && arg !== null
|
|
150
|
+
? ['Create', 'Edit', 'Delete']
|
|
151
|
+
: ['Create'];
|
|
152
|
+
|
|
153
|
+
let best = ActionRight.DeniedNoRight;
|
|
154
|
+
for (const a of checks) {
|
|
155
|
+
const key = `get${a}${entitySegment}Right` as RightMethodKey;
|
|
156
|
+
const r = callRightMethod(key, arg);
|
|
157
|
+
if (r === ActionRight.Allowed) {
|
|
158
|
+
return ActionRight.Allowed;
|
|
159
|
+
}
|
|
160
|
+
if (r === ActionRight.DeniedNotOwner) {
|
|
161
|
+
best = ActionRight.DeniedNotOwner;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return best;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if ((props.action === 'edit' || props.action === 'delete') && typeof arg !== 'object') {
|
|
168
|
+
return ActionRight.DeniedNoRight;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const methodName = `get${actionSegmentMap[props.action]}${entitySegment}Right` as RightMethodKey;
|
|
172
|
+
return callRightMethod(methodName, arg);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
/** Helper for calling the method on useRights */
|
|
176
|
+
function callRightMethod(key: RightMethodKey, arg: unknown): ActionRight {
|
|
177
|
+
return (rights[key] as (arg?: unknown) => ActionRight)(arg);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const hasAccess = computed((): boolean => {
|
|
181
|
+
return actionRight.value === ActionRight.Allowed;
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const message = computed((): string => {
|
|
185
|
+
const entitySegment = entity.value?.[0];
|
|
186
|
+
let key: string;
|
|
187
|
+
let genericKey: string;
|
|
188
|
+
|
|
189
|
+
switch (actionRight.value) {
|
|
190
|
+
case ActionRight.Allowed:
|
|
191
|
+
return '';
|
|
192
|
+
|
|
193
|
+
case ActionRight.DeniedNotOwner:
|
|
194
|
+
key = `RightsIndicator - ${entitySegment} - Not owner`;
|
|
195
|
+
genericKey = 'Generic - Action disabled - Not owner';
|
|
196
|
+
break;
|
|
197
|
+
|
|
198
|
+
case ActionRight.DeniedNoRight:
|
|
199
|
+
return 'insufficient rights';
|
|
200
|
+
|
|
201
|
+
default:
|
|
202
|
+
return 'Cannot process rights';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (te(key)) {
|
|
206
|
+
return t(key);
|
|
207
|
+
} else {
|
|
208
|
+
return t(genericKey);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const uid = getCurrentInstance()?.uid;
|
|
213
|
+
/** ID of the icon for reference by the popover */
|
|
214
|
+
const iconId = computed((): string => 'rights-indicator-' + uid);
|
|
215
|
+
</script>
|
|
@@ -10,16 +10,21 @@
|
|
|
10
10
|
<div
|
|
11
11
|
class="p-2 pe-4 rounded d-flex alert"
|
|
12
12
|
:class="cardClass"
|
|
13
|
+
:role="type === 'error' || type === 'warning' ? 'alert' : 'status'"
|
|
14
|
+
:aria-live="type === 'error' || type === 'warning' ? 'assertive' : 'polite'"
|
|
15
|
+
aria-atomic="true"
|
|
13
16
|
>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
<span aria-hidden="true">
|
|
18
|
+
<slot name="icon">
|
|
19
|
+
<!-- The icon -->
|
|
20
|
+
<component
|
|
21
|
+
:is="iconComponent"
|
|
22
|
+
v-if="!noIcon"
|
|
23
|
+
class="icon"
|
|
24
|
+
:size="text ? 22 : 30"
|
|
25
|
+
/>
|
|
26
|
+
</slot>
|
|
27
|
+
</span>
|
|
23
28
|
|
|
24
29
|
<!-- Main content -->
|
|
25
30
|
<span class="ms-2 content">
|
|
@@ -41,7 +46,7 @@ import CheckCircle from 'vue-material-design-icons/CheckCircle.vue';
|
|
|
41
46
|
import CloseCircle from 'vue-material-design-icons/CloseCircle.vue';
|
|
42
47
|
import Information from 'vue-material-design-icons/Information.vue';
|
|
43
48
|
|
|
44
|
-
const { type, text } = defineProps<{
|
|
49
|
+
const { type, title = undefined, text } = defineProps<{
|
|
45
50
|
/** Disables the icon when true */
|
|
46
51
|
noIcon?: boolean;
|
|
47
52
|
/** An optional title for the alert */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<!--
|
|
2
2
|
Component to make a tab-based navigation
|
|
3
|
+
@deprecated Use ClassicTabs instead
|
|
3
4
|
-->
|
|
4
5
|
<template>
|
|
5
6
|
<ul class="octopus-nav" :class="light ? 'light' : ''">
|
|
@@ -41,9 +42,13 @@
|
|
|
41
42
|
|
|
42
43
|
//Props
|
|
43
44
|
defineProps({
|
|
45
|
+
/** Number of tabs to display */
|
|
44
46
|
tabNumber: { default: 0, type: Number },
|
|
47
|
+
/** Currently active tab */
|
|
45
48
|
activeTab: { default: 0, type: Number },
|
|
49
|
+
/** Alternative css */
|
|
46
50
|
transparent: { default: false, type: Boolean },
|
|
51
|
+
/** Alternative css */
|
|
47
52
|
light: { default: false, type: Boolean },
|
|
48
53
|
})
|
|
49
54
|
|
|
@@ -1,30 +1,31 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
2
|
+
<Teleport to="#app">
|
|
3
|
+
<div
|
|
4
|
+
v-show="displayPopover"
|
|
5
|
+
:id="'popover' + target"
|
|
6
|
+
ref="popover"
|
|
7
|
+
popover
|
|
8
|
+
tabindex="0"
|
|
9
|
+
class="octopus-popover border position-fixed"
|
|
10
|
+
:class="[
|
|
11
|
+
displayPopover ? 'd-block': '',
|
|
12
|
+
onlyClick ? 'octopus-dropdown' : '',
|
|
13
|
+
popoverClass]"
|
|
14
|
+
:style="positionInlineStyle"
|
|
15
|
+
@mouseenter="overPopover = true"
|
|
16
|
+
@mouseleave="
|
|
17
|
+
overPopover = false;
|
|
18
|
+
clearData();
|
|
19
|
+
"
|
|
20
|
+
>
|
|
21
|
+
<div v-if="title" class="bg-secondary-light p-2">
|
|
22
|
+
{{ title }}
|
|
23
|
+
</div>
|
|
24
|
+
<div class="p-2">
|
|
25
|
+
<slot>{{ content }}</slot>
|
|
26
|
+
</div>
|
|
26
27
|
</div>
|
|
27
|
-
</
|
|
28
|
+
</Teleport>
|
|
28
29
|
</template>
|
|
29
30
|
|
|
30
31
|
<script setup lang="ts">
|
|
@@ -40,13 +41,13 @@ const props = defineProps({
|
|
|
40
41
|
onlyClick: { type: Boolean, default: false },
|
|
41
42
|
onlyMouse: { type: Boolean, default: false },
|
|
42
43
|
isFixed: { type: Boolean, default: false },
|
|
43
|
-
/**
|
|
44
|
+
/** @deprecated No longer needed. The popover is teleported to body and always uses position:fixed. */
|
|
44
45
|
relativeClass: { type: String, default: undefined },
|
|
45
46
|
leftPos: { type: Boolean, default: false },
|
|
46
47
|
topPos: { type: Boolean, default: false },
|
|
47
48
|
popoverClass: { type: String, default: undefined },
|
|
48
49
|
isTopLayer: { type: Boolean, default: false },
|
|
49
|
-
/** If set to true, max height of popover will not overflow from parent */
|
|
50
|
+
/** @deprecated No longer needed. If set to true, max height of popover will not overflow from parent */
|
|
50
51
|
constrainHeight: { type: Boolean, default: true }
|
|
51
52
|
})
|
|
52
53
|
|
|
@@ -214,56 +215,24 @@ function setPopoverData(e: MouseEvent | PointerEvent) {
|
|
|
214
215
|
openedByHover.value = true;
|
|
215
216
|
}
|
|
216
217
|
show.value = true;
|
|
217
|
-
let parentLeft = 0;
|
|
218
|
-
let parentTop = 0;
|
|
219
|
-
let parentScrollTop = 0;
|
|
220
|
-
let parentBottom = 0;
|
|
221
|
-
let parentWidth=0;
|
|
222
218
|
const popover = popoverRef?.value as HTMLElement;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (undefined === modalBody) {
|
|
226
|
-
popover.style.display = "block";
|
|
227
|
-
posX.value = 0;
|
|
228
|
-
posY.value = 0;
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
const modalBodyRect = modalBody.getBoundingClientRect();
|
|
232
|
-
parentLeft = modalBodyRect.left;
|
|
233
|
-
parentTop = modalBodyRect.top;
|
|
234
|
-
parentScrollTop = modalBody.scrollTop;
|
|
235
|
-
parentBottom=modalBodyRect.bottom;
|
|
236
|
-
parentWidth = modalBodyRect.width;
|
|
237
|
-
}
|
|
219
|
+
// The popover is teleported to <body> and uses position:fixed, so
|
|
220
|
+
// getBoundingClientRect() coordinates map directly to viewport coordinates.
|
|
238
221
|
const rectElement = (e.target as HTMLElement).getBoundingClientRect();
|
|
239
222
|
popover.style.display = "block";
|
|
240
223
|
const sizePopover = popover.clientWidth;
|
|
241
|
-
const sizeAvailable = parentWidth || window.innerWidth;
|
|
242
224
|
if (props.leftPos) {
|
|
243
|
-
handleLeftPos(rectElement,
|
|
225
|
+
handleLeftPos(rectElement, 0, window.innerWidth, sizePopover);
|
|
244
226
|
} else {
|
|
245
|
-
handleRightPos(rectElement,
|
|
227
|
+
handleRightPos(rectElement, 0, window.innerWidth, sizePopover);
|
|
246
228
|
}
|
|
247
229
|
posX.value = Math.max(0, posX.value);
|
|
248
230
|
const yPosParent = props.topPos ? rectElement.top : rectElement.bottom;
|
|
249
231
|
const yGap = props.topPos
|
|
250
232
|
? -5 - popover.clientHeight
|
|
251
233
|
: 5;
|
|
252
|
-
|
|
253
|
-
posY.value
|
|
254
|
-
yPosParent +
|
|
255
|
-
parentScrollTop -
|
|
256
|
-
parentTop +
|
|
257
|
-
(props.isFixed ? 0 : window.scrollY) +
|
|
258
|
-
yGap;
|
|
259
|
-
if(isTopLayerPopover.value){
|
|
260
|
-
posY.value = Math.max(0, posY.value);
|
|
261
|
-
maxHeight.value = (window.innerHeight - posY.value) + "px";
|
|
262
|
-
}else if(props.relativeClass && props.constrainHeight !== false) {
|
|
263
|
-
maxHeight.value = (parentBottom- posY.value -parentTop) + "px";
|
|
264
|
-
}else{
|
|
265
|
-
maxHeight.value = '80dvh';
|
|
266
|
-
}
|
|
234
|
+
posY.value = Math.max(0, yPosParent + yGap);
|
|
235
|
+
maxHeight.value = (window.innerHeight - posY.value) + "px";
|
|
267
236
|
}
|
|
268
237
|
|
|
269
238
|
function clearDataBlur(e: FocusEvent) {
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Replacement component for ClassicNav
|
|
3
|
+
It is easier to use, and rely on user-defined ids to identify the slots,
|
|
4
|
+
instead of indices.
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<ClassicNav
|
|
8
|
+
v-model:active-tab="activeTab"
|
|
9
|
+
:tab-number="tabs.length"
|
|
10
|
+
>
|
|
11
|
+
<template v-for="(tab, index) in tabs" #[index]>
|
|
12
|
+
{{ tab.label }}
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<template v-for="(tab, index) in tabs" #[tabSlotName(index)]>
|
|
16
|
+
<slot :name="'tab-' + tab.id" />
|
|
17
|
+
</template>
|
|
18
|
+
</ClassicNav>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script setup lang="ts">
|
|
22
|
+
import { computed } from 'vue';
|
|
23
|
+
import ClassicNav from './ClassicNav.vue';
|
|
24
|
+
|
|
25
|
+
/** Type for the ID of tabs */
|
|
26
|
+
type TabId = string|number;
|
|
27
|
+
|
|
28
|
+
export interface Tab {
|
|
29
|
+
/** ID of the tab, used for slots */
|
|
30
|
+
id: TabId;
|
|
31
|
+
/** Label displayed for the tab */
|
|
32
|
+
label: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const props = defineProps<{
|
|
36
|
+
/** Tabs definition */
|
|
37
|
+
tabs: Array<Tab>;
|
|
38
|
+
/** Currently active tab */
|
|
39
|
+
activeTab: TabId;
|
|
40
|
+
}>();
|
|
41
|
+
|
|
42
|
+
const emit = defineEmits<{
|
|
43
|
+
/** Emitted when the tab changes */
|
|
44
|
+
(e: 'update:active-tab', tab: string|number): void;
|
|
45
|
+
}>();
|
|
46
|
+
|
|
47
|
+
/** Currently active tab, just a proxy */
|
|
48
|
+
const activeTab = computed({
|
|
49
|
+
get: () => props.tabs.findIndex(t => t.id === props.activeTab),
|
|
50
|
+
set: (index: number) => emit('update:active-tab', props.tabs[index].id)
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
/** Get the tab content slot name */
|
|
54
|
+
function tabSlotName(index: number): string {
|
|
55
|
+
return 'tab' + index;
|
|
56
|
+
}
|
|
57
|
+
</script>
|
package/src/locale/de.json
CHANGED
|
@@ -425,5 +425,10 @@
|
|
|
425
425
|
"Podcast - Season": "Jahreszeit",
|
|
426
426
|
"Podcast - Episode number": "Episodennummer",
|
|
427
427
|
"Player - Generating subtitles": "Generierung von Untertiteln in {language}…",
|
|
428
|
-
"Transcript - Available languages": "Verfügbare Sprachen"
|
|
428
|
+
"Transcript - Available languages": "Verfügbare Sprachen",
|
|
429
|
+
"Generic - Action disabled - Not owner": "Diese Aktion ist nicht verfügbar, da Ihre Rechte es Ihnen nicht erlauben, Elemente zu ändern oder zu löschen, deren Eigentümer Sie nicht sind",
|
|
430
|
+
"RightsIndicator - Podcast - Not owner": "Dieser Podcast wurde von einem anderen Benutzer erstellt. Sie können ihn ansehen, aber nicht bearbeiten.",
|
|
431
|
+
"RightsIndicator - Cartouchier - Not owner": "Diese Patronenhülse wurde von einem anderen Benutzer erstellt. Sie können sie anzeigen, aber nicht ändern.",
|
|
432
|
+
"RightsIndicator - PlaylistMedia - Not owner": "Dieser Behälter wurde von einem anderen Benutzer erstellt. Sie können ihn anzeigen, aber nicht ändern.",
|
|
433
|
+
"RightsIndicator - Mix - Not owner": "Dieser Mix wurde von einem anderen Benutzer erstellt. Sie können ihn anzeigen, aber nicht bearbeiten."
|
|
429
434
|
}
|
package/src/locale/en.json
CHANGED
|
@@ -425,5 +425,10 @@
|
|
|
425
425
|
"Podcast - Season": "Season",
|
|
426
426
|
"Podcast - Episode number": "Episode number",
|
|
427
427
|
"Player - Generating subtitles": "Generation of subtitles in {language}…",
|
|
428
|
-
"Transcript - Available languages": "Languages available"
|
|
428
|
+
"Transcript - Available languages": "Languages available",
|
|
429
|
+
"Generic - Action disabled - Not owner": "This action is not available because your rights do not allow you to modify or delete elements of which you are not the owner",
|
|
430
|
+
"RightsIndicator - Podcast - Not owner": "This podcast was created by another user, you can view it, but you cannot edit it.",
|
|
431
|
+
"RightsIndicator - Cartouchier - Not owner": "This cartridge case was created by another user, you can view it, but you cannot modify it.",
|
|
432
|
+
"RightsIndicator - PlaylistMedia - Not owner": "This bin was created by another user, you can view it, but you cannot modify it.",
|
|
433
|
+
"RightsIndicator - Mix - Not owner": "This mix was created by another user, you can view it, but you cannot edit it."
|
|
429
434
|
}
|
package/src/locale/es.json
CHANGED
|
@@ -425,5 +425,10 @@
|
|
|
425
425
|
"Podcast - Season": "Estación",
|
|
426
426
|
"Podcast - Episode number": "Número de episodio",
|
|
427
427
|
"Player - Generating subtitles": "Generación de subtítulos en {language}…",
|
|
428
|
-
"Transcript - Available languages": "Idiomas disponibles"
|
|
428
|
+
"Transcript - Available languages": "Idiomas disponibles",
|
|
429
|
+
"Generic - Action disabled - Not owner": "Esta acción no está disponible porque tus derechos no te permiten modificar o eliminar elementos de los que no eres propietario",
|
|
430
|
+
"RightsIndicator - Podcast - Not owner": "Este podcast fue creado por otro usuario, puedes verlo, pero no puedes editarlo.",
|
|
431
|
+
"RightsIndicator - Cartouchier - Not owner": "Esta cartuchera fue creada por otro usuario, puedes verla, pero no puedes modificarla.",
|
|
432
|
+
"RightsIndicator - PlaylistMedia - Not owner": "Este contenedor fue creado por otro usuario, puedes verlo, pero no puedes modificarlo.",
|
|
433
|
+
"RightsIndicator - Mix - Not owner": "Esta mezcla fue creada por otro usuario, puedes verla, pero no puedes editarla."
|
|
429
434
|
}
|
package/src/locale/fr.json
CHANGED
|
@@ -449,5 +449,11 @@
|
|
|
449
449
|
"Podcast - Episode number": "Numéro d'épisode",
|
|
450
450
|
"Player - Generating subtitles": "Génération des sous-titres en {language}…",
|
|
451
451
|
|
|
452
|
-
"Transcript - Available languages": "Langues disponibles"
|
|
452
|
+
"Transcript - Available languages": "Langues disponibles",
|
|
453
|
+
|
|
454
|
+
"Generic - Action disabled - Not owner": "Cette action n'est pas disponible car vos droits ne vous permettent pas de modifier ou supprimer des éléments dont vous n'êtes pas propriétaire",
|
|
455
|
+
"RightsIndicator - Podcast - Not owner": "Ce podcast a été créé par un autre utilisateur, vous pouvez le consulter, mais vous ne pouvez pas le modifier.",
|
|
456
|
+
"RightsIndicator - Cartouchier - Not owner": "Ce cartouchier a été créé par un autre utilisateur, vous pouvez le consulter, mais vous ne pouvez pas le modifier.",
|
|
457
|
+
"RightsIndicator - PlaylistMedia - Not owner": "Ce bac a été créé par un autre utilisateur, vous pouvez le consulter, mais vous ne pouvez pas le modifier.",
|
|
458
|
+
"RightsIndicator - Mix - Not owner": "Ce mix a été créé par un autre utilisateur, vous pouvez le consulter, mais vous ne pouvez pas le modifier."
|
|
453
459
|
}
|
package/src/locale/it.json
CHANGED
|
@@ -426,5 +426,10 @@
|
|
|
426
426
|
"Podcast - Season": "Stagione",
|
|
427
427
|
"Podcast - Episode number": "Numero dell'episodio",
|
|
428
428
|
"Player - Generating subtitles": "Generazione di sottotitoli in {language}…",
|
|
429
|
-
"Transcript - Available languages": "Lingue disponibili"
|
|
429
|
+
"Transcript - Available languages": "Lingue disponibili",
|
|
430
|
+
"Generic - Action disabled - Not owner": "Questa azione non è disponibile perché i tuoi diritti non ti consentono di modificare o eliminare elementi di cui non sei proprietario",
|
|
431
|
+
"RightsIndicator - Podcast - Not owner": "Questo podcast è stato creato da un altro utente, puoi visualizzarlo, ma non puoi modificarlo.",
|
|
432
|
+
"RightsIndicator - Cartouchier - Not owner": "Questo bossolo è stato creato da un altro utente, puoi visualizzarlo, ma non puoi modificarlo.",
|
|
433
|
+
"RightsIndicator - PlaylistMedia - Not owner": "Questo contenitore è stato creato da un altro utente, puoi visualizzarlo, ma non puoi modificarlo.",
|
|
434
|
+
"RightsIndicator - Mix - Not owner": "Questo mix è stato creato da un altro utente, puoi visualizzarlo, ma non puoi modificarlo."
|
|
430
435
|
}
|
package/src/locale/sl.json
CHANGED
|
@@ -424,5 +424,10 @@
|
|
|
424
424
|
"Podcast - Season": "Sezona",
|
|
425
425
|
"Podcast - Episode number": "Številka epizode",
|
|
426
426
|
"Player - Generating subtitles": "Generiranje podnapisov v {language}…",
|
|
427
|
-
"Transcript - Available languages": "Razpoložljivi jeziki"
|
|
427
|
+
"Transcript - Available languages": "Razpoložljivi jeziki",
|
|
428
|
+
"Generic - Action disabled - Not owner": "To dejanje ni na voljo, ker vam vaše pravice ne dovoljujejo spreminjanja ali brisanja elementov, katerih lastnik niste",
|
|
429
|
+
"RightsIndicator - Podcast - Not owner": "Ta podcast je ustvaril drug uporabnik, lahko si ga ogledate, ne morete pa ga urejati.",
|
|
430
|
+
"RightsIndicator - Cartouchier - Not owner": "Ta tulec je ustvaril drug uporabnik, lahko si ga ogledate, ne morete pa ga spreminjati.",
|
|
431
|
+
"RightsIndicator - PlaylistMedia - Not owner": "Ta koš je ustvaril drug uporabnik, lahko si ga ogledate, ne morete pa ga spreminjati.",
|
|
432
|
+
"RightsIndicator - Mix - Not owner": "Ta miks je ustvaril drug uporabnik, lahko si ga ogledate, ne morete pa ga urejati."
|
|
428
433
|
}
|
|
@@ -21,9 +21,6 @@ export const useFilterStore = defineStore("FilterStore", () => {
|
|
|
21
21
|
const filterRubriquage = ref<Array<Rubriquage>>([]);
|
|
22
22
|
const filterRubrique = ref<Array<RubriquageFilter>>([]);
|
|
23
23
|
const filterRubriqueDisplay = ref<Array<Rubrique>>([]);
|
|
24
|
-
const filterTypeMedia = ref<string>();
|
|
25
|
-
const filterSortOrder = ref<string>();
|
|
26
|
-
const filterSortField = ref<string>();
|
|
27
24
|
const filterLive = ref<boolean>(false);
|
|
28
25
|
const filterIab = ref<Category>();
|
|
29
26
|
|
|
@@ -101,22 +98,6 @@ export const useFilterStore = defineStore("FilterStore", () => {
|
|
|
101
98
|
filterRubriqueDisplay.value = rubriques.filter(rubrique=> rubrique);
|
|
102
99
|
}
|
|
103
100
|
|
|
104
|
-
function filterUpdateMedia(filter: {
|
|
105
|
-
type?: string;
|
|
106
|
-
order?: string;
|
|
107
|
-
field?: string;
|
|
108
|
-
}) {
|
|
109
|
-
if (filter.type) {
|
|
110
|
-
filterTypeMedia.value = filter.type;
|
|
111
|
-
}
|
|
112
|
-
if (filter.order) {
|
|
113
|
-
filterSortOrder.value = filter.order;
|
|
114
|
-
}
|
|
115
|
-
if (filter.field) {
|
|
116
|
-
filterSortField.value = filter.field;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
101
|
return {
|
|
121
102
|
filterOrgaId,
|
|
122
103
|
realOrgaId,
|
|
@@ -127,16 +108,12 @@ export const useFilterStore = defineStore("FilterStore", () => {
|
|
|
127
108
|
filterUpdateIab,
|
|
128
109
|
filterUpdateRubrique,
|
|
129
110
|
filterUpdateRubriqueDisplay,
|
|
130
|
-
filterUpdateMedia,
|
|
131
111
|
|
|
132
112
|
filterImgUrl,
|
|
133
113
|
filterName,
|
|
134
114
|
filterRubriquage,
|
|
135
115
|
filterRubrique,
|
|
136
116
|
filterRubriqueDisplay,
|
|
137
|
-
filterTypeMedia,
|
|
138
|
-
filterSortOrder,
|
|
139
|
-
filterSortField,
|
|
140
117
|
filterLive,
|
|
141
118
|
filterIab
|
|
142
119
|
};
|