@saooti/octopus-sdk 41.0.21 → 41.1.1
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 +2 -2
- package/package.json +4 -2
- package/src/components/composable/useOrganisationFilter.ts +7 -4
- package/src/components/display/emission/EmissionInlineList.vue +40 -11
- package/src/components/display/emission/EmissionItem.vue +8 -2
- package/src/components/display/emission/EmissionPlayerItem.vue +0 -2
- package/src/components/display/emission/EmissionPresentationItem.vue +24 -119
- package/src/components/display/emission/EmissionPresentationList.vue +22 -101
- package/src/components/display/list/SwiperList.vue +1 -0
- package/src/components/display/playlist/PodcastPlaylistInlineList.vue +8 -5
- package/src/components/display/podcasts/PodcastPlayButton.vue +2 -2
- package/src/components/display/podcasts/PodcastPresentationList.vue +127 -0
- package/src/components/layout/PresentationItem.vue +148 -0
- package/src/components/layout/PresentationLayout.vue +132 -0
- package/src/components/pages/HomePage.vue +1 -0
- package/src/stores/class/general/podcast.ts +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
|
+
## 41.1.1 (01/12/2025)
|
|
4
|
+
|
|
5
|
+
**Fixes**
|
|
6
|
+
|
|
7
|
+
- Passage de `pinia` en *peerDependency*
|
|
8
|
+
|
|
9
|
+
## 41.1.0 (01/12/2025)
|
|
10
|
+
|
|
11
|
+
**Features**
|
|
12
|
+
|
|
13
|
+
- Accélération `selectOrganisation`
|
|
14
|
+
- Ajout d'options de personnalisation à `EmissionInlineList`
|
|
15
|
+
- Ajout d'options de personnalisation à `EmissionPresentationItem`
|
|
16
|
+
- Ajout des composants `PresentationLayout` & `PresentationItem`
|
|
17
|
+
- `PresentationLayout` permet de simplement disposer quelques éléments
|
|
18
|
+
- `PresentationItem` permet de simplement afficher une image avec description
|
|
19
|
+
- Ajout de `PodcastPresentationList`, pendant podcast de `EmissionPresentationList`
|
|
20
|
+
|
|
21
|
+
**Fixes**
|
|
22
|
+
|
|
23
|
+
- Correction taille variable de `EmissionItem`
|
|
24
|
+
|
|
25
|
+
**Misc**
|
|
26
|
+
|
|
27
|
+
- Réécriture de `EmissionPresentationItem` & `EmissionPresentationList` pour
|
|
28
|
+
utiliser `PresentationItem` & `PresentationLayout`
|
|
29
|
+
|
|
3
30
|
## 41.0.21 (25/11/2025)
|
|
4
31
|
|
|
5
32
|
**Fixes**
|
package/index.ts
CHANGED
|
@@ -68,7 +68,7 @@ export const getLiveList = () => import("./src/components/display/live/LiveList.
|
|
|
68
68
|
export const getEmissionPresentationList = () => import("./src/components/display/emission/EmissionPresentationList.vue");
|
|
69
69
|
export const getPodcastPlayButton = () => import("./src/components/display/podcasts/PodcastPlayButton.vue");
|
|
70
70
|
export const getParticipantInlineList = () => import("./src/components/display/participant/ParticipantInlineList.vue");
|
|
71
|
-
|
|
71
|
+
export const getPodcastPresentationList = () => import("./src/components/display/podcasts/PodcastPresentationList.vue");
|
|
72
72
|
|
|
73
73
|
//Radio
|
|
74
74
|
export const getRadioCurrently = () => import("./src/components/display/live/RadioCurrently.vue");
|
|
@@ -193,4 +193,4 @@ export {
|
|
|
193
193
|
setupRouter,
|
|
194
194
|
SelectOption,
|
|
195
195
|
ROUTE_PARAMS
|
|
196
|
-
};
|
|
196
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saooti/octopus-sdk",
|
|
3
|
-
"version": "41.
|
|
3
|
+
"version": "41.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Javascript SDK for using octopus",
|
|
6
6
|
"author": "Saooti",
|
|
@@ -36,7 +36,6 @@
|
|
|
36
36
|
"globals": "^16.5.0",
|
|
37
37
|
"hls.js": "^1.6.14",
|
|
38
38
|
"humanize-duration": "^3.33.1",
|
|
39
|
-
"pinia": "^3.0.4",
|
|
40
39
|
"postcss-html": "^1.8.0",
|
|
41
40
|
"qrcode.vue": "^3.6.0",
|
|
42
41
|
"sass": "^1.94.1",
|
|
@@ -88,5 +87,8 @@
|
|
|
88
87
|
"repository": {
|
|
89
88
|
"type": "git",
|
|
90
89
|
"url": "git+https://github.com/saooti/octopus-sdk.git"
|
|
90
|
+
},
|
|
91
|
+
"peerDependencies": {
|
|
92
|
+
"pinia": "^2.3.0"
|
|
91
93
|
}
|
|
92
94
|
}
|
|
@@ -18,8 +18,8 @@ export const useOrganisationFilter= ()=>{
|
|
|
18
18
|
|
|
19
19
|
async function selectOrganisation(organisationId: string): Promise<void> {
|
|
20
20
|
try {
|
|
21
|
-
const
|
|
22
|
-
const
|
|
21
|
+
const getOrgaData = saveFetchStore.getOrgaData(organisationId);
|
|
22
|
+
const fetchData = classicApi.fetchData<Array<Rubriquage>>({
|
|
23
23
|
api: 0,
|
|
24
24
|
path:"rubriquage/find/" + organisationId,
|
|
25
25
|
parameters:{
|
|
@@ -28,7 +28,10 @@ export const useOrganisationFilter= ()=>{
|
|
|
28
28
|
},
|
|
29
29
|
specialTreatement:true
|
|
30
30
|
});
|
|
31
|
-
const
|
|
31
|
+
const getOrgaLive = saveFetchStore.getOrgaLiveEnabled(organisationId);
|
|
32
|
+
|
|
33
|
+
const [response, data, isLive] = await Promise.all([getOrgaData, fetchData, getOrgaLive]);
|
|
34
|
+
|
|
32
35
|
filterStore.filterUpdateOrga({
|
|
33
36
|
orgaId: organisationId,
|
|
34
37
|
imgUrl: response.imageUrl,
|
|
@@ -62,4 +65,4 @@ export const useOrganisationFilter= ()=>{
|
|
|
62
65
|
selectOrganisation,
|
|
63
66
|
removeSelectedOrga
|
|
64
67
|
}
|
|
65
|
-
}
|
|
68
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="d-flex flex-column
|
|
2
|
+
<div class="d-flex flex-column list-episode">
|
|
3
3
|
<ClassicLoading
|
|
4
4
|
:loading-text="loading ? t('Loading emissions ...') : undefined"
|
|
5
5
|
/>
|
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
:list-object="allEmissions"
|
|
10
10
|
>
|
|
11
11
|
<template #octopusSlide="{ option }">
|
|
12
|
+
<EmissionPresentationItem
|
|
13
|
+
v-if="emissionDisplay === 'simple'"
|
|
14
|
+
:emission="option"
|
|
15
|
+
class="mx-2"
|
|
16
|
+
is-description
|
|
17
|
+
:is-vertical="emissionVertical"
|
|
18
|
+
/>
|
|
19
|
+
|
|
12
20
|
<EmissionPlayerItem
|
|
21
|
+
v-else
|
|
13
22
|
class="flex-shrink-0 item-phone-margin"
|
|
14
23
|
:emission="option"
|
|
15
24
|
:class="[mainRubriquage(option)]"
|
|
@@ -19,6 +28,7 @@
|
|
|
19
28
|
</template>
|
|
20
29
|
</SwiperList>
|
|
21
30
|
<router-link
|
|
31
|
+
v-if="href"
|
|
22
32
|
:to="href"
|
|
23
33
|
class="btn btn-primary align-self-center w-fit-content m-4"
|
|
24
34
|
>
|
|
@@ -30,6 +40,7 @@
|
|
|
30
40
|
<script setup lang="ts">
|
|
31
41
|
import SwiperList from "../list/SwiperList.vue";
|
|
32
42
|
import classicApi from "../../../api/classicApi";
|
|
43
|
+
import EmissionPresentationItem from "./EmissionPresentationItem.vue";
|
|
33
44
|
import EmissionPlayerItem from "./EmissionPlayerItem.vue";
|
|
34
45
|
import { state } from "../../../stores/ParamSdkStore";
|
|
35
46
|
import {useErrorHandler} from "../../composable/useErrorHandler";
|
|
@@ -41,17 +52,31 @@ import { AxiosError } from "axios";
|
|
|
41
52
|
import { Rubriquage } from "@/stores/class/rubrique/rubriquage";
|
|
42
53
|
import { ListClassicReturn } from "@/stores/class/general/listReturn";
|
|
43
54
|
import { useI18n } from "vue-i18n";
|
|
55
|
+
import { RouteParams } from "vue-router";
|
|
44
56
|
|
|
45
57
|
//Props
|
|
46
|
-
const props = defineProps
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
const props = defineProps<{
|
|
59
|
+
/**
|
|
60
|
+
* Change style of emission item.
|
|
61
|
+
* Simple is what is seen everywhere, player displays a few podcasts that can
|
|
62
|
+
* be played directly
|
|
63
|
+
*/
|
|
64
|
+
emissionDisplay?: 'player'|'simple';
|
|
65
|
+
/** When set to true with the 'simple' emissionDisplay, display emissions vertically */
|
|
66
|
+
emissionVertical?: boolean;
|
|
67
|
+
href?: string|RouteParams;
|
|
68
|
+
buttonText?: string;
|
|
69
|
+
/** Number of podcasts shown when using player display */
|
|
70
|
+
nbPodcasts?: number;
|
|
71
|
+
/** Size, in **rem**, of the emission items */
|
|
72
|
+
itemSize?: number;
|
|
73
|
+
/** Filter on organization */
|
|
74
|
+
organisationId?: string;
|
|
75
|
+
/** Filter on rubrique */
|
|
76
|
+
rubriqueId?: number;
|
|
77
|
+
/** Filter on rubriquage */
|
|
78
|
+
rubriquageId?: number;
|
|
79
|
+
}>();
|
|
55
80
|
|
|
56
81
|
|
|
57
82
|
//Data
|
|
@@ -106,6 +131,7 @@ async function fetchRubriques(): Promise<void> {
|
|
|
106
131
|
});
|
|
107
132
|
rubriques.value = data.rubriques;
|
|
108
133
|
}
|
|
134
|
+
|
|
109
135
|
function rubriquesId(emission: Emission): string | undefined {
|
|
110
136
|
if (
|
|
111
137
|
!displayRubriquage.value ||
|
|
@@ -113,8 +139,10 @@ function rubriquesId(emission: Emission): string | undefined {
|
|
|
113
139
|
0 === emission.rubriqueIds.length ||
|
|
114
140
|
!rubriques.value ||
|
|
115
141
|
!rubriques.value.length
|
|
116
|
-
)
|
|
142
|
+
) {
|
|
117
143
|
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
118
146
|
const rubrique = rubriques.value.find(
|
|
119
147
|
(element: Rubrique) =>
|
|
120
148
|
element.rubriqueId &&
|
|
@@ -125,6 +153,7 @@ function rubriquesId(emission: Emission): string | undefined {
|
|
|
125
153
|
return rubrique.name;
|
|
126
154
|
}
|
|
127
155
|
}
|
|
156
|
+
|
|
128
157
|
function mainRubriquage(emission: Emission): string {
|
|
129
158
|
return state.emissionsPage.mainRubrique &&
|
|
130
159
|
emission.rubriqueIds?.includes(state.emissionsPage.mainRubrique)
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
</ClassicImageBanner>
|
|
23
23
|
|
|
24
24
|
<div class="classic-element-text">
|
|
25
|
-
<div class="d-flex align-items-center element-name
|
|
25
|
+
<div class="d-flex align-items-center element-name">
|
|
26
26
|
<AlertIcon
|
|
27
27
|
v-if="!activeEmission && !isPodcastmaker && editRight"
|
|
28
28
|
:size="16"
|
|
@@ -130,4 +130,10 @@ async function hasPodcast(): Promise<void> {
|
|
|
130
130
|
activeEmission.value = false;
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
</script>
|
|
133
|
+
</script>
|
|
134
|
+
|
|
135
|
+
<style scoped lang="scss">
|
|
136
|
+
article {
|
|
137
|
+
max-height: 254px; // Image size + a few pixels for border
|
|
138
|
+
}
|
|
139
|
+
</style>
|
|
@@ -1,129 +1,34 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Component to display an emission with its description
|
|
3
|
+
-->
|
|
1
4
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
params: { emissionId: emission.emissionId },
|
|
10
|
-
}"
|
|
11
|
-
:title="t('Series name page', { name: emission.name })"
|
|
12
|
-
class="d-flex-column flex-grow-1 text-dark"
|
|
13
|
-
:class="isVertical ? 'flex-column' : ''"
|
|
14
|
-
>
|
|
15
|
-
<img
|
|
16
|
-
v-lazy="useProxyImageUrl(emission.imageUrl, tailleImage)"
|
|
17
|
-
:width="tailleImage"
|
|
18
|
-
:height="tailleImage"
|
|
19
|
-
:class="isVertical ? 'img-box-bigger' : ''"
|
|
20
|
-
class="img-box"
|
|
21
|
-
aria-hidden="true"
|
|
22
|
-
alt=""
|
|
23
|
-
|
|
24
|
-
:title="t('Emission name image', { name: emission.name })"
|
|
25
|
-
/>
|
|
26
|
-
<div class="classic-element-text">
|
|
27
|
-
<div class="element-name mb-2 basic-line-clamp">
|
|
28
|
-
{{ emission.name }}
|
|
29
|
-
</div>
|
|
30
|
-
<div
|
|
31
|
-
v-if="!isPhone && isDescription"
|
|
32
|
-
ref="descriptionEmissionContainer"
|
|
33
|
-
class="element-description htms-wysiwyg-content"
|
|
34
|
-
>
|
|
35
|
-
<!-- eslint-disable vue/no-v-html -->
|
|
36
|
-
<div
|
|
37
|
-
ref="descriptionEmission"
|
|
38
|
-
v-html="urlify(emission.description || '')"
|
|
39
|
-
/>
|
|
40
|
-
<!-- eslint-enable -->
|
|
41
|
-
</div>
|
|
42
|
-
</div>
|
|
43
|
-
</router-link>
|
|
44
|
-
</article>
|
|
5
|
+
<PresentationItem
|
|
6
|
+
:name="emission.name"
|
|
7
|
+
:route="route"
|
|
8
|
+
:image-url="emission.imageUrl"
|
|
9
|
+
:description="isDescription ? emission.description : undefined"
|
|
10
|
+
:vertical="isVertical"
|
|
11
|
+
/>
|
|
45
12
|
</template>
|
|
46
13
|
|
|
47
14
|
<script setup lang="ts">
|
|
48
|
-
import {useResizePhone} from "../../composable/useResizePhone";
|
|
49
15
|
import { Emission } from "@/stores/class/general/emission";
|
|
50
|
-
import
|
|
51
|
-
import
|
|
52
|
-
import {
|
|
53
|
-
import { useI18n } from "vue-i18n";
|
|
16
|
+
import PresentationItem from "../../layout/PresentationItem.vue";
|
|
17
|
+
import { computed } from "vue";
|
|
18
|
+
import { RouteLocationRaw } from "vue-router";
|
|
54
19
|
|
|
55
20
|
//Props
|
|
56
|
-
const props = defineProps
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
//Composables
|
|
68
|
-
const { t } = useI18n();
|
|
69
|
-
const { isPhone } = useResizePhone();
|
|
70
|
-
const { useProxyImageUrl } = useImageProxy();
|
|
71
|
-
|
|
72
|
-
// Computed
|
|
73
|
-
// Calcul de la taille de l'image
|
|
74
|
-
const tailleImage = computed(() => {
|
|
75
|
-
// L'élément fait 400 de large à la verticale, mais on prend en compte les bordures
|
|
76
|
-
return props.isVertical ? '396' : '250';
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
/** The emission to display */
|
|
23
|
+
emission: Emission;
|
|
24
|
+
/** When true display the card vertically */
|
|
25
|
+
isVertical?: boolean;
|
|
26
|
+
/** When true also display the description */
|
|
27
|
+
isDescription?: boolean;
|
|
28
|
+
}>();
|
|
29
|
+
|
|
30
|
+
const route = computed((): RouteLocationRaw => {
|
|
31
|
+
return 'emissions';//{ name: 'emissions'/*, params: { emissionId: props.emission.emissionId }*/ };
|
|
77
32
|
});
|
|
78
33
|
|
|
79
|
-
//Watch
|
|
80
|
-
watch(isPhone, async () => {
|
|
81
|
-
nextTick(() => {
|
|
82
|
-
if (!props.isDescription || isPhone.value) {
|
|
83
|
-
return;
|
|
84
|
-
}
|
|
85
|
-
const emissionDesc = descriptionEmissionRef?.value as HTMLElement;
|
|
86
|
-
const emissionDescContainer = descriptionEmissionContainerRef?.value as HTMLElement;
|
|
87
|
-
if (
|
|
88
|
-
emissionDesc &&
|
|
89
|
-
emissionDescContainer &&
|
|
90
|
-
emissionDesc.clientHeight > emissionDescContainer.clientHeight
|
|
91
|
-
) {
|
|
92
|
-
emissionDescContainer.classList.add("after-element-description");
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
}, {immediate: true});
|
|
96
|
-
|
|
97
|
-
//Methods
|
|
98
|
-
function urlify(text:string|undefined){
|
|
99
|
-
return displayHelper.urlify(text);
|
|
100
|
-
}
|
|
101
34
|
</script>
|
|
102
|
-
<style lang="scss">
|
|
103
|
-
.octopus-app {
|
|
104
|
-
.emission-presentation-container {
|
|
105
|
-
@media (width <= 960px) {
|
|
106
|
-
width: 250px !important;
|
|
107
|
-
margin-right: 0.5rem;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
.element-description {
|
|
111
|
-
height: 0;
|
|
112
|
-
flex-grow: 1;
|
|
113
|
-
max-height: unset;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
.classic-element-container.emission-vertical-item {
|
|
118
|
-
flex-grow: 0;
|
|
119
|
-
width: 400px;
|
|
120
|
-
flex-shrink: 0;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.img-box-bigger {
|
|
124
|
-
// L'élément fait 400 de large à la verticale, mais on prend en compte les bordures
|
|
125
|
-
width: 396px;
|
|
126
|
-
height: 396px;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
</style>
|
|
@@ -1,60 +1,24 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:class="allEmissions.length <= 3 ? 'flex-grow-1' : ''"
|
|
21
|
-
>
|
|
22
|
-
<EmissionItemPresentation
|
|
23
|
-
v-if="allEmissions[1]"
|
|
24
|
-
:emission="allEmissions[1]"
|
|
25
|
-
:is-description="isDescription"
|
|
26
|
-
/>
|
|
27
|
-
<EmissionItemPresentation
|
|
28
|
-
v-if="allEmissions[2]"
|
|
29
|
-
:emission="allEmissions[2]"
|
|
30
|
-
:is-description="isDescription"
|
|
31
|
-
/>
|
|
32
|
-
</div>
|
|
33
|
-
<div
|
|
34
|
-
v-if="allEmissions.length > 3"
|
|
35
|
-
class="emission-column d-flex-row flex-nowrap show-emission-column"
|
|
36
|
-
>
|
|
37
|
-
<EmissionItemPresentation
|
|
38
|
-
v-if="allEmissions[3]"
|
|
39
|
-
:emission="allEmissions[3]"
|
|
40
|
-
:is-description="isDescription"
|
|
41
|
-
/>
|
|
42
|
-
<EmissionItemPresentation
|
|
43
|
-
v-if="allEmissions[4]"
|
|
44
|
-
:emission="allEmissions[4]"
|
|
45
|
-
:is-description="isDescription"
|
|
46
|
-
/>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
49
|
-
<router-link
|
|
50
|
-
v-if="buttonText && href"
|
|
51
|
-
:to="href"
|
|
52
|
-
class="btn btn-primary align-self-center w-fit-content m-4"
|
|
53
|
-
>
|
|
54
|
-
{{ buttonText }}
|
|
55
|
-
</router-link>
|
|
2
|
+
<ClassicLoading
|
|
3
|
+
:loading-text="loading ? $t('Loading emissions ...') : undefined"
|
|
4
|
+
:error-text="error ? $t(`Error`) : undefined"
|
|
5
|
+
/>
|
|
6
|
+
<PresentationLayout
|
|
7
|
+
v-if="!loading && !error"
|
|
8
|
+
:title="title"
|
|
9
|
+
:items="allEmissions"
|
|
10
|
+
:route="href"
|
|
11
|
+
:button-text="buttonText"
|
|
12
|
+
>
|
|
13
|
+
<template #item="{ item, first }">
|
|
14
|
+
<EmissionItemPresentation
|
|
15
|
+
:class="!isPhone && first ? 'me-3' : ''"
|
|
16
|
+
:emission="item"
|
|
17
|
+
:is-vertical="!isPhone && first"
|
|
18
|
+
:is-description="isDescription"
|
|
19
|
+
/>
|
|
56
20
|
</template>
|
|
57
|
-
</
|
|
21
|
+
</PresentationLayout>
|
|
58
22
|
</template>
|
|
59
23
|
|
|
60
24
|
<script setup lang="ts">
|
|
@@ -66,7 +30,9 @@ import { defineAsyncComponent, onMounted, Ref, ref } from "vue";
|
|
|
66
30
|
import { AxiosError } from "axios";
|
|
67
31
|
import {useResizePhone} from "../../composable/useResizePhone";
|
|
68
32
|
import { ListClassicReturn } from "@/stores/class/general/listReturn";
|
|
69
|
-
|
|
33
|
+
|
|
34
|
+
import PresentationLayout from "../../layout/PresentationLayout.vue";
|
|
35
|
+
|
|
70
36
|
const EmissionItemPresentation = defineAsyncComponent(
|
|
71
37
|
() => import("./EmissionPresentationItem.vue"),
|
|
72
38
|
);
|
|
@@ -87,7 +53,6 @@ const error = ref(false);
|
|
|
87
53
|
const allEmissions: Ref<Array<Emission>> = ref([]);
|
|
88
54
|
|
|
89
55
|
//Composables
|
|
90
|
-
const { t } = useI18n();
|
|
91
56
|
const { isPhone } = useResizePhone();
|
|
92
57
|
const {handle403} = useErrorHandler();
|
|
93
58
|
|
|
@@ -121,47 +86,3 @@ async function fetchNext(): Promise<void> {
|
|
|
121
86
|
loading.value = false;
|
|
122
87
|
}
|
|
123
88
|
</script>
|
|
124
|
-
<style lang="scss">
|
|
125
|
-
.octopus-app {
|
|
126
|
-
.overflow-phone-auto {
|
|
127
|
-
@media (width <= 960px) {
|
|
128
|
-
overflow-y: auto;
|
|
129
|
-
scroll-snap-type: x mandatory;
|
|
130
|
-
|
|
131
|
-
.classic-element-container{
|
|
132
|
-
scroll-snap-align: center;
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
.emission-column {
|
|
138
|
-
flex-shrink: 0;
|
|
139
|
-
width: calc((100% - 420px) / 2);
|
|
140
|
-
|
|
141
|
-
@media (width <= 1550px) {
|
|
142
|
-
width: calc((100% - 420px));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
@media (width <= 960px) {
|
|
146
|
-
width: auto;
|
|
147
|
-
flex-direction: row !important;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
.emission-column-margin {
|
|
152
|
-
margin-right: 1rem;
|
|
153
|
-
|
|
154
|
-
@media (width <= 960px) {
|
|
155
|
-
margin-right: 0;
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
.show-emission-column {
|
|
160
|
-
display: flex;
|
|
161
|
-
|
|
162
|
-
@media (width <= 1550px) and (width > 960px) {
|
|
163
|
-
display: none;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
</style>
|
|
@@ -41,6 +41,7 @@ import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from "vue";
|
|
|
41
41
|
//Props
|
|
42
42
|
const props = defineProps({
|
|
43
43
|
listObject: { default: () => [], type: Array as () => Array<unknown> },
|
|
44
|
+
/** Size, in **rem**, of the emission items */
|
|
44
45
|
sizeItemOverload: { default: undefined, type: Number },
|
|
45
46
|
})
|
|
46
47
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<PodcastInlineListTemplate
|
|
3
3
|
v-if="loading || (!loading && 0 !== allPodcasts.length)"
|
|
4
4
|
:display-arrow="false"
|
|
5
|
-
:button-text="t('See more')"
|
|
5
|
+
:button-text="noMoreButton ? undefined : t('See more')"
|
|
6
6
|
:button-plus="true"
|
|
7
7
|
:title="playlist?.title ?? ''"
|
|
8
8
|
:href="'/main/pub/playlist/' + playlistId"
|
|
@@ -42,10 +42,13 @@ import { onMounted, Ref, ref, watch } from "vue";
|
|
|
42
42
|
import { useI18n } from "vue-i18n";
|
|
43
43
|
|
|
44
44
|
//Props
|
|
45
|
-
const props = defineProps
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const props = defineProps<{
|
|
46
|
+
/** ID of the playlist to display */
|
|
47
|
+
playlistId: number;
|
|
48
|
+
sizeItemOverload?: number;
|
|
49
|
+
/** When set to true, disable display of "see more" button */
|
|
50
|
+
noMoreButton?: boolean;
|
|
51
|
+
}>();
|
|
49
52
|
|
|
50
53
|
//Data
|
|
51
54
|
const loading = ref(true);
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
:title="playingPodcast? t('Pause') : t('Play')"
|
|
25
25
|
@mouseenter="hoverType = 'audio'"
|
|
26
26
|
@mouseleave="hoverType = ''"
|
|
27
|
-
@click="play(false)"
|
|
27
|
+
@click.prevent="play(false)"
|
|
28
28
|
>
|
|
29
29
|
<PlayIcon
|
|
30
30
|
v-if="!playingPodcast || (playingPodcast && playerStore.playerVideo)"
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
v-if="isVideoPodcast"
|
|
44
44
|
:title="t('Video')"
|
|
45
45
|
:disabled="playerStore.playerVideo"
|
|
46
|
-
@click="play(true)"
|
|
46
|
+
@click.prevent="play(true)"
|
|
47
47
|
@mouseenter="hoverType = 'video'"
|
|
48
48
|
@mouseleave="hoverType = ''"
|
|
49
49
|
>
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Simple component to display a few podcasts
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<PresentationLayout
|
|
6
|
+
v-if="!loading && !error"
|
|
7
|
+
:title="title"
|
|
8
|
+
:items="podcasts"
|
|
9
|
+
:route="href"
|
|
10
|
+
:button-text="buttonText"
|
|
11
|
+
>
|
|
12
|
+
<template #item="{ item, first }">
|
|
13
|
+
<PresentationItem
|
|
14
|
+
:class="!isPhone && first ? 'me-3' : ''"
|
|
15
|
+
:name="item.title"
|
|
16
|
+
:route="route(item)"
|
|
17
|
+
:image-url="item.imageUrl"
|
|
18
|
+
:description="item.description"
|
|
19
|
+
:vertical="!isPhone && first"
|
|
20
|
+
>
|
|
21
|
+
<template #after-image>
|
|
22
|
+
<PodcastPlayButton
|
|
23
|
+
:podcast="item"
|
|
24
|
+
:hide-play="false"
|
|
25
|
+
:show-processing="false"
|
|
26
|
+
/>
|
|
27
|
+
</template>
|
|
28
|
+
</PresentationItem>
|
|
29
|
+
</template>
|
|
30
|
+
</PresentationLayout>
|
|
31
|
+
<ClassicLoading
|
|
32
|
+
v-else
|
|
33
|
+
:loading-text="loading ? $t('Loading emissions ...') : undefined"
|
|
34
|
+
:error-text="error ? $t(`Error`) : undefined"
|
|
35
|
+
/>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script setup lang="ts">
|
|
39
|
+
import classicApi from "../../../api/classicApi";
|
|
40
|
+
import {useErrorHandler} from "../../composable/useErrorHandler";
|
|
41
|
+
import ClassicLoading from "../../form/ClassicLoading.vue";
|
|
42
|
+
import { Emission } from "@/stores/class/general/emission";
|
|
43
|
+
import { onMounted, Ref, ref } from "vue";
|
|
44
|
+
import { AxiosError } from "axios";
|
|
45
|
+
import {useResizePhone} from "../../composable/useResizePhone";
|
|
46
|
+
import { ListClassicReturn } from "@/stores/class/general/listReturn";
|
|
47
|
+
|
|
48
|
+
import PresentationLayout from "../../layout/PresentationLayout.vue";
|
|
49
|
+
import { Podcast } from "@/stores/class/general/podcast";
|
|
50
|
+
import { ModuleApi } from "../../../api/apiConnection";
|
|
51
|
+
|
|
52
|
+
import PresentationItem from "../../layout/PresentationItem.vue";
|
|
53
|
+
import PodcastPlayButton from "./PodcastPlayButton.vue";
|
|
54
|
+
import { RouteLocationRaw } from "vue-router";
|
|
55
|
+
|
|
56
|
+
//Props
|
|
57
|
+
const props = defineProps({
|
|
58
|
+
organisationId: { default: undefined, type: String },
|
|
59
|
+
title: { default: "", type: String },
|
|
60
|
+
href: { default: undefined, type: String },
|
|
61
|
+
buttonText: { default: undefined, type: String },
|
|
62
|
+
isDescription: { default: false, type: Boolean },
|
|
63
|
+
rubriquesId: { default: [], type: Array<number> },
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
//Data
|
|
67
|
+
const loading = ref(true);
|
|
68
|
+
const error = ref(false);
|
|
69
|
+
const podcasts: Ref<Array<Podcast>> = ref([]);
|
|
70
|
+
|
|
71
|
+
//Composables
|
|
72
|
+
const { isPhone } = useResizePhone();
|
|
73
|
+
const {handle403} = useErrorHandler();
|
|
74
|
+
|
|
75
|
+
onMounted(()=>fetchNext())
|
|
76
|
+
|
|
77
|
+
//Methods
|
|
78
|
+
async function fetchNext(): Promise<void> {
|
|
79
|
+
loading.value = true;
|
|
80
|
+
try {
|
|
81
|
+
// Retrieve latest emissions
|
|
82
|
+
const emissions = await classicApi.fetchData<ListClassicReturn<Emission>>({
|
|
83
|
+
api: 0,
|
|
84
|
+
path: "emission/search",
|
|
85
|
+
parameters: {
|
|
86
|
+
first: 0,
|
|
87
|
+
size: 5,
|
|
88
|
+
organisationId: props.organisationId,
|
|
89
|
+
sort: "LAST_PODCAST_DESC",
|
|
90
|
+
rubriqueId: props.rubriquesId
|
|
91
|
+
},
|
|
92
|
+
specialTreatement: true,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Retrieve the podcasts for these emissions
|
|
96
|
+
const data = await classicApi.fetchData<ListClassicReturn<Podcast>>({
|
|
97
|
+
api: ModuleApi.DEFAULT,
|
|
98
|
+
path: "podcast/search",
|
|
99
|
+
parameters: {
|
|
100
|
+
first: 0,
|
|
101
|
+
size: 5,
|
|
102
|
+
organisationId: props.organisationId,
|
|
103
|
+
emissionId: emissions.result.map(e => e.emissionId),
|
|
104
|
+
sort: "DATE",
|
|
105
|
+
rubriqueId: props.rubriquesId
|
|
106
|
+
},
|
|
107
|
+
specialTreatement: true
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
podcasts.value = podcasts.value.concat(
|
|
111
|
+
data.result.filter((em: Podcast | null) => null !== em),
|
|
112
|
+
);
|
|
113
|
+
loading.value = false;
|
|
114
|
+
} catch (errorWs) {
|
|
115
|
+
handle403(errorWs as AxiosError);
|
|
116
|
+
error.value = true;
|
|
117
|
+
}
|
|
118
|
+
loading.value = false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function route(podcast: Podcast): RouteLocationRaw {
|
|
122
|
+
return {
|
|
123
|
+
name: 'podcast',
|
|
124
|
+
params: { podcastId: podcast.podcastId }
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
</script>
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Component to display an element with its description
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<article
|
|
6
|
+
class="classic-element-container item-presentation-container mt-3"
|
|
7
|
+
:class="vertical ? 'vertical-item' : ''"
|
|
8
|
+
>
|
|
9
|
+
<router-link
|
|
10
|
+
:to="route"
|
|
11
|
+
class="d-flex-column flex-grow-1 text-dark"
|
|
12
|
+
:class="vertical ? 'flex-column' : ''"
|
|
13
|
+
>
|
|
14
|
+
<div
|
|
15
|
+
class="img-box"
|
|
16
|
+
:class="vertical ? 'img-box-bigger' : ''"
|
|
17
|
+
>
|
|
18
|
+
<img
|
|
19
|
+
v-lazy="useProxyImageUrl(imageUrl, tailleImage)"
|
|
20
|
+
:width="tailleImage"
|
|
21
|
+
:height="tailleImage"
|
|
22
|
+
aria-hidden="true"
|
|
23
|
+
:alt="name"
|
|
24
|
+
:title="name"
|
|
25
|
+
>
|
|
26
|
+
|
|
27
|
+
<slot name="after-image" />
|
|
28
|
+
</div>
|
|
29
|
+
|
|
30
|
+
<div class="classic-element-text">
|
|
31
|
+
<div class="element-name mb-2 basic-line-clamp">
|
|
32
|
+
{{ name }}
|
|
33
|
+
</div>
|
|
34
|
+
<div
|
|
35
|
+
v-if="!isPhone && description"
|
|
36
|
+
ref="descriptionItemContainer"
|
|
37
|
+
class="element-description htms-wysiwyg-content"
|
|
38
|
+
>
|
|
39
|
+
<!-- eslint-disable vue/no-v-html -->
|
|
40
|
+
<div
|
|
41
|
+
ref="descriptionItem"
|
|
42
|
+
v-html="urlify(description || '')"
|
|
43
|
+
/>
|
|
44
|
+
<!-- eslint-enable -->
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</router-link>
|
|
48
|
+
</article>
|
|
49
|
+
</template>
|
|
50
|
+
|
|
51
|
+
<script setup lang="ts">
|
|
52
|
+
import {useResizePhone} from "../composable/useResizePhone";
|
|
53
|
+
import {useImageProxy} from "../composable/useImageProxy";
|
|
54
|
+
import displayHelper from "../../helper/displayHelper";
|
|
55
|
+
import { nextTick, useTemplateRef, watch, computed } from "vue";
|
|
56
|
+
import { RouteLocationRaw } from "vue-router";
|
|
57
|
+
|
|
58
|
+
//Props
|
|
59
|
+
const props = defineProps<{
|
|
60
|
+
/** The route to which to redirect when clicking on element */
|
|
61
|
+
route: RouteLocationRaw|string;
|
|
62
|
+
/** The URL to the image */
|
|
63
|
+
imageUrl: string;
|
|
64
|
+
/** The name of the element */
|
|
65
|
+
name: string;
|
|
66
|
+
/** The description of the element */
|
|
67
|
+
description?: string;
|
|
68
|
+
/** When true display the card vertically */
|
|
69
|
+
vertical?: boolean;
|
|
70
|
+
}>();
|
|
71
|
+
|
|
72
|
+
//Data
|
|
73
|
+
const descriptionItemRef = useTemplateRef('descriptionItem');
|
|
74
|
+
const descriptionItemContainerRef = useTemplateRef('descriptionItemContainer');
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
//Composables
|
|
78
|
+
const { isPhone } = useResizePhone();
|
|
79
|
+
const { useProxyImageUrl } = useImageProxy();
|
|
80
|
+
|
|
81
|
+
// Computed
|
|
82
|
+
// Calcul de la taille de l'image
|
|
83
|
+
const tailleImage = computed(() => {
|
|
84
|
+
// L'élément fait 400 de large à la verticale, mais on prend en compte les bordures
|
|
85
|
+
return props.vertical ? '396' : '250';
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
//Watch
|
|
89
|
+
watch(isPhone, async () => {
|
|
90
|
+
nextTick(() => {
|
|
91
|
+
if (!props.description || isPhone.value) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const itemDesc = descriptionItemRef?.value as HTMLElement;
|
|
95
|
+
const itemDescContainer = descriptionItemContainerRef?.value as HTMLElement;
|
|
96
|
+
if (
|
|
97
|
+
itemDesc &&
|
|
98
|
+
itemDescContainer &&
|
|
99
|
+
itemDesc.clientHeight > itemDescContainer.clientHeight
|
|
100
|
+
) {
|
|
101
|
+
itemDescContainer.classList.add("after-element-description");
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}, { immediate: true });
|
|
105
|
+
function urlify(text:string|undefined){
|
|
106
|
+
return displayHelper.urlify(text);
|
|
107
|
+
}
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<style lang="scss">
|
|
111
|
+
.octopus-app {
|
|
112
|
+
.item-presentation-container {
|
|
113
|
+
@media (width <= 960px) {
|
|
114
|
+
width: 250px !important;
|
|
115
|
+
margin-right: 0.5rem;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.element-description {
|
|
119
|
+
height: 0;
|
|
120
|
+
flex-grow: 1;
|
|
121
|
+
max-height: unset;
|
|
122
|
+
|
|
123
|
+
p:first-child {
|
|
124
|
+
// Prevent unecessary space before first paragraph
|
|
125
|
+
margin-top: 0 !important;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.classic-element-container.vertical-item {
|
|
131
|
+
flex-grow: 0;
|
|
132
|
+
width: 400px;
|
|
133
|
+
flex-shrink: 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.img-box {
|
|
137
|
+
// Make it relative so that absolute elements associated with the image
|
|
138
|
+
// are positioned within the image box
|
|
139
|
+
position: relative;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.img-box-bigger {
|
|
143
|
+
// L'élément fait 400 de large à la verticale, mais on prend en compte les bordures
|
|
144
|
+
width: 396px;
|
|
145
|
+
height: 396px;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
</style>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A simple layout to display 5 elements over 3 columns
|
|
3
|
+
-->
|
|
4
|
+
<template>
|
|
5
|
+
<div class="d-flex flex-column p-3">
|
|
6
|
+
<h2
|
|
7
|
+
v-if="title"
|
|
8
|
+
class="mb-3"
|
|
9
|
+
>
|
|
10
|
+
{{ title }}
|
|
11
|
+
</h2>
|
|
12
|
+
|
|
13
|
+
<div class="d-flex flex-nowrap align-items-stretch overflow-phone-auto">
|
|
14
|
+
<!-- First column & first item -->
|
|
15
|
+
<slot
|
|
16
|
+
v-if="items[0]"
|
|
17
|
+
name="first" :item="items[0]"
|
|
18
|
+
>
|
|
19
|
+
<slot name="item" :item="items[0]" :first="true" />
|
|
20
|
+
</slot>
|
|
21
|
+
|
|
22
|
+
<!-- Second column & second/third items -->
|
|
23
|
+
<div
|
|
24
|
+
v-if="items.length > 1"
|
|
25
|
+
class="column column-margin d-flex-row flex-nowrap"
|
|
26
|
+
:class="items.length <= 3 ? 'flex-grow-1' : ''"
|
|
27
|
+
>
|
|
28
|
+
<slot
|
|
29
|
+
v-if="items[1]"
|
|
30
|
+
name="item"
|
|
31
|
+
:item="items[1]"
|
|
32
|
+
:first="false"
|
|
33
|
+
/>
|
|
34
|
+
<slot
|
|
35
|
+
v-if="items[2]"
|
|
36
|
+
name="item"
|
|
37
|
+
:item="items[2]"
|
|
38
|
+
:first="false"
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- Third column & fourth/fifth items -->
|
|
43
|
+
<div
|
|
44
|
+
v-if="items.length > 3"
|
|
45
|
+
class="column d-flex-row flex-nowrap show-column"
|
|
46
|
+
>
|
|
47
|
+
<slot
|
|
48
|
+
v-if="items[3]"
|
|
49
|
+
name="item"
|
|
50
|
+
:item="items[3]"
|
|
51
|
+
:first="false"
|
|
52
|
+
/>
|
|
53
|
+
<slot
|
|
54
|
+
v-if="items[4]"
|
|
55
|
+
name="item"
|
|
56
|
+
:item="items[4]"
|
|
57
|
+
:first="false"
|
|
58
|
+
/>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<!-- Redirection button -->
|
|
63
|
+
<router-link
|
|
64
|
+
v-if="route"
|
|
65
|
+
:to="route"
|
|
66
|
+
class="btn btn-primary align-self-center w-fit-content m-4"
|
|
67
|
+
>
|
|
68
|
+
{{ buttonText || $t('See more') }}
|
|
69
|
+
</router-link>
|
|
70
|
+
</div>
|
|
71
|
+
</template>
|
|
72
|
+
|
|
73
|
+
<script setup lang="ts" generic="T">
|
|
74
|
+
import { RouteParams } from 'vue-router';
|
|
75
|
+
|
|
76
|
+
//Props
|
|
77
|
+
defineProps<{
|
|
78
|
+
/** The items to display */
|
|
79
|
+
items: Array<T>;
|
|
80
|
+
/** The title to display at the top of the layout */
|
|
81
|
+
title?: string;
|
|
82
|
+
/** If defined, the route to which the 'See more' button will redirect to */
|
|
83
|
+
route?: RouteParams|string;
|
|
84
|
+
/** The label on the 'See more' button */
|
|
85
|
+
buttonText?: string;
|
|
86
|
+
}>();
|
|
87
|
+
</script>
|
|
88
|
+
|
|
89
|
+
<style lang="scss">
|
|
90
|
+
.octopus-app {
|
|
91
|
+
.overflow-phone-auto {
|
|
92
|
+
@media (width <= 960px) {
|
|
93
|
+
overflow-y: auto;
|
|
94
|
+
scroll-snap-type: x mandatory;
|
|
95
|
+
|
|
96
|
+
.classic-element-container{
|
|
97
|
+
scroll-snap-align: center;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.column {
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
width: calc((100% - 420px) / 2);
|
|
105
|
+
|
|
106
|
+
@media (width <= 1550px) {
|
|
107
|
+
width: calc((100% - 420px));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@media (width <= 960px) {
|
|
111
|
+
width: auto;
|
|
112
|
+
flex-direction: row !important;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.column-margin {
|
|
117
|
+
margin-right: 1rem;
|
|
118
|
+
|
|
119
|
+
@media (width <= 960px) {
|
|
120
|
+
margin-right: 0;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.show-column {
|
|
125
|
+
display: flex;
|
|
126
|
+
|
|
127
|
+
@media (width <= 1550px) and (width > 960px) {
|
|
128
|
+
display: none;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
</style>
|