Skip to content

Commit

Permalink
Merge pull request #269 from RyanCoulsonCA/fix-410
Browse files Browse the repository at this point in the history
implement dynamic slideshow editor
  • Loading branch information
yileifeng authored Apr 5, 2024
2 parents 367a85b + 12afb85 commit 5b18909
Show file tree
Hide file tree
Showing 10 changed files with 645 additions and 136 deletions.
81 changes: 71 additions & 10 deletions src/components/editor/chart-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
class="chart-btn bg-gray-100 cursor-pointer hover:bg-gray-200"
id="modal-btn"
@click="clearEditor()"
:disabled="!allowMany && chartConfigs.length > 0"
>
<div class="flex items-center">
<svg height="18px" width="18px" viewBox="0 0 23 21" xmlns="http://www.w3.org/2000/svg">
Expand Down Expand Up @@ -44,6 +45,8 @@
:key="`${element.name}-${index}`"
:chart="element"
:configFileStructure="configFileStructure"
:sourceCounts="sourceCounts"
:lang="lang"
@edit="editChart"
@delete="$vfm.open(`${element.name}-${index}`)"
></ChartPreview>
Expand All @@ -62,11 +65,18 @@

<script lang="ts">
import { Options, Prop, Vue } from 'vue-property-decorator';
import { ChartConfig, ChartPanel, ConfigFileStructure, Highchart, SourceCounts } from '@/definitions';
import {
ChartConfig,
ChartPanel,
ConfigFileStructure,
Highchart,
PanelType,
SlideshowPanel,
SourceCounts
} from '@/definitions';
import ChartPreviewV from '@/components/editor/helpers/chart-preview.vue';
import ConfirmationModalV from '@/components/editor/helpers/confirmation-modal.vue';
import draggable from 'vuedraggable';
import { chart } from 'highcharts';
@Options({
components: {
Expand All @@ -78,10 +88,11 @@ import { chart } from 'highcharts';
}
})
export default class ChartEditorV extends Vue {
@Prop() panel!: ChartPanel;
@Prop() panel!: ChartPanel | SlideshowPanel;
@Prop() configFileStructure!: ConfigFileStructure;
@Prop() lang!: string;
@Prop() sourceCounts!: SourceCounts;
@Prop({ default: true }) allowMany!: boolean;
edited = false;
Expand All @@ -106,9 +117,17 @@ export default class ChartEditorV extends Vue {
);
});
// This allows us to access the chart(s) using one consistent variable instead of needing to check panel type.
const charts =
this.panel.type === PanelType.Slideshow
? (this.panel.items as Array<ChartPanel>)
: this.panel.src
? [this.panel]
: [];
// load charts from existing storylines product
if (this.panel.charts !== undefined && this.panel.charts.length) {
this.chartConfigs = this.panel.charts.map((chart: ChartConfig) => {
if (charts !== undefined && charts.length) {
this.chartConfigs = charts.map((chart: ChartPanel) => {
let chartName = '';
// extract chart name
if (chart.options && chart.options.title) {
Expand Down Expand Up @@ -150,11 +169,19 @@ export default class ChartEditorV extends Vue {
createNewChart(chartInfo: string): void {
const chart = JSON.parse(chartInfo);
// prevent duplicate chart names (alternative is to assign a unique ID for each chart)
if (this.chartConfigs.some((chartConfig) => chartConfig.name === chart.title.text)) {
alert('Existing chart already has the same chart name.');
const chartSrc = `${this.configFileStructure.uuid}/charts/${this.lang}/${chart.title.text}.json`;
// Check to see if a chart already exists with the provided name. If so, alert the user and re-prompt.
if (this.sourceCounts[chartSrc] > 0) {
alert(
this.$t('editor.chart.label.nameExists', {
name: chart.title.text
})
);
// Re-open the editor the the issue can be fixed.
setTimeout(() => this.modalEditor.show(), 100);
} else {
const chartSrc = `${this.configFileStructure.uuid}/charts/${this.lang}/${chart.title.text}.json`;
const chartConfig = {
name: chart.title.text,
src: chartSrc
Expand Down Expand Up @@ -218,8 +245,42 @@ export default class ChartEditorV extends Vue {
saveChanges(): void {
if (this.edited) {
this.panel.charts = this.chartConfigs; // option to delete config property as is redundant
// Delete the existing properties so we can rebuild the object.
Object.keys(this.panel).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
delete this.panel[key];
});
// Handle case where every image is deleted.
if (this.chartConfigs.length === 0) {
this.panel.type = PanelType.Chart;
(this.panel as ChartPanel).src = '';
} else if (this.chartConfigs.length === 1) {
this.panel.type = PanelType.Chart;
// Grab the one chart config from the array.
const newChart = this.chartConfigs[0];
// Sort of gross, but required to update the panel config as we're not allowed to directly manipulate props.
Object.keys(newChart).forEach((key) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
(this.panel as ChartPanel)[key] = newChart[key];
});
} else {
this.panel.type = PanelType.Slideshow;
// Turn each of the chart configs into a chart panel and add them to the slideshow.
(this.panel as SlideshowPanel).items = this.chartConfigs.map((chart: ChartConfig) => {
return {
...chart,
type: PanelType.Chart
} as ChartPanel;
});
}
}
this.edited = false;
}
Expand Down
103 changes: 58 additions & 45 deletions src/components/editor/dynamic-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
</tr>
<tr class="table-contents" v-for="(item, idx) in panel.children" :key="idx">
<td>{{ item.id }}</td>
<td>{{ item.panel.type }}</td>
<td>{{ determineEditorType(item.panel) }}</td>
<td>
<span @click="() => switchSlide(idx)">{{ $t('editor.chart.label.edit') }}</span> |
<span @click="() => removeSlide(idx)">{{ $t('editor.remove') }}</span>
<span @click="() => removeSlide(item, idx)">{{ $t('editor.remove') }}</span>
</td>
</tr>
<tr class="table-add-row">
Expand All @@ -49,10 +49,7 @@
</th>
<th>
<select v-model="newSlideType">
<option
v-for="thing in Object.keys(editors).filter((editor) => editor !== 'image')"
:key="thing"
>
<option v-for="thing in Object.keys(editors)" :key="thing">
{{ thing }}
</option>
</select>
Expand All @@ -69,14 +66,8 @@
><br />
<component
ref="slide"
:is="
editors[
panel.children[editingSlide].panel.type === 'image'
? 'slideshow'
: panel.children[editingSlide].panel.type
]
"
:key="editingSlide + panel.children[editingSlide].panel.type"
:is="editors[determineEditorType(panel.children[editingSlide].panel)]"
:key="editingSlide + determineEditorType(panel.children[editingSlide].panel)"
:panel="panel.children[editingSlide].panel"
:configFileStructure="configFileStructure"
:lang="lang"
Expand All @@ -91,7 +82,7 @@
<script lang="ts">
import { Options, Prop, Vue } from 'vue-property-decorator';
import {
ChartConfig,
BasePanel,
ChartPanel,
ConfigFileStructure,
DefaultConfigs,
Expand All @@ -102,6 +93,7 @@ import {
PanelType,
SlideshowPanel,
SourceCounts,
TextPanel,
VideoPanel
} from '@/definitions';
Expand All @@ -110,12 +102,14 @@ import ImageEditorV from './image-editor.vue';
import TextEditorV from './text-editor.vue';
import MapEditorV from './map-editor.vue';
import VideoEditorV from './video-editor.vue';
import SlideshowEditorV from './slideshow-editor.vue';
@Options({
components: {
'chart-editor': ChartEditorV,
'image-editor': ImageEditorV,
'text-editor': TextEditorV,
'slideshow-editor': SlideshowEditorV,
'dynamic-editor': DynamicEditorV,
'map-editor': MapEditorV,
'video-editor': VideoEditorV
Expand All @@ -130,7 +124,7 @@ export default class DynamicEditorV extends Vue {
editors: Record<string, string> = {
text: 'text-editor',
image: 'image-editor',
slideshow: 'image-editor',
slideshow: 'slideshow-editor',
chart: 'chart-editor',
map: 'map-editor',
video: 'video-editor'
Expand All @@ -151,11 +145,16 @@ export default class DynamicEditorV extends Vue {
},
slideshow: {
type: PanelType.Slideshow,
images: []
items: [],
userCreated: true
},
image: {
type: PanelType.Image,
src: ''
},
chart: {
type: PanelType.Chart,
charts: []
src: ''
},
map: {
type: PanelType.Map,
Expand Down Expand Up @@ -192,19 +191,9 @@ export default class DynamicEditorV extends Vue {
// Save slide changes if neccessary and switch to the newly selected slide.
this.saveChanges();
this.editingSlide = idx;
// Image Panel to Slideshow Panel Conversion
if (this.panel.children[this.editingSlide].panel.type === 'image') {
(this.panel.children[this.editingSlide].panel as SlideshowPanel) = {
type: PanelType.Slideshow,
images: [this.panel.children[this.editingSlide].panel as ImagePanel]
};
}
}
removeSlide(item: number): void {
const panel = this.panel.children.find((panel: DynamicChildItem, idx: number) => idx === item)?.panel;
removeSlide(panel: BasePanel, index?: number): void {
// Update source counts based on which panel is removed.
switch (panel?.type) {
case 'map': {
Expand All @@ -220,22 +209,27 @@ export default class DynamicEditorV extends Vue {
case 'chart': {
const chartPanel = panel as ChartPanel;
chartPanel.charts.forEach((chart: ChartConfig) => {
this.sourceCounts[chart.src] -= 1;
if (this.sourceCounts[chart.src] === 0) {
this.configFileStructure.zip.remove(`${chart.src.substring(chart.src.indexOf('/') + 1)}`);
}
});
this.sourceCounts[chartPanel.src] -= 1;
if (this.sourceCounts[chartPanel.src] === 0) {
this.configFileStructure.zip.remove(`${chartPanel.src.substring(chartPanel.src.indexOf('/') + 1)}`);
}
break;
}
case 'image': {
const imagePanel = panel as ImagePanel;
this.sourceCounts[imagePanel.src] -= 1;
if (this.sourceCounts[imagePanel.src] === 0) {
this.configFileStructure.zip.remove(`${imagePanel.src.substring(imagePanel.src.indexOf('/') + 1)}`);
}
break;
}
case 'slideshow': {
const slideshowPanel = panel as SlideshowPanel;
slideshowPanel.images.forEach((image: ImagePanel) => {
this.sourceCounts[image.src] -= 1;
if (this.sourceCounts[image.src] === 0) {
this.configFileStructure.zip.remove(`${image.src.substring(image.src.indexOf('/') + 1)}`);
}
slideshowPanel.items.forEach((item: TextPanel | ImagePanel | MapPanel | ChartPanel) => {
this.removeSlide(item);
});
break;
}
Expand All @@ -254,12 +248,14 @@ export default class DynamicEditorV extends Vue {
}
}
// Remove the panel itself.
this.panel.children = this.panel.children.filter((panel: DynamicChildItem, idx: number) => idx !== item);
if (index) {
// Remove the panel itself.
this.panel.children = this.panel.children.filter((panel: DynamicChildItem, idx: number) => idx !== index);
// If the slide being removed is the currently selected slide, unselect it.
if (this.editingSlide === item) {
this.editingSlide = -1;
// If the slide being removed is the currently selected slide, unselect it.
if (this.editingSlide === index) {
this.editingSlide = -1;
}
}
}
Expand All @@ -275,6 +271,23 @@ export default class DynamicEditorV extends Vue {
this.panel.children.push(newConfig);
}
determineEditorType(panel: BasePanel): string {
if (panel.type !== PanelType.Slideshow) return panel.type;
if ((panel as SlideshowPanel).items.length === 0 || (panel as SlideshowPanel).userCreated)
return PanelType.Slideshow;
// Determine whether the slideshow consists of only charts. If so, display the chart editor.
const allCharts = (panel as SlideshowPanel).items.every((item: BasePanel) => item.type === PanelType.Chart);
if (allCharts) return PanelType.Chart;
// Determine whether the slideshow consists of only images. If so, display the image editor.
const allImages = (panel as SlideshowPanel).items.every((item: BasePanel) => item.type === PanelType.Image);
if (allImages) return PanelType.Image;
// Otherwise display the slideshow editor.
return PanelType.Slideshow;
}
saveChanges(): void {
if (
this.$refs.slide !== undefined &&
Expand Down
Loading

0 comments on commit 5b18909

Please sign in to comment.