Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add modified property to dynamic panel schema, Fix SVG rendering #481

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions StorylinesSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.9",
"throttle-debounce": "^5.0.0",
"url": "^0.11.3",
"uuid": "^9.0.0",
Expand Down
4 changes: 4 additions & 0 deletions public/StorylinesSlideSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
82 changes: 42 additions & 40 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -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);
Expand All @@ -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({
Expand All @@ -161,35 +160,38 @@ 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 (
fs.access(PRODUCT_PATH, async (error) => {
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;
}
Expand Down Expand Up @@ -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.
Expand All @@ -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',
Expand Down Expand Up @@ -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({
Expand All @@ -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) {
Expand All @@ -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
*/
Expand All @@ -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);
}
}

Expand All @@ -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} <>"` });
}

/*
Expand Down
2 changes: 1 addition & 1 deletion src/components/editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,8 @@ import {
ConfigFileStructure,
HelpSection,
MetadataContent,
Slide,
MultiLanguageSlide,
Slide,
SourceCounts,
StoryRampConfig,
TextPanel
Expand Down
35 changes: 26 additions & 9 deletions src/components/image-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
})
);
}
}
});
Expand Down
49 changes: 36 additions & 13 deletions src/components/metadata-editor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,9 @@ import {
ImagePanel,
MapPanel,
MetadataContent,
MultiLanguageSlide,
PanelType,
Slide,
MultiLanguageSlide,
SlideshowPanel,
SourceCounts,
StoryRampConfig,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Loading