From 51b0a3e9ffa2f2952c3078327cd6c3ef9056c5de Mon Sep 17 00:00:00 2001 From: IshavSohal Date: Fri, 29 Nov 2024 15:12:15 -0500 Subject: [PATCH] add modified property to dynamic panel schema, fix svg rendering --- StorylinesSchema.json | 4 ++ package-lock.json | 4 +- package.json | 2 +- public/StorylinesSlideSchema.json | 4 ++ server/index.js | 82 +++++++++++++++--------------- src/components/editor.vue | 2 +- src/components/image-editor.vue | 35 +++++++++---- src/components/metadata-editor.vue | 49 +++++++++++++----- 8 files changed, 116 insertions(+), 66 deletions(-) diff --git a/StorylinesSchema.json b/StorylinesSchema.json index b1b4a37b..64689c90 100644 --- a/StorylinesSchema.json +++ b/StorylinesSchema.json @@ -103,6 +103,10 @@ "type": { "type": "string", "enum": ["dynamic"] + }, + "modified": { + "type": "boolean", + "description": "An optional tag that specifies whether the panel has been modified from its default configuration" } }, "required": ["content", "type", "children", "title"] diff --git a/package-lock.json b/package-lock.json index 586bf9e1..c239e88a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8073,8 +8073,8 @@ }, "node_modules/ramp-storylines_demo-scenarios-pcar": { "version": "3.2.8", - "resolved": "https://registry.npmjs.org/ramp-storylines_demo-scenarios-pcar/-/ramp-storylines_demo-scenarios-pcar-3.2.8.tgz", - "integrity": "sha512-GChEZiJQKWdzciSj0P5uSpzvayfiJ7AmClDF5y+gv/RcM9dBPFJkVPW0egSmFcf1pHbaR9Ln9sElkL/ab+Sg0A==", + "resolved": "git+ssh://git@github.com/IshavSohal/story-ramp.git#ada3693513be60415d395eb5af63e3af026f12a1", + "integrity": "sha512-biu181GtT8s8hYJy35HKj9rc9H55dEdiqzLruxJLWcZ63uubi8oARyQOmNR/QG7rdeHAzXQVl4rKA63vPJdszA==", "license": "MIT", "dependencies": { "@rollup/plugin-dsv": "^3.0.4", diff --git a/package.json b/package.json index c16e8598..6e8d4c33 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "nouislider": "^15.5.0", "ramp-config-editor_editeur-config-pcar": "^3.6.0", "ramp-pcar": "^4.8.0", - "ramp-storylines_demo-scenarios-pcar": "^3.2.8", + "ramp-storylines_demo-scenarios-pcar":"^3.2.8", "throttle-debounce": "^5.0.0", "url": "^0.11.3", "uuid": "^9.0.0", diff --git a/public/StorylinesSlideSchema.json b/public/StorylinesSlideSchema.json index fa41ece8..4c230755 100644 --- a/public/StorylinesSlideSchema.json +++ b/public/StorylinesSlideSchema.json @@ -107,6 +107,10 @@ "customStyles": { "type": "string", "description": "Additional CSS styles to apply to the panel." + }, + "modified": { + "type": "boolean", + "description": "An optional tag that specifies whether the panel has been modified from its default configuration" } }, "additionalProperties": false, diff --git a/server/index.js b/server/index.js index 399ae2ec..41fd8d9d 100644 --- a/server/index.js +++ b/server/index.js @@ -45,7 +45,7 @@ app.use(cors()); // POST requests made to /upload will be handled here. app.route(ROUTE_PREFIX + '/upload').post(function (req, res, next) { // TODO: Putting this on the header isn't great. The body has the zipped folder. And usernames in the URL doesn't look great either. Maybe improve this somehow. - const user = req.headers.user + const user = req.headers.user; if (!user) { // Send back error if the user uploading the storyline was not provided. responseMessages.push({ @@ -123,10 +123,9 @@ app.route(ROUTE_PREFIX + '/upload').post(function (req, res, next) { // Initialize a new git repo if this is a new storyline. // Otherwise, simply create a new commit with the zipped folder. if (!newStorylines) { - await commitToRepo(fileName, user, false) - } - else { - await initGitRepo(fileName, user) + await commitToRepo(fileName, user, false); + } else { + await initGitRepo(fileName, user); } // Finally, delete the uploaded zip file. safeRM(secureFilename, UPLOAD_PATH); @@ -143,10 +142,10 @@ app.route(ROUTE_PREFIX + '/upload').post(function (req, res, next) { // GET requests made to /retrieve/ID/commitHash will be handled here. // Calling this with commitHash as "latest" simply fetches the product as normal. app.route(ROUTE_PREFIX + '/retrieve/:id/:hash').get(function (req, res, next) { - // This user is only needed for backwards compatibility. + // This user is only needed for backwards compatibility. // If we have an existing storylines product that is not a git repo, we need to initialize a git repo // and make an initial commit for it, but we need the user for the commit. - const user = req.headers.user + const user = req.headers.user; if (!user) { // Send back error if the user uploading the storyline was not provided. responseMessages.push({ @@ -161,7 +160,7 @@ app.route(ROUTE_PREFIX + '/retrieve/:id/:hash').get(function (req, res, next) { var archive = archiver('zip'); const PRODUCT_PATH = `${TARGET_PATH}/${req.params.id}`; const uploadLocation = `${UPLOAD_PATH}/${req.params.id}-outgoing.zip`; - const commitHash = req.params.hash + const commitHash = req.params.hash; // Check if the product exists. if ( @@ -169,27 +168,30 @@ app.route(ROUTE_PREFIX + '/retrieve/:id/:hash').get(function (req, res, next) { if (!error) { // Backwards compatibility. If the existing product is not a git repo i.e. it existed before git version control, // we make a git repo for it before returning the version history. Otherwise, the code below will explode. - await initGitRepo(PRODUCT_PATH, user) + await initGitRepo(PRODUCT_PATH, user); const git = simpleGit(PRODUCT_PATH); // Get the current branch. We do it this way instead of assuming its "main" in case someone has it set to master. - const branches = await git.branchLocal() - const currBranch = branches.current + const branches = await git.branchLocal(); + const currBranch = branches.current; if (commitHash !== 'latest') { - // If the user does not ask for the latest commit, we checkout a new branch at the point of the requested commit, + // If the user does not ask for the latest commit, we checkout a new branch at the point of the requested commit, // and then proceed with getting the zipped folder below. try { // First, we check if the requested commit exists. // NOTE: When calling from frontend, the catch block should never run. const commitExists = await git.catFile(['-t', commitHash]); if (commitExists !== 'commit\n') { - throw new Error() + throw new Error(); } } catch (error) { responseMessages.push({ type: 'INFO', message: `Access attempt to version ${commitHash} of product ${req.params.id} failed, does not exist.` }); - logger('INFO', `Access attempt to version ${commitHash} of product ${req.params.id} failed, does not exist.`); + logger( + 'INFO', + `Access attempt to version ${commitHash} of product ${req.params.id} failed, does not exist.` + ); res.status(404).send({ status: 'Not Found' }); return; } @@ -219,10 +221,9 @@ app.route(ROUTE_PREFIX + '/retrieve/:id/:hash').get(function (req, res, next) { // Since the user has not asked for the latest commit, we need to clean up. // Go back to the main branch and delete the newly created branch. await git.checkout(currBranch); - await git.deleteLocalBranch(`version-${commitHash}`) + await git.deleteLocalBranch(`version-${commitHash}`); } }); - }); // Write the product data to the ZIP file. @@ -235,7 +236,6 @@ app.route(ROUTE_PREFIX + '/retrieve/:id/:hash').get(function (req, res, next) { responseMessages.push({ type: 'INFO', message: `Successfully loaded product ${req.params.id}` }); logger('INFO', `Successfully loaded product ${req.params.id}`); - } else { responseMessages.push({ type: 'INFO', @@ -291,10 +291,10 @@ app.route(ROUTE_PREFIX + '/retrieve/:id/:lang').get(function (req, res) { }); app.route(ROUTE_PREFIX + '/history/:id').get(function (req, res, next) { - // This user is only needed for backwards compatibility. + // This user is only needed for backwards compatibility. // If we have an existing storylines product that is not a git repo, we need to initialize a git repo // and make an initial commit for it, but we need the user for the commit. - const user = req.headers.user + const user = req.headers.user; if (!user) { // Send back error if the user uploading the storyline was not provided. responseMessages.push({ @@ -316,21 +316,21 @@ app.route(ROUTE_PREFIX + '/history/:id').get(function (req, res, next) { }); logger('INFO', `Access attempt to versions of ${req.params.id} failed, does not exist.`); res.status(404).send({ status: 'Not Found' }); - } - else { + } else { // Backwards compatibility. If the existing product is not a git repo i.e. it existed before git version control, // we make a git repo for it before returning the version history. Otherwise, the code below will explode. - await initGitRepo(PRODUCT_PATH, user) + await initGitRepo(PRODUCT_PATH, user); // Get version history for this product via git log command const git = simpleGit(PRODUCT_PATH); - const log = await git.log() + const log = await git.log(); // TODO: Remove the 10 version limit once pagination is implemented - const history = log.all.slice(0, 10).map((commit) => ({hash: commit.hash, created: commit.date, storylineUUID: req.params.id})) - res.json(history) + const history = log.all + .slice(0, 10) + .map((commit) => ({ hash: commit.hash, created: commit.date, storylineUUID: req.params.id })); + res.json(history); } - }) - -}) + }); +}); // GET reuests made to /retrieveMessages will recieve all the responseMessages currently queued. app.route(ROUTE_PREFIX + '/retrieveMessages').get(function (req, res) { @@ -341,7 +341,7 @@ app.route(ROUTE_PREFIX + '/retrieveMessages').get(function (req, res) { /* * Initializes a git repo at the requested path, if one does not already exist. * Creates an initial commit with any currently existing files in the directory. - * + * * @param {string} path the path of the git repo * @param {string} username the name of the user initializing the repo */ @@ -357,16 +357,16 @@ async function initGitRepo(path, username) { // Product directory is in a git repo but not top-level, we are working locally. repoExists = false; } - } catch(error) { + } catch (error) { // Product directory is not a git repo nor is it within a git repo. repoExists = false; } if (!repoExists) { - // Repo does not exist for the storyline product. + // Repo does not exist for the storyline product. // Initialize a git repo and add an initial commit with all existing files. - await git.init() - await commitToRepo(path, username, true) + await git.init(); + await commitToRepo(path, username, true); } } @@ -378,20 +378,22 @@ async function initGitRepo(path, username) { * @param {boolean} initial specifies whether this is the initial commit */ async function commitToRepo(path, username, initial) { - const date = moment().format('YYYY-MM-DD') - const time = moment().format('hh:mm:ss a') + const date = moment().format('YYYY-MM-DD'); + const time = moment().format('hh:mm:ss a'); // Initialize git const git = simpleGit(path); - let versionNumber = 1 + let versionNumber = 1; if (!initial) { // Compute version number for storyline if this is not the initial commit. - const log = await git.log() - const lastMessage = log.latest.message - versionNumber = lastMessage.split(' ')[3] + const log = await git.log(); + const lastMessage = log.latest.message; + versionNumber = lastMessage.split(' ')[3]; versionNumber = Number(versionNumber) + 1; } // Commit the files for this storyline to its repo. - await git.add('./*').commit(`Add product version ${versionNumber} on ${date} at ${time}`, {'--author': `"${username} <>"`}) + await git + .add('./*') + .commit(`Add product version ${versionNumber} on ${date} at ${time}`, { '--author': `"${username} <>"` }); } /* diff --git a/src/components/editor.vue b/src/components/editor.vue index f7b0bb4a..4c7b5348 100644 --- a/src/components/editor.vue +++ b/src/components/editor.vue @@ -335,8 +335,8 @@ import { ConfigFileStructure, HelpSection, MetadataContent, - Slide, MultiLanguageSlide, + Slide, SourceCounts, StoryRampConfig, TextPanel diff --git a/src/components/image-editor.vue b/src/components/image-editor.vue index 3cc7f00e..1ea129b8 100644 --- a/src/components/image-editor.vue +++ b/src/components/image-editor.vue @@ -155,16 +155,33 @@ export default class ImageEditorV extends Vue { const assetSrc = `${image.src.substring(image.src.indexOf('/') + 1)}`; const filename = image.src.replace(/^.*[\\/]/, ''); const assetFile = this.configFileStructure.zip.file(assetSrc); + const assetType = assetSrc.split('.').at(-1); + if (assetFile) { - this.imagePreviewPromises.push( - assetFile.async('blob').then((res: Blob) => { - return { - ...image, - id: filename ? filename : image.src, - src: URL.createObjectURL(res) - } as ImageFile; - }) - ); + if (assetType != 'svg') { + this.imagePreviewPromises.push( + assetFile.async('blob').then((res: Blob) => { + return { + ...image, + id: filename ? filename : image.src, + src: URL.createObjectURL(res) + } as ImageFile; + }) + ); + } else { + this.imagePreviewPromises.push( + assetFile.async('text').then((res) => { + const imageFile = new File([res], filename, { + type: 'image/svg+xml' + }); + return { + ...image, + id: filename ? filename : image.src, + src: URL.createObjectURL(imageFile) + } as ImageFile; + }) + ); + } } }); diff --git a/src/components/metadata-editor.vue b/src/components/metadata-editor.vue index 9f1fa2dd..b6fd602d 100644 --- a/src/components/metadata-editor.vue +++ b/src/components/metadata-editor.vue @@ -563,9 +563,9 @@ import { ImagePanel, MapPanel, MetadataContent, + MultiLanguageSlide, PanelType, Slide, - MultiLanguageSlide, SlideshowPanel, SourceCounts, StoryRampConfig, @@ -764,12 +764,24 @@ export default class MetadataEditorV extends Vue { if (logo) { const logoFile = this.configFileStructure?.zip.file(logoSrc); + const logoType = logoSrc.split('.').at(-1); if (logoFile) { - logoFile.async('blob').then((img: Blob) => { - this.logoImage = new File([img], this.metadata.logoName); - this.metadata.logoPreview = URL.createObjectURL(img); - this.loadStatus = 'loaded'; - }); + if (logoType !== 'svg') { + logoFile.async('blob').then((img: Blob) => { + this.logoImage = new File([img], this.metadata.logoName); + this.metadata.logoPreview = URL.createObjectURL(img); + this.loadStatus = 'loaded'; + }); + } else { + logoFile.async('text').then((img) => { + const logoImageFile = new File([img], this.metadata.logoName, { + type: 'image/svg+xml' + }); + this.logoImage = logoImageFile; + this.metadata.logoPreview = URL.createObjectURL(logoImageFile); + this.loadStatus = 'loaded'; + }); + } } else { // Fill in the field with this value whether it exists or not. this.metadata.logoName = logo; @@ -1269,19 +1281,30 @@ export default class MetadataEditorV extends Vue { if (logo) { // Set the alt text for the logo. this.metadata.logoAltText = config.introSlide.logo?.altText ? config.introSlide.logo.altText : ''; + this.metadata.logoName = logo.split('/').at(-1); // Fetch the logo from the folder (if it exists). const logoSrc = `${logo.substring(logo.indexOf('/') + 1)}`; const logoName = `${logo.split('/')[logo.split('/').length - 1]}`; const logoFile = this.configFileStructure?.zip.file(logoSrc); - + const logoType = logoSrc.split('.').at(-1); if (logoFile) { - logoFile.async('blob').then((img: Blob) => { - this.logoImage = new File([img], logoName); - this.metadata.logoPreview = URL.createObjectURL(img); - this.metadata.logoName = logoName; - this.loadStatus = 'loaded'; - }); + if (logoType !== 'svg') { + logoFile.async('blob').then((img: Blob) => { + this.logoImage = new File([img], this.metadata.logoName); + this.metadata.logoPreview = URL.createObjectURL(img); + this.loadStatus = 'loaded'; + }); + } else { + logoFile.async('text').then((img) => { + const logoImageFile = new File([img], this.metadata.logoName, { + type: 'image/svg+xml' + }); + this.logoImage = logoImageFile; + this.metadata.logoPreview = URL.createObjectURL(logoImageFile); + this.loadStatus = 'loaded'; + }); + } } else { // Fill in the field with this value whether it exists or not. this.metadata.logoName = logo;