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

story can be downloaded as html-presentation #96

Merged
merged 2 commits into from
Jun 23, 2021
Merged
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
10 changes: 10 additions & 0 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ module.exports = function(grunt) {
}
]
},
dot: {
files: [
{
expand: true,
cwd: 'node_modules/dot',
src: ['**/*.min.js'],
dest: 'dist/dependencies'
}
]
},
app: {
files: [
{
Expand Down
13 changes: 13 additions & 0 deletions app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ import {
downloadSVG,
setEncoded
} from './domain-story-modeler/features/export/svgDownload';
import {
initStoryDownload,
downloadStory
} from './domain-story-modeler/features/export/storyDownload';
import { downloadPNG } from './domain-story-modeler/features/export/pngDownload';
import {
loadPersistedDST,
Expand Down Expand Up @@ -166,6 +170,7 @@ let modal = document.getElementById('modal'),
),
pngSaveButton = document.getElementById('buttonPNG'),
svgSaveButton = document.getElementById('buttonSVG'),
storySaveButton = document.getElementById('buttonDownloadStory'),
wpsLogoButton = document.getElementById('closeWPSLogoInfo'),
dstLogoButton = document.getElementById('closeDSTLogoInfo'),
exportConfigurationButton = document.getElementById(
Expand Down Expand Up @@ -221,6 +226,7 @@ function initialize(
modeler.on('commandStack.changed', exportArtifacts);

initReplay(canvas, selection);
initStoryDownload(canvas, selection, modeler);
initElementRegistry(elementRegistry);
initImports(elementRegistry, version, modeler, eventBus, saveSVG);

Expand Down Expand Up @@ -538,6 +544,13 @@ pngSaveButton.addEventListener('click', function() {
closeImageDownloadDialog();
});

storySaveButton.addEventListener('click', function() {
const filename =
title.innerText + '_' + new Date().toISOString().slice(0, 10);
downloadStory(filename);
closeImageDownloadDialog();
});

incompleteStoryDialogButtonCancel.addEventListener('click', function() {
modal.style.display = 'none';
incompleteStoryDialog.style.display = 'none';
Expand Down
261 changes: 261 additions & 0 deletions app/domain-story-modeler/features/export/storyDownload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,261 @@
'use strict';

import { sanitizeForDesktop } from '../../util/Sanitizer';
import { GROUP } from '../../language/elementTypes';
import {
getActivitesFromActors,
wasInitialized,
} from '../../language/canvasElementRegistry';
import { traceActivities } from '../replay/initializeReplay';
import doT from 'dot';
import { editMode, presentationMode, showCurrentStep } from '../replay/replayFunctions';

let canvas;
let selection;

let multiplexSecret;
let multiplexId;

let replayOn = false;
let currentStep = 0;
let replaySteps = [];
let initialViewbox;

let errorStep = 0;

let dsModeler;

let modal = document.getElementById('modal');
let incompleteStoryDialog = document.getElementById('incompleteStoryInfo');

export function initStoryDownload(inCanvas, inSelection, modeler) {
canvas = inCanvas;
selection = inSelection;
dsModeler = modeler;

// cors.bridged.cc is a CORS ANYWHERE server. For details: https://blog.bridged.xyz/cors-anywhere-for-everyone-free-reliable-cors-proxy-service-73507192714e
fetch('https://cors.bridged.cc/https://reveal-multiplex.glitch.me/token', { headers: { 'x-requested-with':'XMLHttpRequest' } }).then(res => res.json()).then((out) => {
multiplexSecret=out.secret;
multiplexId=out.socketId;
}).catch(err => console.error(err));
}

export async function downloadStory(filename) {
var svgData = [];
currentStep = 0;

// export all sentences of domain story
startReplay();
try {
const result = await dsModeler.saveSVG({ });
svgData.push({ content: createSVGData(result.svg), transition: 'slide' });
} catch (err) {
alert('There was an error exporting the SVG.\n' + err);
}
while (currentStep < replaySteps.length - 1) {
nextStep();
try {
const result = await dsModeler.saveSVG({ });
svgData.push({ content: createSVGData(result.svg), transition: 'slide' });
} catch (err) {
alert('There was an error exporting the SVG.\n' + err);
}
}
stopReplay();

// create download for presentation
let revealjsTemplate = document.getElementById('revealjs-template');
var dots = doT.template(revealjsTemplate.innerHTML);
var revealjsData = { };
revealjsData.script = 'script'; // don't change this!!
revealjsData.title = document.getElementById('title').innerHTML;
revealjsData.description = document.getElementById('infoText').innerHTML;
revealjsData.sentences = svgData;
revealjsData.multiplexSecret = multiplexSecret;
revealjsData.multiplexId = multiplexId;
let element;
element = document.createElement('a');
element.setAttribute(
'href',
'data:text/html;charset=UTF-8,' + dots(revealjsData)
);
element.setAttribute('download', sanitizeForDesktop(filename) + '.html');
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}

function startReplay() {
if (wasInitialized()) {
initialViewbox = canvas.viewbox();
let activities = getActivitesFromActors();

if (!replayOn && activities.length > 0) {
replaySteps = traceActivities(activities);

if (isStoryConsecutivelyNumbered(replaySteps)) {
replayOn = true;
presentationMode(selection);
currentStep = 0;
showCurrentStep(currentStep, replaySteps, canvas);
} else {
let errorText = '\nThe numbers: ';
for (let i = 0; i < replaySteps.length; i++) {
if (errorStep[i]) {
errorText += i + 1 + ',';
}
}
errorText = errorText.substring(0, errorText.length - 1);
errorText += ' are missing!';

let oldText = incompleteStoryDialog.getElementsByTagName('text');
if (oldText) {
for (let i = 0; i < oldText.length; i++) {
incompleteStoryDialog.removeChild(oldText[i]);
}
}

let text = document.createElement('text');
text.innerHTML =
' The activities in this Domain Story are not numbered consecutively.<br>' +
'Please fix the numbering in order to replay the story.<br>' +
errorText;
incompleteStoryDialog.appendChild(text);
incompleteStoryDialog.style.display = 'block';
modal.style.display = 'block';
}
}
}
}

function stopReplay() {
if (replayOn) {
editMode();

// show all canvas elements
let allObjects = [];
let groupObjects = [];
let canvasObjects = canvas._rootElement.children;
let i = 0;

for (i = 0; i < canvasObjects.length; i++) {
if (canvasObjects[i].type.includes(GROUP)) {
groupObjects.push(canvasObjects[i]);
} else {
allObjects.push(canvasObjects[i]);
}
}

i = groupObjects.length - 1;
while (groupObjects.length >= 1) {
let currentgroup = groupObjects.pop();
currentgroup.children.forEach(child => {
if (child.type.includes(GROUP)) {
groupObjects.push(child);
} else {
allObjects.push(child);
}
allObjects.push(currentgroup);
});
i = groupObjects.length - 1;
}
allObjects.forEach(element => {
let domObject = document.querySelector(
'[data-element-id=' + element.id + ']'
);
domObject.style.display = 'block';
});

replayOn = false;
currentStep = 0;
canvas.viewbox(initialViewbox);
}
}

function nextStep() {
if (replayOn) {
if (currentStep < replaySteps.length - 1) {
currentStep += 1;
showCurrentStep(currentStep, replaySteps, canvas);
}
}
}

function isStoryConsecutivelyNumbered(replaySteps) {
errorStep = [];
let complete = true;
for (let i = 0; i < replaySteps.length; i++) {
if (!replaySteps[i].activities[0]) {
complete = false;
errorStep[i] = true;
} else {
errorStep[i] = false;
}
}
return complete;
}

/*
---------------------------
SVG handling starts here
----------------------------
*/

function createSVGData(svg) {

let data = JSON.parse(JSON.stringify(svg));

// to ensure that the title and description are inside the SVG container and do not overlapp with any elements,
// we change the confines of the SVG viewbox
let viewBoxIndex = data.indexOf('width="');

let viewBox = viewBoxCoordinates(data);

let xLeft, xRight, yUp, yDown;
let bounds = '';
let splitViewBox = viewBox.split(/\s/);

xLeft = +splitViewBox[0];
yUp = +splitViewBox[1];
xRight = +splitViewBox[2];
yDown = +splitViewBox[3];

if (xRight < 300) {
xRight += 300;
}

bounds =
'width="100%"' +
' height="auto" ' +
' preserveAspectRatio="xMidYMid meet"' +
' viewBox="' +
xLeft +
' ' +
yUp +
' ' +
xRight +
' ' +
(yDown + 30);
let dataStart = data.substring(0, viewBoxIndex);
viewBoxIndex = data.indexOf('" version');
let dataEnd = data.substring(viewBoxIndex);
dataEnd.substring(viewBoxIndex);

data = dataStart + bounds + dataEnd;

let insertIndex = data.indexOf('</defs>');
if (insertIndex < 0) {
insertIndex = data.indexOf('version="1.2">') + 14;
} else {
insertIndex += 7;
}

return encodeURIComponent(data);
}

function viewBoxCoordinates(svg) {
const ViewBoxCoordinate = /width="([^"]+)"\s+height="([^"]+)"\s+viewBox="([^"]+)"/;
const match = svg.match(ViewBoxCoordinate);
return match[3];
}
Loading