Skip to content

Commit 7ce4b4c

Browse files
authored
[Remove Vuetify from Studio] Channel details in Channels - page layout
1 parent 6277ec6 commit 7ce4b4c

File tree

8 files changed

+342
-137
lines changed

8 files changed

+342
-137
lines changed

contentcuration/contentcuration/frontend/settings/pages/SettingsIndex.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
</template>
1919
</AppBar>
2020
<StudioOfflineAlert :offset="104" />
21-
<StudioPage :offline="offline">
21+
<StudioPage
22+
:offline="offline"
23+
:marginTop="104"
24+
>
2225
<router-view />
2326
</StudioPage>
2427
<GlobalSnackbar />

contentcuration/contentcuration/frontend/shared/styles/main.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
html {
99
overflow-y: auto !important;
1010

11+
// Prevent body scrolling when StudioImmersiveModal is open
12+
&.modal-open {
13+
overflow-y: hidden !important;
14+
}
15+
1116
.title,
1217
.headline,
1318
.display,
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
<template>
2+
3+
<div
4+
v-if="value"
5+
class="modal-wrapper"
6+
data-testid="modal-wrapper"
7+
:style="{ backgroundColor: $themeTokens.surface }"
8+
>
9+
<KToolbar
10+
textColor="white"
11+
:style="{ backgroundColor: $themeTokens.appBarDark }"
12+
>
13+
<template #icon>
14+
<KIconButton
15+
icon="close"
16+
:ariaLabel="$tr('close')"
17+
:color="$themeTokens.textInverted"
18+
data-test="close"
19+
@click="$emit('input', false)"
20+
/>
21+
</template>
22+
23+
<template #default>
24+
<span class="toolbar-title">
25+
<slot name="header">{{ title }}</slot>
26+
</span>
27+
</template>
28+
29+
<template #actions>
30+
<slot name="action"></slot>
31+
</template>
32+
</KToolbar>
33+
34+
<StudioOfflineAlert :offset="46" />
35+
36+
<StudioPage
37+
:offline="offline"
38+
:marginTop="0"
39+
:centered="true"
40+
>
41+
<slot></slot>
42+
</StudioPage>
43+
</div>
44+
45+
</template>
46+
47+
48+
<script>
49+
50+
import { mapState } from 'vuex';
51+
import StudioOfflineAlert from './StudioOfflineAlert';
52+
import StudioPage from './StudioPage';
53+
54+
export default {
55+
name: 'StudioImmersiveModal',
56+
components: {
57+
StudioOfflineAlert,
58+
StudioPage,
59+
},
60+
props: {
61+
value: {
62+
type: Boolean,
63+
default: false,
64+
},
65+
title: {
66+
type: String,
67+
required: false,
68+
default: '',
69+
},
70+
},
71+
computed: {
72+
...mapState({
73+
offline: state => !state.connection.online,
74+
}),
75+
},
76+
mounted() {
77+
document.documentElement.classList.add('modal-open');
78+
const handleKeyDown = event => {
79+
if (event.key === 'Escape') {
80+
this.$emit('input', false);
81+
}
82+
};
83+
document.addEventListener('keydown', handleKeyDown);
84+
this.$once('hook:beforeDestroy', () => {
85+
document.documentElement.classList.remove('modal-open');
86+
document.removeEventListener('keydown', handleKeyDown);
87+
});
88+
},
89+
$trs: {
90+
close: 'Close',
91+
},
92+
};
93+
94+
</script>
95+
96+
97+
<style lang="scss" scoped>
98+
99+
.modal-wrapper {
100+
position: fixed;
101+
top: 0;
102+
right: 0;
103+
bottom: 0;
104+
left: 0;
105+
z-index: 16;
106+
display: flex;
107+
flex-direction: column;
108+
}
109+
110+
.toolbar-title {
111+
margin-inline-start: 16px;
112+
margin-inline-end: 16px;
113+
}
114+
115+
</style>

contentcuration/contentcuration/frontend/shared/views/StudioLargeLoader.vue

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<template>
22

3-
<div class="large-loader">
3+
<div
4+
class="large-loader"
5+
data-testid="loader"
6+
>
47
<KCircularLoader :size="70" />
58
</div>
69

contentcuration/contentcuration/frontend/shared/views/StudioPage.vue

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
<template>
22

3-
<main class="studio-page-outer">
3+
<main
4+
class="studio-page-outer"
5+
:style="outerStyle"
6+
>
47
<div
58
class="studio-page-inner"
69
:style="innerStyle"
@@ -29,12 +32,22 @@
2932
paddingRight: `${paddingX.value}px`,
3033
paddingTop: `${paddingTop.value}px`,
3134
maxWidth: windowIsLarge.value ? '1000px' : '100%',
35+
margin: props.centered ? '0 auto' : '0',
3236
}));
3337
34-
return { innerStyle };
38+
const outerStyle = computed(() => {
39+
return {
40+
marginTop: `${props.marginTop}px`,
41+
height: `calc(100vh - ${props.marginTop}px)`,
42+
};
43+
});
44+
45+
return { innerStyle, outerStyle };
3546
},
3647
props: {
3748
offline: { type: Boolean, default: false },
49+
marginTop: { type: Number, default: 104 },
50+
centered: { type: Boolean, default: false },
3851
},
3952
};
4053
@@ -45,16 +58,12 @@
4558
4659
.studio-page-outer {
4760
width: 100%;
48-
height: calc(100vh - 104px);
49-
margin-top: 104px;
5061
margin-bottom: 16px;
51-
overflow-x: hidden;
5262
overflow-y: auto;
5363
}
5464
5565
.studio-page-inner {
5666
width: 100%;
57-
margin-left: 0;
5867
}
5968
6069
</style>

contentcuration/contentcuration/frontend/shared/views/channel/ChannelDetailsModal.vue

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,79 @@
11
<template>
22

3-
<FullscreenModal
4-
v-model="dialog"
5-
color="appBarDark"
6-
:dark="true"
7-
>
3+
<StudioImmersiveModal v-model="dialog">
84
<template #header>
95
<span class="notranslate">{{ channel ? channel.name : '' }}</span>
106
</template>
11-
<LoadingText
12-
v-if="loading"
13-
absolute
14-
/>
15-
<VCardText v-else-if="channel">
16-
<VLayout class="mb-3">
17-
<VSpacer v-if="$vuetify.breakpoint.smAndUp" />
18-
<BaseMenu>
19-
<template #activator="{ on }">
20-
<VBtn
21-
color="primary"
22-
dark
23-
:block="$vuetify.breakpoint.xsOnly"
24-
v-on="on"
25-
>
26-
{{ $tr('downloadButton') }}
27-
&nbsp;
28-
<Icon
29-
icon="dropdown"
30-
:color="$themeTokens.textInverted"
31-
/>
32-
</VBtn>
7+
<StudioLargeLoader v-if="show('channelDetails', loading, 500)" />
8+
<div v-else-if="channel">
9+
<div
10+
class="download-button-container"
11+
:style="downloadButtonContainerStyle"
12+
>
13+
<KButton
14+
:text="$tr('downloadButton')"
15+
:primary="true"
16+
hasDropdown
17+
:style="downloadButtonStyle"
18+
>
19+
<template #menu>
20+
<KDropdownMenu
21+
:options="downloadOptions"
22+
@select="handleDownloadSelect"
23+
/>
3324
</template>
34-
<VList>
35-
<VListTile @click="generatePDF">
36-
<VListTileTitle>{{ $tr('downloadPDF') }}</VListTileTitle>
37-
</VListTile>
38-
<VListTile
39-
data-test="dl-csv"
40-
@click="generateCSV"
41-
>
42-
<VListTileTitle>{{ $tr('downloadCSV') }}</VListTileTitle>
43-
</VListTile>
44-
</VList>
45-
</BaseMenu>
46-
</VLayout>
25+
</KButton>
26+
</div>
4727
<DetailsPanel
4828
v-if="channel && details"
4929
class="channel-details-wrapper"
5030
:details="channelWithDetails"
5131
:loading="loading"
5232
/>
53-
</VCardText>
54-
</FullscreenModal>
33+
</div>
34+
</StudioImmersiveModal>
5535

5636
</template>
5737

5838

5939
<script>
6040
6141
import { mapActions, mapGetters } from 'vuex';
42+
import { computed } from 'vue';
43+
import useKShow from 'kolibri-design-system/lib/composables/useKShow';
44+
import useKResponsiveWindow from 'kolibri-design-system/lib/composables/useKResponsiveWindow';
6245
import { channelExportMixin } from './mixins';
6346
import DetailsPanel from 'shared/views/details/DetailsPanel.vue';
47+
import StudioLargeLoader from 'shared/views/StudioLargeLoader';
48+
import StudioImmersiveModal from 'shared/views/StudioImmersiveModal';
6449
import { routerMixin } from 'shared/mixins';
65-
import LoadingText from 'shared/views/LoadingText';
66-
import FullscreenModal from 'shared/views/FullscreenModal';
6750
6851
export default {
6952
name: 'ChannelDetailsModal',
7053
components: {
7154
DetailsPanel,
72-
LoadingText,
73-
FullscreenModal,
55+
StudioLargeLoader,
56+
StudioImmersiveModal,
7457
},
7558
mixins: [routerMixin, channelExportMixin],
59+
setup() {
60+
const { show } = useKShow();
61+
const { windowIsSmall } = useKResponsiveWindow();
62+
63+
const downloadButtonContainerStyle = computed(() => ({
64+
justifyContent: windowIsSmall.value ? 'center' : 'flex-end',
65+
}));
66+
67+
const downloadButtonStyle = computed(() => ({
68+
width: windowIsSmall.value ? '100%' : 'auto',
69+
}));
70+
71+
return {
72+
show,
73+
downloadButtonContainerStyle,
74+
downloadButtonStyle,
75+
};
76+
},
7677
props: {
7778
channelId: {
7879
type: String,
@@ -83,7 +84,6 @@
8384
return {
8485
dialog: true,
8586
loading: true,
86-
loadError: false,
8787
details: null,
8888
};
8989
},
@@ -98,6 +98,12 @@
9898
}
9999
return { ...this.channel, ...this.details };
100100
},
101+
downloadOptions() {
102+
return [
103+
{ label: this.$tr('downloadPDF'), value: 'pdf' },
104+
{ label: this.$tr('downloadCSV'), value: 'csv' },
105+
];
106+
},
101107
backLink() {
102108
return {
103109
name: this.$route.query.last,
@@ -135,6 +141,13 @@
135141
},
136142
methods: {
137143
...mapActions('channel', ['loadChannel', 'loadChannelDetails']),
144+
handleDownloadSelect(option) {
145+
if (option.value === 'pdf') {
146+
this.generatePDF();
147+
} else if (option.value === 'csv') {
148+
this.generateCSV();
149+
}
150+
},
138151
async generatePDF() {
139152
this.$analytics.trackEvent('channel_details', 'Download PDF', {
140153
id: this.channelId,
@@ -171,7 +184,6 @@
171184
.catch(error => {
172185
this.$store.dispatch('errors/handleAxiosError', error);
173186
this.loading = false;
174-
this.loadError = true;
175187
});
176188
},
177189
},
@@ -187,14 +199,11 @@
187199

188200
<style lang="scss" scoped>
189201
190-
.v-toolbar__title {
191-
font-weight: bold;
192-
}
193-
194-
.draft-header {
195-
padding-right: 10px;
196-
font-style: italic;
197-
color: gray;
202+
.download-button-container {
203+
display: flex;
204+
justify-content: flex-end;
205+
margin-top: 32px;
206+
margin-bottom: 24px;
198207
}
199208
200209
.channel-details-wrapper {

0 commit comments

Comments
 (0)