@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 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.0.21",
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 response = await saveFetchStore.getOrgaData(organisationId);
22
- const data = await classicApi.fetchData<Array<Rubriquage>>({
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 isLive = await saveFetchStore.getOrgaLiveEnabled(organisationId);
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 p-3 list-episode">
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
- organisationId: { default: undefined, type: String },
48
- href: { default: undefined, type: String },
49
- buttonText: { default: undefined, type: String },
50
- rubriqueId: { default: undefined, type: Number },
51
- rubriquageId: { default: undefined, type: Number },
52
- nbPodcasts: { default: undefined, type: Number },
53
- itemSize: { default: undefined, type: Number },
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 basic-line-clamp">
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>
@@ -142,8 +142,6 @@ async function loadPodcasts(): Promise<void> {
142
142
  </script>
143
143
 
144
144
  <style lang="scss">
145
-
146
-
147
145
  .emission-player-container {
148
146
  list-style: none;
149
147
  background: var(--octopus-background);
@@ -1,129 +1,34 @@
1
+ <!--
2
+ Component to display an emission with its description
3
+ -->
1
4
  <template>
2
- <article
3
- class="classic-element-container emission-presentation-container mt-3"
4
- :class="isVertical ? 'emission-vertical-item' : ''"
5
- >
6
- <router-link
7
- :to="{
8
- name: 'emission',
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 {useImageProxy} from "../../composable/useImageProxy";
51
- import displayHelper from "../../../helper/displayHelper";
52
- import { nextTick, useTemplateRef, watch, computed } from "vue";
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
- emission: { default: () => ({}), type: Object as () => Emission },
58
- isVertical: { default: false, type: Boolean },
59
- isDescription: { default: false, type: Boolean },
60
- })
61
-
62
- //Data
63
- const descriptionEmissionRef = useTemplateRef('descriptionEmission');
64
- const descriptionEmissionContainerRef = useTemplateRef('descriptionEmissionContainer');
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
- <div class="d-flex flex-column p-3">
3
- <h2 class="mb-3">{{ title }}</h2>
4
- <ClassicLoading
5
- :loading-text="loading ? t('Loading emissions ...') : undefined"
6
- :error-text="error ? t(`Error`) : undefined"
7
- />
8
- <template v-if="!loading && !error">
9
- <div class="d-flex flex-nowrap align-items-stretch overflow-phone-auto">
10
- <EmissionItemPresentation
11
- v-if="allEmissions[0]"
12
- :class="!isPhone ? 'me-3' : ''"
13
- :emission="allEmissions[0]"
14
- :is-vertical="!isPhone"
15
- :is-description="isDescription"
16
- />
17
- <div
18
- v-if="allEmissions.length > 1"
19
- class="emission-column emission-column-margin d-flex-row flex-nowrap"
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
- </div>
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
- import { useI18n } from "vue-i18n";
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
- playlistId: { default: undefined, type: Number },
47
- sizeItemOverload: { default: undefined, type: Number }
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>
@@ -18,6 +18,7 @@
18
18
  </template>
19
19
  </ClassicLazy>
20
20
  </template>
21
+
21
22
  <template v-else>
22
23
  <ClassicLazy
23
24
  v-for="(r, index) in rubriqueToShow"
@@ -3,6 +3,7 @@ import { Organisation } from "./organisation";
3
3
  import { Participant } from "./participant";
4
4
  import { Person } from "../user/person";
5
5
  import { Video } from "./video";
6
+
6
7
  export interface Podcast {
7
8
  imageUrl?: string;
8
9
  animators?: Array<Participant>;