diff --git a/server/index.js b/server/index.js index 499952d2..092a5d6b 100644 --- a/server/index.js +++ b/server/index.js @@ -66,6 +66,7 @@ app.route(ROUTE_PREFIX + '/upload').post(function (req, res, next) { res.status(500).send({ status: 'Internal Server Error' }); return; } + logger('INFO', file.data.originalFilename); const fileName = `${TARGET_PATH}/${file.data.originalFilename.split('.zip')[0]}`; const secureFilename = `${UPLOAD_PATH}/${file.data.newFilename}`; diff --git a/src/components/helpers/custom-editor.vue b/src/components/helpers/custom-editor.vue index bb6fd2d8..3cbf79a8 100644 --- a/src/components/helpers/custom-editor.vue +++ b/src/components/helpers/custom-editor.vue @@ -129,6 +129,7 @@ export default class CustomEditorV extends Vue { } saveChanges(): void { + console.log('customeditor.vue - saving changes after opening advanced editor '); this.$emit('config-edited', this.updatedConfig); this.edited = false; diff --git a/src/components/image-editor.vue b/src/components/image-editor.vue index d4ba3d92..1965813f 100644 --- a/src/components/image-editor.vue +++ b/src/components/image-editor.vue @@ -127,6 +127,8 @@ export default class ImageEditorV extends Vue { } mounted(): void { + console.log(''); + console.log('image panel mounted'); // This basically allows us to access the image(s) using one consistent variable instead of needing to check panel type. const images = this.panel.type === PanelType.Slideshow @@ -134,6 +136,8 @@ export default class ImageEditorV extends Vue { : this.panel.src ? [this.panel] : []; + console.log('images of panel'); + console.log(images); if (this.centerSlide && this.dynamicSelected) { for (const i in images) { @@ -151,12 +155,28 @@ export default class ImageEditorV extends Vue { // Process each existing image. images.map((image: ImagePanel) => { + console.log('current image being processed'); + console.log(image); // Check if the config file exists in the ZIP folder first. const assetSrc = `${image.src.substring(image.src.indexOf('/') + 1)}`; + console.log('image src'); + console.log(assetSrc); const filename = image.src.replace(/^.*[\\/]/, ''); - - const assetFile = this.configFileStructure.zip.file(assetSrc); + console.log('image file name'); + console.log(filename); + + // Check if the asset has been removed from the current langs assets folder and added to the + // shared assets folder. If so, obtain the image from the shared assets folder to insert into + // imagePreviews. In this case, the src of this asset within the panel will still be "out of date" + // (i.e. will still refer to the current langs assets folder), but this will be resolved once + // saveChanges() is executed + const assetFile = + this.configFileStructure.zip.file(assetSrc) ?? + this.configFileStructure.assets['shared'].file(filename); + //const assetFile = this.configFileStructure.zip.file(assetSrc); if (assetFile) { + console.log('asset file found, will be inserted into imagePreviews'); + console.log(assetFile); this.imagePreviewPromises.push( assetFile.async('blob').then((res: Blob) => { return { @@ -173,40 +193,86 @@ export default class ImageEditorV extends Vue { Promise.all(this.imagePreviewPromises).then((res) => { this.imagePreviews = res; this.imagePreviewsLoading = false; + console.log('imagePreviews after mounting image panel'); + console.log(this.imagePreviews); }); - this.slideshowCaption = this.panel.caption as string; } } + // helper for onFileChange and dropImages. Maps a File object to an ImageFile object + addUploadedFile(file: File) { + let uploadSource = `${this.configFileStructure.uuid}/assets/shared/${file.name}`; + const oppositeLang = this.lang === 'en' ? 'fr' : 'en'; + let oppositeSourceCount = 0; + console.log('file name of dropped image'); + console.log(file.name); + console.log('file in opposite langs asets folder?'); + console.log(!!this.configFileStructure.assets[oppositeLang].file(file.name)); + console.log('file in current langs asets folder?'); + console.log(!!this.configFileStructure.assets[this.lang].file(file.name)); + + // If the file is already in the opposite langs assets folder, we should move it to the shared + // assets folder, rather than duplicating it within the current lang's assets folder. Otherwise, + // if the file is already in the shared assets folder, then nothing needs to be done, since the + // panel config will simply access the file from the shared assets folder. If the asset is neither + // in the shared nor the opposite langs assets folder, the file should be uploaded to the current + // langs assets folder + if (this.configFileStructure.assets[oppositeLang].file(file.name)) { + const oppositeFileSource = `${this.configFileStructure.uuid}/assets/${oppositeLang}/${file.name}`; + oppositeSourceCount = this.sourceCounts[oppositeFileSource] ?? 0; + this.sourceCounts[oppositeFileSource] = 0; + this.configFileStructure.assets[oppositeLang].remove(file.name); + this.configFileStructure.assets['shared'].file(file.name, file); + } else if (!this.configFileStructure.assets['shared'].file(file.name)) { + uploadSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${file.name}`; + this.configFileStructure.assets[this.lang].file(file.name, file); + } + + if (this.sourceCounts[uploadSource]) { + this.sourceCounts[uploadSource] += 1 + oppositeSourceCount; + } else { + this.sourceCounts[uploadSource] = 1 + oppositeSourceCount; + } + + let imageSrc = URL.createObjectURL(file); + return { + id: file.name, + altText: '', + caption: '', + src: imageSrc + }; + } + onFileChange(e: Event): void { + console.log(' '); + console.log('onFileChange()'); + console.log('configFileStructure: Before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews: before'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); // create object URL(s) to display image(s) const filelist = Array.from((e.target as HTMLInputElement).files as ArrayLike); this.imagePreviews.push( ...filelist.map((file: File) => { - // Add the uploaded images to the product ZIP file. - const uploadSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${file.name}`; - this.configFileStructure.assets[this.lang].file(file.name, file); - - if (this.sourceCounts[uploadSource]) { - this.sourceCounts[uploadSource] += 1; - } else { - this.sourceCounts[uploadSource] = 1; - } - - let imageSrc = URL.createObjectURL(file); - return { - id: file.name, - altText: '', - caption: '', - src: imageSrc - }; + return this.addUploadedFile(file); }) ); + + console.log('configFileStructure: After'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews: after'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); this.onImagesEdited(); } dropImages(e: DragEvent): void { + console.log(' '); + console.log('dropImages()'); + console.log('configFileStructure: Before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews: before'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); if (e.dataTransfer !== null) { let files = [...e.dataTransfer.files]; @@ -217,48 +283,110 @@ export default class ImageEditorV extends Vue { this.imagePreviews.push( ...files.map((file: File) => { - // Add the uploaded images to the product ZIP file. - const uploadSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${file.name}`; - this.configFileStructure.assets[this.lang].file(file.name, file); - - if (this.sourceCounts[uploadSource]) { - this.sourceCounts[uploadSource] += 1; - } else { - this.sourceCounts[uploadSource] = 1; - } - - let imageSrc = URL.createObjectURL(file); - return { - id: file.name, - altText: '', - caption: '', - src: imageSrc - }; + return this.addUploadedFile(file); }) ); this.dragging = false; + + console.log('configFileStructure: After'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews: after'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); } this.onImagesEdited(); } deleteImage(img: ImageFile): void { + console.log(' '); + console.log('deleteImages()'); + console.log('deleting the following image:'); + console.log(img); + console.log('configFileStructure before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews before'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); const idx = this.imagePreviews.findIndex((file: ImageFile) => file.id === img.id); if (idx !== -1) { - const fileSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${this.imagePreviews[idx].id}`; + // Set file source based on whether it exists in the shared assets folder or not + const fileSource = `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(this.imagePreviews[idx].id) ? 'shared' : this.lang + }/${this.imagePreviews[idx].id}`; // Remove the image from the product ZIP file. this.sourceCounts[fileSource] -= 1; if (this.sourceCounts[fileSource] === 0) { - this.configFileStructure.assets[this.lang].remove(this.imagePreviews[idx].id); + console.log('image file being deleted'); + console.log(this.imagePreviews[idx].id); + if (this.configFileStructure.assets['shared'].file(this.imagePreviews[idx].id)) { + console.log('file being deleted exists in the shared folder, delete from there'); + } else { + console.log('file being deleted should be deleted from current langs folder'); + } + + this.configFileStructure.assets[ + this.configFileStructure.assets['shared'].file(this.imagePreviews[idx].id) ? 'shared' : this.lang + ].remove(this.imagePreviews[idx].id); URL.revokeObjectURL(this.imagePreviews[idx].src); } this.imagePreviews.splice(idx, 1); } this.onImagesEdited(); + console.log('configFileStructure after'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('imagePreviews before'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); + } + + // checks if an asset within the image panel is "out of date". That is, the assets src is referrinhg to + // the current langs assets folder, but the asset has already been added to the shared assets folder. + // When this returns true, we want to execute the main logic of saveChanges() (regardless of if any + // changes were made to the panels content), where the src of each asset is fixed + assetsOutOfDate() { + console.log(' '); + console.log('assetsOutOfDate()'); + if (this.panel.type === 'slideshow') { + console.log('this is slideshow panel'); + console.log('imagePreviews'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); + for (let i = 0; i < this.panel.items.length; i++) { + let asset = this.panel.items[i]; + console.log('current asset of slideshow'); + console.log(asset.src); + let assetSrc = asset.src.split('/'); + const assetFolder = assetSrc[2]; + const assetId = assetSrc[3]; + if (this.configFileStructure.assets['shared'].file(assetId) && assetFolder !== 'shared') { + console.log('asset is out of date'); + return true; + } + } + } else { + console.log('this is image panel'); + console.log('asset of image panel'); + console.log(this.panel.src); + console.log('imagePreviews'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); + let assetSrc = this.panel.src.split('/'); + const assetFolder = assetSrc[2]; + const assetId = assetSrc[3]; + if (this.configFileStructure.assets['shared'].file(assetId) && assetFolder !== 'shared') { + console.log('asset is out of date'); + return true; + } + } + + return false; } saveChanges(): void { - if (this.edited) { + console.log(' '); + console.log('saveChanges()'); + console.log('saveChanges of image panel executed'); + console.log('image panel before'); + console.log(JSON.parse(JSON.stringify(this.panel))); + console.log('imagePreviews'); + console.log(JSON.parse(JSON.stringify(this.imagePreviews))); + if (this.edited || this.assetsOutOfDate()) { // 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 @@ -276,6 +404,8 @@ export default class ImageEditorV extends Vue { // Grab the one image from the array. const imageFile = this.imagePreviews[0]; + console.log('individual imageFile of image panel'); + console.log(imageFile); // Sort of gross, but required to update the panel config as we're not allowed to directly manipulate props. Object.keys(imageFile).forEach((key) => { @@ -286,23 +416,44 @@ export default class ImageEditorV extends Vue { (this.panel as ImagePanel)[key] = imageFile[key]; }); - (this.panel as ImagePanel).src = `${this.configFileStructure.uuid}/assets/${this.lang}/${imageFile.id}`; + if (this.configFileStructure.assets['shared'].file(imageFile.id)) { + console.log('image will be put into shared folder'); + } else { + console.log('image will be put into current langs folde'); + } + // Set the image source based on whether the image is in the shared assets folder or not + (this.panel as ImagePanel).src = `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(imageFile.id) ? 'shared' : this.lang + }/${imageFile.id}`; } else { // Otherwise, convert this to a slideshow panel. this.panel.type = PanelType.Slideshow; this.panel.caption = this.slideshowCaption ?? undefined; - // Turn each of the image configs into an image panel and add them to the slidesow. + // Turn each of the image configs into an image panel and add them to the slideshow. (this.panel as SlideshowPanel).items = this.imagePreviews.map((imageFile: ImageFile) => { + console.log('current image file of slideshow panel'); + console.log(imageFile); + if (this.configFileStructure.assets['shared'].file(imageFile.id)) { + console.log('image will be put into shared folder'); + } else { + console.log('image will be put into current langs folde'); + } + // Set the image source based on whether the image is in the shared assets folder or not + const imageSource = `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(imageFile.id) ? 'shared' : this.lang + }/${imageFile.id}`; return { ...imageFile, - src: `${this.configFileStructure.uuid}/assets/${this.lang}/${imageFile.id}`, + src: imageSource, type: PanelType.Image } as ImagePanel; }); } } this.edited = false; + console.log('image panel after'); + console.log(JSON.parse(JSON.stringify(this.panel))); } onImagesEdited(): void { diff --git a/src/components/metadata-editor.vue b/src/components/metadata-editor.vue index 873d1d42..7bd85d6e 100644 --- a/src/components/metadata-editor.vue +++ b/src/components/metadata-editor.vue @@ -447,7 +447,8 @@ export default class MetadataEditorV extends Vue { this.metadata.tocOrientation = 'vertical'; this.metadata.returnTop = true; } - + console.log('route values'); + console.log(this.$route); // Find which view to render based on route if (this.$route.name === 'editor') { this.loadEditor = true; @@ -455,6 +456,8 @@ export default class MetadataEditorV extends Vue { // Properties already passed in props, load editor view (could use a refactor to clean up this workflow process) if (props && props.configs && props.configFileStructure) { + console.log('props'); + console.log(props); this.configs = props.configs; this.configLang = props.configLang; this.configFileStructure = props.configFileStructure; @@ -465,7 +468,11 @@ export default class MetadataEditorV extends Vue { this.unsavedChanges = props.unsavedChanges; // Load product logo (if provided). const logo = this.configs[this.configLang]?.introSlide.logo?.src; - const logoSrc = `assets/${this.configLang}/${this.metadata.logoName}`; + console.log('logo'); + console.log(logo); + const logoSrc = `assets/shared/${this.metadata.logoName}`; + console.log('logoSrc'); + console.log(logoSrc); if (logo) { const logoFile = this.configFileStructure?.zip.file(logoSrc); @@ -501,8 +508,12 @@ export default class MetadataEditorV extends Vue { // If a product UUID is provided, fetch the contents from the server. if (this.$route.params.uid) { + console.log('fetching config from server'); this.generateRemoteConfig(); } + + console.log('source counts upon creating/mounting metadata editor'); + console.log(this.sourceCounts); } /** @@ -519,7 +530,8 @@ export default class MetadataEditorV extends Vue { if (!this.metadata.logoName) { config.introSlide.logo.src = ''; } else if (!this.metadata.logoName.includes('http')) { - config.introSlide.logo.src = `${this.uuid}/assets/${this.configLang}/${this.logoImage?.name}`; + // product logo located in shared folder now + config.introSlide.logo.src = `${this.uuid}/assets/shared/${this.logoImage?.name}`; } else { config.introSlide.logo.src = this.metadata.logoName; } @@ -536,6 +548,8 @@ export default class MetadataEditorV extends Vue { configZip.file(fileName, formattedConfigFile); configZip.file(`${this.uuid}_${otherLang}.json`, formattedOtherLangConfig); + console.log('zipped config (so far)'); + console.log(JSON.parse(JSON.stringify(configZip))); // Generate the file structure, defer uploading the image until the structure is created. this.configFileStructureHelper(configZip, this.logoImage); @@ -833,6 +847,14 @@ export default class MetadataEditorV extends Vue { const assetsFolder = configZip.folder('assets'); const chartsFolder = configZip.folder('charts'); const rampConfigFolder = configZip.folder('ramp-config'); + console.log('current state of configZip'); + console.log(JSON.parse(JSON.stringify(configZip))); + console.log('assetsFolder'); + console.log(JSON.parse(JSON.stringify(assetsFolder))); + console.log('chartsFolder'); + console.log(JSON.parse(JSON.stringify(chartsFolder))); + console.log('rampConfigFolder'); + console.log(JSON.parse(JSON.stringify(rampConfigFolder))); this.configFileStructure = { uuid: this.uuid, @@ -840,7 +862,8 @@ export default class MetadataEditorV extends Vue { configs: this.configs as unknown as { [key: string]: StoryRampConfig }, assets: { en: (assetsFolder as JSZip).folder('en') as JSZip, - fr: (assetsFolder as JSZip).folder('fr') as JSZip + fr: (assetsFolder as JSZip).folder('fr') as JSZip, + shared: (assetsFolder as JSZip).folder('shared') as JSZip }, charts: { en: (chartsFolder as JSZip).folder('en') as JSZip, @@ -849,11 +872,21 @@ export default class MetadataEditorV extends Vue { rampConfig: rampConfigFolder as JSZip }; + console.log('Initialization of configFileStructure complete'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + // If uploadLogo is set, upload the logo to the directory. + // Q: if we create a shared assets folder, should the logo just go there, since both langs + // should have the same logo? if (uploadLogo !== undefined) { - this.configFileStructure.assets[this.configLang].file(uploadLogo?.name, uploadLogo); + console.log('logo uploaded to configFileStructure'); + console.log(uploadLogo); + this.configFileStructure.assets['shared'].file(uploadLogo?.name, uploadLogo); } + console.log('source counts upon initializing configFileStructure'); + console.log(this.sourceCounts); + this.loadConfig(); } @@ -920,12 +953,19 @@ export default class MetadataEditorV extends Vue { const logo = config.introSlide.logo?.src; if (logo) { + console.log('logo'); + console.log(logo); // Set the alt text for the logo. this.metadata.logoAltText = config.introSlide.logo?.altText ? config.introSlide.logo.altText : ''; // Fetch the logo from the folder (if it exists). const logoSrc = `${logo.substring(logo.indexOf('/') + 1)}`; + console.log('logoSrc'); + console.log(logoSrc); + console.log('logoSrc being added to zip folder of configFileStructure'); const logoName = `${logo.split('/')[logo.split('/').length - 1]}`; + console.log('logoName'); + console.log(logoName); const logoFile = this.configFileStructure?.zip.file(logoSrc); if (logoFile) { @@ -962,12 +1002,17 @@ export default class MetadataEditorV extends Vue { */ generateConfig(): ConfigFileStructure { this.saving = true; - + console.log('current configs'); + console.log(JSON.parse(JSON.stringify(this.configs))); + console.log('current configFileStructure'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); // Update the configuration file. const fileName = `${this.uuid}_${this.configLang}.json`; const formattedConfigFile = JSON.stringify(this.configs[this.configLang], null, 4); this.configFileStructure?.zip.file(fileName, formattedConfigFile); + console.log('new configFileStructure'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); // Upload the ZIP file. this.configFileStructure?.zip.generateAsync({ type: 'blob' }).then((content: Blob) => { @@ -986,7 +1031,9 @@ export default class MetadataEditorV extends Vue { this.loadExisting = true; // if editExisting was false, we can now set it to true if (import.meta.env.VITE_APP_CURR_ENV) { + console.log('test1'); if (responseData.new) { + console.log('test2'); axios .post(import.meta.env.VITE_APP_NET_API_URL + '/api/user/register', { uuid: this.uuid, @@ -1016,6 +1063,7 @@ export default class MetadataEditorV extends Vue { }) .catch((error: any) => console.log(error.response || error)); } else { + console.log('test3'); formData.append('uuid', this.uuid); formData.append('titleEn', this.configs['en']?.title ?? ''); formData.append('titleFr', this.configs['fr']?.title ?? ''); @@ -1032,6 +1080,7 @@ export default class MetadataEditorV extends Vue { }, 500); }); } + console.log('test4'); fetch(this.apiUrl + `/retrieveMessages`) .then((res: any) => { @@ -1046,6 +1095,7 @@ export default class MetadataEditorV extends Vue { }) .catch((error: any) => console.log(error.response || error)); } else { + console.log('test5'); // padding to prevent save button from being clicked rapidly setTimeout(() => { this.saving = false; @@ -1093,11 +1143,9 @@ export default class MetadataEditorV extends Vue { if (!this.metadata.logoName) { config.introSlide.logo.src = ''; } else if (!this.metadata.logoName.includes('http')) { - config.introSlide.logo.src = `${this.uuid}/assets/${this.configLang}/${this.logoImage?.name}`; - this.configFileStructure?.assets[this.configLang].file( - this.logoImage?.name as string, - this.logoImage as File - ); + // logo should now be in shared folder + config.introSlide.logo.src = `${this.uuid}/assets/shared/${this.logoImage?.name}`; + this.configFileStructure?.assets['shared'].file(this.logoImage?.name as string, this.logoImage as File); } else { config.introSlide.logo.src = this.metadata.logoName; } @@ -1273,6 +1321,7 @@ export default class MetadataEditorV extends Vue { Message.error('Missing required field: UUID'); this.error = true; } else { + console.log('TIME to generate a new config'); this.generateNewConfig(); } } diff --git a/src/components/slide-editor.vue b/src/components/slide-editor.vue index 02cffde0..547d1da7 100644 --- a/src/components/slide-editor.vue +++ b/src/components/slide-editor.vue @@ -488,6 +488,7 @@ export default class SlideEditorV extends Vue { children: [] } }; + console.log(this.configFileStructure); // Before swapping panel type, update sources from the to-be-deleted config. this.currentSlide.panel.forEach((panel: BasePanel) => this.removeSourceCounts(panel)); @@ -504,12 +505,20 @@ export default class SlideEditorV extends Vue { } removeSourceCounts(panel: BasePanel): void { + console.log(''); + console.log('removeSourceCounts()'); + console.log('configFileStructure before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); // The provided panel is being removed. Update source counts accordingly. switch (panel.type) { case 'map': { const mapPanel = panel as MapPanel; + console.log('mapPanel being removed'); + console.log(mapPanel); this.sourceCounts[mapPanel.config] -= 1; if (this.sourceCounts[mapPanel.config] === 0) { + console.log('map being removed from configFileStructure'); + console.log(`${mapPanel.config.substring(mapPanel.config.indexOf('/') + 1)}`); this.configFileStructure.zip.remove( `${mapPanel.config.substring(mapPanel.config.indexOf('/') + 1)}` ); @@ -518,9 +527,15 @@ export default class SlideEditorV extends Vue { } case 'image': { + // as long as assets are uploaded/removed from the appropriate folders (either shared or current lang), + // then this should work fine as is const imagePanel = panel as ImagePanel; + console.log('image panel being removed'); + console.log(imagePanel); this.sourceCounts[imagePanel.src] -= 1; if (this.sourceCounts[imagePanel.src] === 0) { + console.log('image being removed from configFileStructure (source count has hit 0)'); + console.log(`${imagePanel.src.substring(imagePanel.src.indexOf('/') + 1)}`); this.configFileStructure.zip.remove(`${imagePanel.src.substring(imagePanel.src.indexOf('/') + 1)}`); } @@ -529,8 +544,12 @@ export default class SlideEditorV extends Vue { case 'chart': { const chartPanel = panel as ChartPanel; + console.log('chart panel being removed'); + console.log(chartPanel); this.sourceCounts[chartPanel.src] -= 1; if (this.sourceCounts[chartPanel.src] === 0) { + console.log('chart being removed from configFileStructure (source count has hit 0)'); + console.log(`${chartPanel.src.substring(chartPanel.src.indexOf('/') + 1)}`); this.configFileStructure.zip.remove(`${chartPanel.src.substring(chartPanel.src.indexOf('/') + 1)}`); } @@ -539,6 +558,8 @@ export default class SlideEditorV extends Vue { case 'slideshow': { const slideshowPanel = panel as SlideshowPanel; + console.log('slideshow panel being removed (source count has hit 0)'); + console.log(slideshowPanel); slideshowPanel.items.forEach((item: TextPanel | ImagePanel | MapPanel | ChartPanel) => { this.removeSourceCounts(item); }); @@ -547,9 +568,13 @@ export default class SlideEditorV extends Vue { case 'video': { const videoPanel = panel as VideoPanel; + console.log('video panel being removed'); + console.log(videoPanel); if (videoPanel.videoType === 'local') { this.sourceCounts[videoPanel.src] -= 1; if (this.sourceCounts[videoPanel.src] === 0) { + console.log('local video being removed from configFileStructure'); + console.log(`${videoPanel.src.substring(videoPanel.src.indexOf('/') + 1)}`); this.configFileStructure.zip.remove( `${videoPanel.src.substring(videoPanel.src.indexOf('/') + 1)}` ); @@ -560,6 +585,8 @@ export default class SlideEditorV extends Vue { case 'dynamic': { const dynamicPanel = panel as DynamicPanel; + console.log('dynamic panel being removed'); + console.log(dynamicPanel); dynamicPanel.children.forEach((subPanel: DynamicChildItem) => { this.removeSourceCounts(subPanel.panel); }); @@ -570,6 +597,9 @@ export default class SlideEditorV extends Vue { break; } } + + console.log('configFileStructure after'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); } saveChanges(): void { @@ -578,7 +608,14 @@ export default class SlideEditorV extends Vue { typeof (this.$refs.editor as ImageEditorV | ChartEditorV | VideoEditorV | CustomEditorV).saveChanges === 'function' ) { + // console.log('slideeditor.vue - saving changes after opening advanced editor '); + // console.log('editor for whcih we also save changes for'); + // console.log(this.$refs.editor); + // console.log('slide config before saving image panel slide'); + // console.log(JSON.parse(JSON.stringify(this.currentSlide))); (this.$refs.editor as ImageEditorV | ChartEditorV | VideoEditorV | CustomEditorV).saveChanges(); + // console.log('slide config after saving image panel slide'); + // console.log(JSON.parse(JSON.stringify(this.currentSlide))); } } diff --git a/src/components/video-editor.vue b/src/components/video-editor.vue index 4066f0d6..12063868 100644 --- a/src/components/video-editor.vue +++ b/src/components/video-editor.vue @@ -133,9 +133,23 @@ export default class VideoEditorV extends Vue { const assetSrc = `${this.panel.src.substring(this.panel.src.indexOf('/') + 1)}`; const filename = this.panel.src.replace(/^.*[\\/]/, ''); - const assetFile = this.configFileStructure.zip.file(assetSrc); + console.log('video src'); + console.log(assetSrc); + console.log('video file name'); + console.log(filename); + + // Check if the asset has been removed from the current langs assets folder and added to the + // shared assets folder. If so, obtain the image from the shared assets folder to insert into + // imagePreviews. In this case, the src of this asset within the panel will still be "out of date" + // (i.e. will still refer to the current langs assets folder), but this will be resolved once + // saveChanges() is executed + const assetFile = + this.configFileStructure.zip.file(assetSrc) ?? + this.configFileStructure.assets['shared'].file(filename); if (assetFile) { this.videoPreviewPromise = assetFile.async('blob').then((res: Blob) => { + console.log('asset file found, will be inserted into videoPreview'); + console.log(assetFile); return { ...this.panel, id: filename ? filename : this.panel.src, @@ -147,6 +161,8 @@ export default class VideoEditorV extends Vue { this.videoPreviewPromise?.then((res) => { this.videoPreview = res; this.videoPreviewLoading = false; + console.log('videoPreview after mounting video panel'); + console.log(this.imagePreviews); }); this.slideshowCaption = this.panel.caption as string; @@ -169,16 +185,59 @@ export default class VideoEditorV extends Vue { // adds an uploaded file that is either a: video, transcript or captions addUploadedFile(file: File, type: string): void { - const uploadSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${file.name}`; - this.configFileStructure.assets[this.lang].file(file.name, file); + let uploadSource = `${this.configFileStructure.uuid}/assets/shared/${file.name}`; + const oppositeLang = this.lang === 'en' ? 'fr' : 'en'; + let oppositeSourceCount = 0; + console.log(' '); + console.log('addUploadedFile()'); + console.log('this.videoPreview before'); + console.log(JSON.parse(JSON.stringify(this.videoPreview))); + console.log('this.configFileStructure before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('file name of dropped image'); + console.log(file.name); + console.log('file in opposite langs asets folder?'); + console.log(!!this.configFileStructure.assets[oppositeLang].file(file.name)); + console.log('file in current langs asets folder?'); + console.log(!!this.configFileStructure.assets[this.lang].file(file.name)); + + // If the file is already in the opposite langs assets folder, we should move it to the shared + // assets folder, rather than duplicating it within the current lang's assets folder. Otherwise, + // if the file is already in the shared assets folder, then nothing needs to be done, since the + // panel's config will simply access the file from the shared assets folder. If the asset is neither + // in the shared nor the opposite langs assets folder, the file should be uploaded to the current + // langs assets folder + if (this.configFileStructure.assets[oppositeLang].file(file.name)) { + const oppositeFileSource = `${this.configFileStructure.uuid}/assets/${oppositeLang}/${file.name}`; + console.log('source count of file in opposite langs assets folder'); + console.log(this.sourceCounts[oppositeFileSource]); + oppositeSourceCount = this.sourceCounts[oppositeFileSource] ?? 0; + this.sourceCounts[oppositeFileSource] = 0; + this.configFileStructure.assets[oppositeLang].remove(file.name); + this.configFileStructure.assets['shared'].file(file.name, file); + } else if (!this.configFileStructure.assets['shared'].file(file.name)) { + uploadSource = `${this.configFileStructure.uuid}/assets/${this.lang}/${file.name}`; + this.configFileStructure.assets[this.lang].file(file.name, file); + } + + console.log('upload source of video file'); + console.log(uploadSource); + console.log('source count removed from asset in opposite langs assets folder'); + console.log(oppositeSourceCount); if (this.sourceCounts[uploadSource]) { - this.sourceCounts[uploadSource] += 1; + console.log('previous source count of uploaded video'); + console.log(this.sourceCounts[uploadSource]); + this.sourceCounts[uploadSource] += 1 + oppositeSourceCount; } else { - this.sourceCounts[uploadSource] = 1; + this.sourceCounts[uploadSource] = 1 + oppositeSourceCount; } + console.log('source count of uploaded video'); + console.log(this.sourceCounts[uploadSource]); // check if source file is creating a new video or uploading captions/transcript for current video const fileSrc = URL.createObjectURL(file); + console.log('fileSrc'); + console.log(fileSrc); if (type === 'src') { this.videoPreview = { id: file.name, @@ -192,6 +251,10 @@ export default class VideoEditorV extends Vue { } this.edited = true; this.$emit('slide-edit'); + console.log('this.videoPreview after'); + console.log(JSON.parse(JSON.stringify(this.videoPreview))); + console.log('this.configFileStructure after'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); } onFileChange(e: Event): void { @@ -258,32 +321,111 @@ export default class VideoEditorV extends Vue { } deleteVideo(): void { + console.log(' '); + console.log('deleteVideo()'); + console.log('configFileStructure before'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('videoPreview before'); + console.log(JSON.parse(JSON.stringify(this.videoPreview))); + + if (this.videoPreview.videoType === 'local') { + const videoSource = `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(this.videoPreview.id) ? 'shared' : this.lang + }/${this.videoPreview.id}`; + console.log('source of video being deleted'); + console.log(videoSource); + console.log('name of video file being deleted'); + console.log(this.videoPreview.id); + + this.sourceCounts[videoSource] -= 1; + if (this.sourceCounts[videoSource] === 0) { + console.log('video being deleted from configFileStructure (source count hit 0)'); + if (this.configFileStructure.assets['shared'].file(this.videoPreview.id)) { + console.log('file being deleted exists in the shared folder, delete from there'); + } else { + console.log('file being deleted should be deleted from current langs folder'); + } + this.configFileStructure.assets[ + this.configFileStructure.assets['shared'].file(this.videoPreview.id) ? 'shared' : this.lang + ].remove(this.videoPreview.id); + URL.revokeObjectURL(this.videoPreview.src); + } else { + console.log(`source count of video is still: ${this.sourceCounts[videoSource]} `); + } + } (this.$refs.videoFileInput as HTMLInputElement).value = ''; this.videoPreview = {}; this.onVideoEdited(); + console.log('configFileStructure after'); + console.log(JSON.parse(JSON.stringify(this.configFileStructure))); + console.log('videoPreview after'); + console.log(JSON.parse(JSON.stringify(this.videoPreview))); + } + + assetOutOfDate() { + if (this.panel.videoType === 'local' && this.panel.src) { + console.log(' '); + console.log('assetOutOfDate()'); + console.log('local asset of video panel'); + console.log(this.panel.src); + let assetSrc = this.panel.src.split('/'); + const assetFolder = assetSrc[2]; + const assetId = assetSrc[3]; + + // An assets src value is "out of date" if the asset exists in the shared asset folder, but still + // references the curr lang's assets folder + if (this.configFileStructure.assets['shared'].file(assetId) && assetFolder !== 'shared') { + console.log('asset is out of date'); + return true; + } + } + return false; } saveChanges(): void { - if (this.edited && this.videoPreview) { + console.log(' '); + console.log('saveChanges()'); + console.log('video panel being saved'); + console.log('video panel before'); + console.log(JSON.parse(JSON.stringify(this.panel))); + console.log('videoPreview'); + console.log(this.videoPreview); + if ((this.edited || this.assetOutOfDate()) && this.videoPreview) { // save all changes to panel config (cannot directly set to avoid prop mutate) this.panel.title = this.videoPreview.title; this.panel.videoType = this.videoPreview.videoType; + + if (this.configFileStructure.assets['shared'].file(this.videoPreview.id)) { + console.log('video preview will be put into shared folder'); + } else { + console.log('video preview will be put into current langs folde'); + } + if (this.panel.videoType === 'local') { + console.log('video preview src'); + console.log( + `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(this.videoPreview.id) ? 'shared' : this.lang + }/${this.videoPreview.id}` + ); + } + this.panel.src = this.videoPreview.videoType === 'local' - ? `${this.configFileStructure.uuid}/assets/${this.lang}/${this.videoPreview.id}` + ? `${this.configFileStructure.uuid}/assets/${ + this.configFileStructure.assets['shared'].file(this.videoPreview.id) ? 'shared' : this.lang + }/${this.videoPreview.id}` : this.videoPreview.src; this.panel.caption = this.videoPreview.caption ? this.videoPreview.caption : ''; this.panel.transcript = this.videoPreview.transcript ? this.videoPreview.transcript : ''; } this.edited = false; + console.log('video panel after'); + console.log(JSON.parse(JSON.stringify(this.panel))); } onVideoEdited(): void { this.edited = true; - this.$emit( - 'slide-edit', - (this.videoPreview?.videoType || this.videoPreview?.title?.length) ? true : false - ); + this.$emit('slide-edit', this.videoPreview?.videoType || this.videoPreview?.title?.length ? true : false); } }