diff --git a/backend/features/step_definitions/stepdefs.js b/backend/features/step_definitions/stepdefs.js index 06a1eaa02..841a4533d 100644 --- a/backend/features/step_definitions/stepdefs.js +++ b/backend/features/step_definitions/stepdefs.js @@ -755,6 +755,48 @@ Then( } ); +Then('So the picture {string} has the name {string}', async function checkPicture(picture, name) { + const world = this; + const identifiers = [`//picture[source[contains(@srcset, '${picture}')] or img[contains(@src, '${picture}') or contains(@alt, '${picture}') or @id='${picture}' or contains(@title, '${picture}')]]`, `//img[contains(@src, '${picture}') or contains(@alt, '${picture}') or @id='${picture}' or contains(@title, '${picture}')]`, `${picture}`]; + const promises = []; + for (const idString of identifiers) promises.push(driver.wait(until.elementLocated(By.xpath(idString)), searchTimeout, `Timed out after ${searchTimeout} ms`, 100)); + const domain = (await driver.getCurrentUrl()).split('/').slice(0, 3).join('/'); + let finSrc = ''; + await Promise.any(promises) + .then(async (elem) => { + if (await elem.getTagName() === 'picture') { + const childSourceElems = await elem.findElements(By.xpath('.//source')); + const elementWithSrcset = await childSourceElems.find(async (element) => { + const srcsetValue = await element.getAttribute('srcset'); + return srcsetValue && srcsetValue.includes(name); + }); + finSrc = await elementWithSrcset.getAttribute('srcset'); + } + const primSrc = await elem.getAttribute('src'); + const secSrc = await elem.getAttribute('srcset'); + if (!finSrc && primSrc && primSrc.includes(name)) finSrc = primSrc; + if (!finSrc && secSrc && secSrc.includes(name)) finSrc = secSrc; + finSrc = finSrc.split(' ').filter((substring) => substring.includes(name)); + }) + .catch(async (e) => { + await driver.takeScreenshot().then(async (buffer) => { + world.attach(buffer, 'image/png'); + }); + throw Error(e); + }); + await fetch(domain + finSrc, { method: 'HEAD' }) + .then((response) => { + if (!response.ok) throw Error(`Image ${finSrc} not Found`); + }) + .catch(async (e) => { + await driver.takeScreenshot().then(async (buffer) => { + world.attach(buffer, 'image/png'); + }); + throw Error(`Image availability check: could not reach image source ${domain + finSrc} `, e); + }); + await driver.sleep(100 + currentParameters.waitTime); +}); + // Search if a text isn't in html code Then('So I can\'t see the text: {string}', async function checkIfTextIsMissing(expectedText) { const world = this; diff --git a/backend/package-lock.json b/backend/package-lock.json index 791c919f9..780441dc0 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -4644,9 +4644,9 @@ } }, "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "engines": { "node": "*" } @@ -13353,9 +13353,9 @@ "dev": true }, "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==" }, "get-intrinsic": { "version": "1.1.1", diff --git a/backend/src/database/DbServices.js b/backend/src/database/DbServices.js index d62f931ec..7271dd8db 100644 --- a/backend/src/database/DbServices.js +++ b/backend/src/database/DbServices.js @@ -1168,6 +1168,9 @@ async function saveBlock(block) { async function updateBlock(blockId, updatedBlock) { try { + updatedBlock._id = ObjectId(updatedBlock._id); + updatedBlock.repositoryId = ObjectId(updatedBlock.repositoryId); + updatedBlock.owner = ObjectId(updatedBlock.owner); const db = dbConnection.getConnection(); updatedBlock._id = ObjectId(updatedBlock._id) updatedBlock.repositoryId = ObjectId(updatedBlock.repositoryId); @@ -1179,6 +1182,18 @@ async function updateBlock(blockId, updatedBlock) { } } +// get one Block by Id +async function getBlock(blockId) { + try { + const db = dbConnection.getConnection(); + return await db.collection(CustomBlocksCollection) + .findOne({ _id: ObjectId(blockId) }); + } catch (e) { + console.log(`ERROR in getBlock: ${e}`); + throw e; + } +} + // get all Blocks by Id returns Array with all existing CustomBlocks async function getBlocks(repoId) { try { @@ -1371,6 +1386,7 @@ module.exports = { getResetRequestByEmail, saveBlock, updateBlock, + getBlock, getBlocks, deleteBlock, getWorkgroup, diff --git a/backend/src/database/stepTypes.js b/backend/src/database/stepTypes.js index 1c294aa0f..704669057 100644 --- a/backend/src/database/stepTypes.js +++ b/backend/src/database/stepTypes.js @@ -463,6 +463,13 @@ function stepDefs() { type: 'Tool-Tip', pre: 'So the element', mid: 'has the tool-tip ', + + }, { + id: 3, + stepType: 'then', + type: 'Check Image', + pre: 'So the picture', + mid: 'has the name ', values: [ '', '' diff --git a/backend/src/serverHelper.js b/backend/src/serverHelper.js index 1f4a75ee4..b8789907a 100644 --- a/backend/src/serverHelper.js +++ b/backend/src/serverHelper.js @@ -78,6 +78,15 @@ function getExamples(steps) { return `${data}\n`; } +// parse Steps from stepDefinition container to feature content +async function parseSteps(steps) { + let data = ''; + if (steps.given !== undefined) data += `${getSteps(steps.given, Object.keys(steps)[0])}\n`; + if (steps.when !== undefined) data += `${getSteps(steps.when, Object.keys(steps)[1])}\n`; + if (steps.then !== undefined) data += `${getSteps(steps.then, Object.keys(steps)[2])}\n`; + return data; +} + // Building feature file scenario-name-content function getScenarioContent(scenarios, storyID) { let data = ''; @@ -132,8 +141,11 @@ function writeFile(story) { // Updates feature file based on _id async function updateFeatureFile(issueID) { - const result = await mongo.getOneStory(issueID); - if (result != null) writeFile(result); + const story = await mongo.getOneStory(issueID); + if (story != null) { + story.scenarios = await replaceRefBlocks(story.scenarios); + writeFile(story); + }; } async function deleteFeatureFile(storyTitle, storyId) { @@ -224,7 +236,6 @@ async function executeTest(req, mode, story) { }); } - function scenarioPrep(scenarios, driver) { const parameters = { scenarios: [] }; scenarios.forEach((scenario) => { @@ -332,11 +343,34 @@ async function updateScenarioTestStatus(uploadedReport) { } } - +async function replaceRefBlocks(scenarios) { + if (!scenarios.some((scen) => scen.hasRefBlock)) return scenarios; + const retScenarios = []; + for (const scen of scenarios) { + let stepdef = {}; + // eslint-disable-next-line guard-for-in + for (const steps in scen.stepDefinitions) { // iterate over given, when, then + const promised = await scen.stepDefinitions[steps].map(async (elem) => { + if (!elem._blockReferenceId) return [elem]; + return mongo.getBlock(elem._blockReferenceId).then((block) => { + // Get an array of the values of the given, when, then and example properties + let steps = Object.values(block.stepDefinitions); + // Flatten array + return steps.flat(1); + }); + }); + stepdef[steps] = await Promise.all(promised).then((resSteps) => resSteps.flat(1)); + } + scen.stepDefinitions = stepdef; + retScenarios.push(scen); + } + return retScenarios; +} async function exportSingleFeatureFile(_id) { const dbStory = mongo.getOneStory(_id); return dbStory.then(async (story) => { + story.scenarios = await replaceRefBlocks(story.scenarios); writeFile(story); return pfs.readFile(`./features/${this.cleanFileName(story.title + story._id.toString())}.feature`, 'utf8') .catch((err) => console.log('couldn`t read File')); @@ -348,6 +382,7 @@ async function exportProjectFeatureFiles(repoId, versionId) { return dbStories.then(async (stories) => { const zip = new AdmZip(); return Promise.all(stories.map(async (story) => { + story.scenarios = await replaceRefBlocks(story.scenarios); writeFile(story); const postfix = versionId ? `-v${versionId}` : ''; const filename = this.cleanFileName(story.title + story._id.toString()); diff --git a/backend/src/serverRouter/blockRouter.js b/backend/src/serverRouter/blockRouter.js index 84493c574..b453f72cf 100644 --- a/backend/src/serverRouter/blockRouter.js +++ b/backend/src/serverRouter/blockRouter.js @@ -45,6 +45,20 @@ router.post('/', async (req, res) => { } }); +// update Block a Block +router.put('/block', async (req, res) => { + try { + const { body } = req; + if (!req.user) res.sendStatus(401); + else { + const result = await mongo.updateBlock(body); + res.status(200).json(result.value); + } + } catch (error) { + handleError(res, error, error, 500); + } +}); + // update custom Blocks router.put('/:blockId', async (req, res) => { try { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index eab6d7221..4c82f56c7 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1370,11 +1370,12 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==" }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -1751,9 +1752,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -1794,12 +1795,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -1807,9 +1808,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -3346,13 +3347,13 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -3390,12 +3391,12 @@ } }, "node_modules/@babel/types": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", - "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -16164,7 +16165,6 @@ "esbuild": { "optional": true } - } }, "node_modules/jest-preset-angular/node_modules/v8-to-istanbul": { @@ -21417,7 +21417,6 @@ }, "engines": { "node": ">=0.10.0" - } }, "node_modules/ssri": { diff --git a/frontend/src/app/Services/block.service.ts b/frontend/src/app/Services/block.service.ts index 24fc9a8eb..2985c551b 100644 --- a/frontend/src/app/Services/block.service.ts +++ b/frontend/src/app/Services/block.service.ts @@ -5,6 +5,10 @@ import { ApiService } from '../Services/api.service'; import { HttpClient } from '@angular/common/http'; import { ToastrService } from 'ngx-toastr'; import {catchError, tap} from 'rxjs/operators'; +import { Story } from '../model/Story'; +import { StepType } from '../model/StepType'; +import { StoryService } from './story.service'; +import { Scenario } from '../model/Scenario'; /** * Service for communication between block component and the backend @@ -17,27 +21,45 @@ export class BlockService { /** * @ignore */ - constructor(public apiService: ApiService, private http: HttpClient, public toastr: ToastrService) { } + constructor(public apiService: ApiService, private http: HttpClient, public toastr: ToastrService, public storyService: StoryService) { } /** * Event emitter to add a block to the current scenario */ public addBlockToScenarioEvent = new EventEmitter(); /** - * Event emitter to add update a block + * Event emitter to add update a block */ public updateBlocksEvent: EventEmitter = new EventEmitter(); /** - * Delete emitter to add delete a block from blocks + * Event emitter to delete reference + */ + public deleteReferenceEvent: EventEmitter = new EventEmitter(); + /** + * Event emitter to check for reference + */ + public checkRefOnRemoveEvent: EventEmitter = new EventEmitter(); + /** + * Delete emitter to add delete a block from blocks */ public deleteBlockEvent = new EventEmitter(); + /** + * Event emitter to unpack Block + */ + public unpackBlockEvent = new EventEmitter(); + + blocks: Block[] = []; + referenceStories: Story[]; + referenceScenarios: Scenario[]; + block: Block; + /** * Emits the add block to scenario event * @param block * @param correspondingComponent */ - addBlockToScenario(block: Block, correspondingComponent: string) { - this.addBlockToScenarioEvent.emit([correspondingComponent, block]); + addBlockToScenario(block: Block, correspondingComponent: string, addAsReference: boolean) { + this.addBlockToScenarioEvent.emit([correspondingComponent, block, addAsReference]); } /** * Emits the update block in blocks @@ -52,6 +74,28 @@ export class BlockService { public deleteBlockEmitter() { this.deleteBlockEvent.emit(); } + /** + * Emits the unpack block event + * @param block + */ + public unpackBlockEmitter(block) { + this.unpackBlockEvent.emit(block); + } + /** + * Emits the checking stories for references event + * @param blockReferenceId + */ + public checkRefOnRemoveEmitter(blockReferenceId) { + this.checkRefOnRemoveEvent.emit(blockReferenceId); + } + /** + * Emits the delete block as reference + * @param block + */ + public deleteReferenceEmitter(block: Block) { + this.deleteReferenceEvent.emit(block); + } + /** * Retrieves the blocks * @param repoId id of the project of the blocks @@ -117,4 +161,111 @@ export class BlockService { ) } } + + /** + * Update a Block + * @param block + * @returns + */ + editBlock(block: Block) { + return this.http + .put(this.apiService.apiServer + '/block/' + block._id, block, ApiService.getOptions()) + .pipe(tap(), + catchError(this.apiService.handleError) + ); + } + /** + * Search for a references in all stories + * @param stories + */ + searchReferences(stories: Story[]){ + this.referenceScenarios = []; + stories.filter((s) => s !== null).flatMap((story) => story.scenarios + .filter((scenario) => scenario.hasRefBlock)) + .forEach((scenario) => this.referenceScenarios.push(scenario)); + this.referenceStories = this.referenceScenarios + .map((scenario) => stories.find((story) => story.scenarios.includes(scenario))) + .filter((story, index, arr) => story && arr.indexOf(story) === index); + } + /** + * Add/Delete for reference steps in scenario. + */ + stepAsReference() { + if(this.blocks.length !== 0){ + for (const block of this.blocks) { + if (block.usedAsReference === false) { + delete block.usedAsReference; + this.updateBlock(block) + .subscribe(_ => { + this.updateBlocksEvent.emit(); + }); + } + } + this.blocks = []; + } + else return 0; + } + + /** + * Remove the reference property for the steps + * @param blockReferenceId + * @param blocks + * @param stories + */ + removeReferenceForStep(blocks:Block[], stories: Story[], blockReferenceId){ + this.searchReferences(stories); + let blockNoLongerRef = true; + for (const scen of this.referenceScenarios){ + for (const prop in scen.stepDefinitions) { + for (let i = scen.stepDefinitions[prop].length - 1; i >= 0; i--) { + if (scen.stepDefinitions[prop][i]._blockReferenceId == blockReferenceId) { + blockNoLongerRef = false; + } + } + } + } + if (blockNoLongerRef){ + for (const block of blocks){ + if(block._id === blockReferenceId){ + block.usedAsReference = false; + this.blocks.push(block); + } + } + } + } + /** + * Check reference and delete in all repository + * @param block + * @param stories + */ + deteleBlockReference(block, stories: Story[]) { + this.searchReferences(stories); + this.referenceScenarios.forEach((scenario) => { + this.unpackScenarioWithBlock(block, scenario); + }); + this.referenceStories.forEach((story) => { + this.storyService.updateStory(story).subscribe(_resp => {}); + }); + } + + /** + * Unpack references. Wenn delete block unpack all reference in repository + * @param block + * @param scenario + */ + unpackScenarioWithBlock(block, scenario) { + delete block.usedAsReference; + if (block && block.stepDefinitions) { + for (const s in block.stepDefinitions) { + block.stepDefinitions[s].forEach((step: StepType, j) => { + step.checked = false; + scenario.stepDefinitions[s].push(JSON.parse(JSON.stringify(step))); + }); + const index = scenario.stepDefinitions[s].findIndex((element) => element._blockReferenceId == block._id); + if (index > -1) { + scenario.stepDefinitions[s].splice(index, 1); + } + } + } + } } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 92ca8576b..611324366 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -47,6 +47,7 @@ import { HttpLoggerInterceptor } from "./Services/http-logger.interceptor"; import { ReportHistoryComponent } from "./report-history/report-history.component"; import { ClipboardModule } from "@angular/cdk/clipboard"; import { LoggerModule, NgxLoggerLevel } from "ngx-logger"; +import { EditBlockComponent } from './modals/edit-block/edit-block.component'; import { ThemingService } from "./Services/theming.service"; import { MatSlideToggleModule } from "@angular/material/slide-toggle"; import { MatIconModule } from "@angular/material/icon"; @@ -119,6 +120,7 @@ import { ConfirmResetPasswordPopupComponent } from "./confirm-reset-password-pop ChangeJiraAccountComponent, RepoSwichComponent, CreateScenarioComponent, + EditBlockComponent, ResizeInputDirective, RenameBackgroundComponent, BaseEditorComponent, diff --git a/frontend/src/app/base-editor/base-editor.component.css b/frontend/src/app/base-editor/base-editor.component.css index 0d69e7051..13feaf116 100644 --- a/frontend/src/app/base-editor/base-editor.component.css +++ b/frontend/src/app/base-editor/base-editor.component.css @@ -1,41 +1,45 @@ -:host ::ng-deep .stepsListContainer{ +:host ::ng-deep .stepsListContainer { display: flex; justify-content: space-between; } - -:host ::ng-deep .stepsListContainer em{ + +:host ::ng-deep .stepsListContainer em { color: var(--ocean-blue); } - -:host ::ng-deep .stepsListContainer .disabled em{ + +:host ::ng-deep .stepsListContainer .disabled em { color: gray !important; } - - -:host ::ng-deep .stepsListContainer button{ + +:host ::ng-deep .stepsListContainer button { border: none; background: none; } -:host ::ng-deep .checkbox{ +:host ::ng-deep .checkbox { margin-right: 5px; margin-bottom: 5px; margin-left: 3px; margin-top: 5px -} +} -:host ::ng-deep .checkbox:hover{ +:host ::ng-deep .checkbox:hover { cursor: pointer; } -:host ::ng-deep #checkbox_all_example{ +.block-editor-margin { + padding: 0 !important; +} + +:host ::ng-deep #checkbox_all_example { margin-left: 10px; } - -:host ::ng-deep #checkbox_all{ + +:host ::ng-deep #checkbox_all { margin-top: 2px; margin-bottom: 2px; margin-left: 6px; + margin-right: 15px; } .select-item-drag-preview { @@ -46,15 +50,15 @@ display: inline-block; width: 30px; line-height: 30px; -text-align: center; + text-align: center; } input[type=checkbox] { height: 13px; } -.stepsContainer { - white-space: nowrap; +:host ::ng-deep .stepsContainer { + white-space: nowrap; overflow: hidden; width: 100%; } @@ -64,40 +68,76 @@ input[type=checkbox] { width: fit-content; max-width: inherit; padding: 1px 20px 1px 20px; -} +} .cdk-drop-list-dragging .cdk-drag { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } -.dragIconContainer{ +.dragIconContainer { text-align: center; cursor: move; + margin-top: 1.5px; } -em.dragIcon{ +em.dragIcon { font-size: 18px; color: black; background-color: var(--white); - margin-top:1px; + margin-top: 1px; } -.text-inline{ +.text-inline { display: flex; } - -.text-inline > button{ + +.mat-expansion-panel-header.stepBlockHeader { + height: 100%; + padding: 3.4px +} + +.blockReference { + width: 100%; +} + +.text-inline>button { margin: 0 5px; } - -.text-inline > p > input{ + +.stepBlockTitle { + justify-content: center; + word-break: break-word; + white-space: pre-wrap; +} + +.stepBlockTitle .SmallHeadline5 { + margin-top: 2px; + font-weight: bold; + text-align: center; +} + +.blockButton { + border: none; + background: inherit; +} + +.blockButtonGroup .material-icons { + color: var(--ocean-blue); +} + +.mat-action-row.blockButtonGroup { + padding: 5px 8px 0px 24px !important; +} + +.text-inline>p>input { margin: 0 5px; } .uk-card-title { font-size: medium; } -.addStepButton{ + +.addStepButton { border: inherit; background: inherit; color: var(--ocean-blue); @@ -105,7 +145,8 @@ em.dragIcon{ transform: translate(0, -45px); } -em.addStep{ + +em.addStep { color: var(--ocean-blue); } @@ -115,7 +156,6 @@ em.addStep{ :host ::ng-deep .disabled { color: gray !important; - /* background: var(--white) !important; */ font-style: italic; text-decoration: line-through; } @@ -124,31 +164,31 @@ em.addStep{ color: var(--buttonDisabled) !important; } -.checkboxDropdown{ +.checkboxDropdown { margin-top: -15px !important; } -input[type="text"]{ - margin: 1px; - padding: 1px; - border: none; - border-bottom: 1px solid #333; - position: relative; - top: -2px; - width: auto; - min-width: 5px; +input[type="text"] { + margin: 1px; + padding: 1px; + border: none; + border-bottom: 1px solid #333; + position: relative; + top: -2px; + width: auto; + min-width: 5px; } -.scenarioBarContainer{ +.scenarioBarContainer { display: flex; justify-content: space-between; border-bottom: 1px solid #e5e5e5; background-color: #f8f8f8; margin: 0px 20px; -} +} .scenarioBarContainer button, -.exampleBarContainer button{ +.exampleBarContainer button { border: none; background: none; transform: translate(0px, -3px); @@ -156,8 +196,8 @@ input[type="text"]{ border-radius: 10%; } -.scenarioBarContainer > div, -.exampleBarContainer{ +.scenarioBarContainer>div, +.exampleBarContainer { margin-top: 5px; margin-bottom: 5px; padding: 10px 15px; @@ -181,7 +221,7 @@ input[type="text"]{ transform: translate(0px, 3px) } */ -:host ::ng-deep .scenarioTitle{ +:host ::ng-deep .scenarioTitle { /* background: var(--white); */ background: transparent; border: none; @@ -193,13 +233,13 @@ input[type="text"]{ } */ -:host ::ng-deep .scenarioTitle > em{ - color: var(--ocean-blue); +:host ::ng-deep .scenarioTitle>em { + color: var(--ocean-blue); font-size: 24px; transform: translate(0px, 3px) } -.exampleBarContainer{ +.exampleBarContainer { border-bottom: 1px solid #e5e5e5; background-color: #f8f8f8; display: flex; @@ -209,29 +249,29 @@ input[type="text"]{ margin-right: 10px; } -.actionBarButtonGroup{ +.actionBarButtonGroup { margin-left: 3px; } -.actionButton{ +.actionButton { margin-left: 10px; } -.actionButton > em{ +.actionButton>em { color: #333; font-size: 22px; } -.darkTheme .actionButton > em { - color:#000000; +.darkTheme .actionButton>em { + color: #000000; } -.scenarioButton em{ - color: var(--ocean-blue); +.scenarioButton em { + color: var(--ocean-blue); } -.darkTheme.scenarioButton em{ - color: var(--ocean-blue); +.darkTheme.scenarioButton em { + color: var(--ocean-blue); } .scenarioButton em:active { @@ -239,25 +279,30 @@ input[type="text"]{ color: var(--white); } -:host ::ng-deep .disabled input{ - color: gray; - border-bottom: 1px solid gray; +:host ::ng-deep .disabled input { + color: gray; + border-bottom: 1px solid gray; + text-decoration: line-through; + font-style: italic; } :host ::ng-deep .darkTheme .disabled input { - color: var(--buttonDisabled) !important; - border-color: var(--border); + color: var(--buttonDisabled) !important; + border-color: var(--border); + text-decoration: line-through; + font-style: italic; } -.uk-dropdown{ +.uk-dropdown { padding: 15px; } -/* .darkTheme .uk-dropdown { +/* :host ::ng-deep .darkTheme . ng-star-inserted.uk-dropdown { background-color: #60767b; -} */ + color: var(--light-blue); +} */ -.padding{ +.padding { padding-top: 20px; padding-left: 20px; padding-bottom: 20px; @@ -265,11 +310,11 @@ input[type="text"]{ } /* Eventually not in use */ -.mat-mdc-form-field + .mat-mdc-form-field { +.mat-mdc-form-field+.mat-mdc-form-field { margin-left: 8px; } -:host ::ng-deep .scenarioBarContainer .disabled em{ +:host ::ng-deep .scenarioBarContainer .disabled em { color: gray !important; /* background: #f8f8f8 !important; */ } @@ -278,35 +323,53 @@ input[type="text"]{ color: var(--buttonDisabled) !important; } -.exampleList{ +.exampleList { min-width: 100px; margin-left: 10px; margin-right: 10px; } -.exampleList ::ng-deep .mat-mdc-form-field-wrapper{ +.stepInBlock { + display: inline-flex; + margin-bottom: 10px; + max-width: 660px; + max-height: 20px; +} + +.exampleList ::ng-deep .mat-mdc-form-field-wrapper { padding-bottom: 0px !important; } -.exampleList ::ng-deep .mat-mdc-form-field-underline{ +mat-expansion-panel.stepBlockContainer .mat-expansion-panel-body { + padding: 0 4px 0px 10px; +} + +.exampleList ::ng-deep .mat-mdc-form-field-underline { bottom: 0px !important; } -.exampleList ::ng-deep .mat-mdc-select-value{ + +.exampleList ::ng-deep .mat-mdc-select-value { color: var(--ocean-blue) !important; font-weight: bold !important; max-width: max-content !important; } -::ng-deep .mat-mdc-select-panel-above{ + +::ng-deep .mat-mdc-select-panel-above { width: auto !important; } -:host::ng-deep .mat-mdc-text-field-wrapper{ + +.mat-expansion-panel { + width: 100%; +} + +:host::ng-deep .mat-mdc-text-field-wrapper { height: 26px; background-color: transparent; padding: 0; } -::ng-deep .mat-mdc-form-field-subscript-wrapper{ +::ng-deep .mat-mdc-form-field-subscript-wrapper { height: 5px; } @@ -316,22 +379,24 @@ input[type="text"]{ .darkTheme ::ng-deep .exampleList .mat-mdc-select-value { font-weight: 595 !important; - } +} ::ng-deep .darkTheme .exampleListOption { font-weight: 595 !important; } - + .exampleList ::ng-deep .mat-mdc-form-field-infix { width: fit-content !important; } -.exampleButton{ +.exampleButton { height: 20px; width: 20px; - -moz-border-radius: 30px; /* or 50% */ - border-radius: 30px; /* or 50% */ + -moz-border-radius: 30px; + /* or 50% */ + border-radius: 30px; + /* or 50% */ border: 2px solid; text-align: center; @@ -339,63 +404,52 @@ input[type="text"]{ font-weight: bold; font-size: 12px; display: inline-block; - color: var(--ocean-blue) - + color: var(--ocean-blue) } .darkTheme ::ng-deep .exampleButton { - color: var(--light-blue) + color: var(--light-blue) } -.exampleMatSelect{ +.exampleMatSelect { padding-left: 5px; } -.uk-nav > li > span{ +.uk-nav>li>span { padding: 1px; cursor: pointer; } -:host ::ng-deep .uk-nav > li > .dropdownStep { - color:var(--ocean-blue); +:host ::ng-deep .uk-nav>li>span:hover { + color: gray; } -/* :host ::ng-deep .darkTheme .uk-nav > li > .dropdownStep { - color: #40C4FF; -} */ -:host ::ng-deep .darkTheme .uk-nav > li > .dropdownStep { - color: var(--light-blue); -} - -:host ::ng-deep .uk-nav > li > span:hover { - color: gray; -} - -:host ::ng-deep .darkTheme .uk-nav > li > span:hover { +:host ::ng-deep .darkTheme .uk-nav>li>span:hover { color: white; } -:host ::ng-deep .uk-nav > li > .dropdownStepUndefined { - color:var(--marine-blue); +:host ::ng-deep .uk-nav>li>.dropdownStepUndefined { + color: var(--marine-blue); } -:host ::ng-deep .darkTheme .uk-nav > li > .dropdownStepUndefined { - color:#00B0FF; +:host ::ng-deep .darkTheme .uk-nav>li>.dropdownStepUndefined { + color: #00B0FF; } -#saveButton.iconButton_unsaved em{ +#saveButton.iconButton_unsaved em { color: red !important; } -input.scenario, input.background { +input.scenario, +input.background { margin-left: 10px; margin-right: 10px; padding-left: 5px; padding-right: 5px; } -#tableAddButton{ +#tableAddButton { grid-column: 1; grid-row: 1; font-size: 15px !important; @@ -405,7 +459,7 @@ input.scenario, input.background { z-index: 100; } -#tableAddButtonBackground{ +#tableAddButtonBackground { grid-column: 1; grid-row: 1; font-size: 10px !important; @@ -418,4 +472,38 @@ input.scenario, input.background { width: 11px !important; height: 11px !important; - } \ No newline at end of file +} + +.scrollable { + overflow: auto; + max-height: 400px; +} + +.blockEditorElement ::-webkit-scrollbar-thumb, +.blockEditorElement::-webkit-scrollbar-thumb{ + background: #90CAF9 !important; +} + +.dropdownHeader { + font-weight: bold; + padding: 10px 0; + border-top: 1px solid #ddd; + margin-top: 5px; + text-decoration: underline; +} + +.darkTheme .uk-dropdown .dropdownHeader { + font-weight: bold; + padding: 10px 0; + color: white !important; + border-top: 1px solid #ddd; + margin-top: 5px; + text-decoration: underline; +} + +.dropdownHeader:first-of-type { + border-top: none; + margin-top: 0; + padding-top: 0; +} + diff --git a/frontend/src/app/base-editor/base-editor.component.html b/frontend/src/app/base-editor/base-editor.component.html index 947fbea7f..fdbee7e87 100644 --- a/frontend/src/app/base-editor/base-editor.component.html +++ b/frontend/src/app/base-editor/base-editor.component.html @@ -1,232 +1,346 @@ - + -
+
- +
- - - -
- -
+ + +
- - + +
+
    + + +
  • + + {{item.type}} + +
  • +
    +
+
- - -
- + + +
+ +
- - -
-
-
{{i+1}}. Given (Precondition)
-
{{i+1}}. When (Action)
-
{{i+1}}. Then (Result)
-
-
-
-
-
-
-
- drag_indicator -
-
- -
-
- -
-
-
- {{ selectedCount(selectedScenario.stepDefinitions,i) || 1 }} -
+ + + +
+ + +
+
+ + +
+ + +
+
+ +
+
{{i+1}}. Given (Precondition)
+
{{i+1}}. When (Action)
+
{{i+1}}. Then (Result)
+
+
+
+
+
+
+
+ drag_indicator +
+ +
+ +
+
+ +
+
+
+ {{ selectedCount(stepDefs,i) || 1 }}
- {{i+1}}.{{j+1}} {{currentStep.pre}} -
-
-
- -
-
- - - - {{exampleList}} - - - -
  - -   -
-
- - - - {{dropdownValue}} - - - -
- {{currentStep.mid}} -
-
-
- -
-
- - - - {{exampleList}} - - - -
  - -   -
+
+ {{i+1}}.{{j+1}} +
+ + +
+ {{currentStep.pre}} +
+
+
+
-
- - - - {{dropdownValue}} - - +
+ + + + {{exampleList}} + + -
- {{currentStep.post}} +
  + +   +
+
+ + + + {{dropdownValue}} + + +
-
-
-
- + {{currentStep.mid}} +
+
+
+
-
+
- - + + {{exampleList}}
  - -  
-
+
- - - {{dropdownValue}} - - + + + {{dropdownValue}} + +
-
- - - This step got updated. Please check if the implemented logic is still valid. When you've adjusted the step, save the scenario to confirm your adjustments. - - + {{currentStep.post}} +
+
+
+
+ +
+
+ + + + {{exampleList}} + + + +
  + +  
+
+ + + + {{dropdownValue}} + + + +
+
+ + + This step got updated. Please check if the implemented logic is still valid. When you've + adjusted the step, save the scenario to confirm your adjustments. + + +
-
-
-
- -
-
    -
  • - - {{step.type}} -
  • -
+
-
- - +
+
+ +
+
    +
  • + + {{step.type}} +
  • +
+
+ + + + - -
+ +
Multiple Scenarios - Define multiple Values in your Steps, to run a Scenario with the same Steps multiple times, but with different Data. Each line in the following Table, results in a separate Scenario. + Define multiple Values in your Steps, to run a Scenario with the same Steps multiple times, but with + different Data. Each line in the following Table, results in a separate Scenario.
-
-
-
+
- + -
+
-
-
-
-
+
+
+
+
drag_indicator
-
+
{{ selectedCount(selectedStory.background.stepDefinitions,1) || 1 }}
- + {{j+1}}. {{currentStep.pre}}
- +
- {{currentStep.mid}} + {{currentStep.mid}}
- +
{{currentStep.post}}
- +
- This step got updated. Please check if the implemented logic is still valid. When you've adjusted the step, save the background to confirm your adjustments. + This step got updated. Please check if the implemented logic is still valid. When you've + adjusted the step, save the background to confirm your adjustments.
@@ -301,29 +435,85 @@

- -
-
    -
  • - + +
    +
      +
    • + {{step.type}} -
    • -
    -
    +
  • +
+
-
+
+ + + + + +
+ {{block.type}} +
+
+ +
+ +
+
+ + +
+
+ + + + +
+
+ + +
+ {{j+1}}. {{currentStep.pre}}  +
+

+ {{currentStep.values[0]}}  +

+
+ {{currentStep.mid }}  +
+

+ {{currentStep.values[1]}}  +

+
+ {{currentStep.post}}  +
+
+ + - - - - - - + + \ No newline at end of file diff --git a/frontend/src/app/base-editor/base-editor.component.ts b/frontend/src/app/base-editor/base-editor.component.ts index 164ea08b5..124310348 100644 --- a/frontend/src/app/base-editor/base-editor.component.ts +++ b/frontend/src/app/base-editor/base-editor.component.ts @@ -1,6 +1,6 @@ import { ApiService } from 'src/app/Services/api.service'; import { CdkDragDrop, CdkDragStart, DragRef, moveItemInArray } from '@angular/cdk/drag-drop'; -import { Component, ElementRef, Input, QueryList, ViewChild, ViewChildren } from '@angular/core'; +import { Component, ElementRef, EventEmitter, Input, Output, QueryList, ViewChild, ViewChildren } from '@angular/core'; import { ToastrService } from 'ngx-toastr'; import { AddBlockFormComponent } from '../modals/add-block-form/add-block-form.component'; import { NewStepRequestComponent } from '../modals/new-step-request/new-step-request.component'; @@ -19,13 +19,15 @@ import { ExampleService } from '../Services/example.service'; import { ScenarioService } from '../Services/scenario.service'; import { BackgroundService } from '../Services/background.service'; import { InfoWarningToast } from '../info-warning-toast'; +import { EditBlockComponent } from '../modals/edit-block/edit-block.component'; +import { DeleteToast } from '../delete-toast'; @Component({ selector: 'app-base-editor', templateUrl: './base-editor.component.html', styleUrls: ['./base-editor.component.css'] }) -export class BaseEditorComponent { +export class BaseEditorComponent { @ViewChildren('step_type_input') step_type_input: QueryList; @@ -40,9 +42,10 @@ export class BaseEditorComponent { * View child of the modals component */ @ViewChild('saveBlockModal') saveBlockModal: SaveBlockFormComponent; - @ViewChild('addBlockModal')addBlockModal: AddBlockFormComponent; + @ViewChild('addBlockModal') addBlockModal: AddBlockFormComponent; @ViewChild('newStepRequest') newStepRequest: NewStepRequestComponent; @ViewChild('newExampleModal') newExampleModal: NewExampleComponent; + @ViewChild('editBlockModal') editBlockModal: EditBlockComponent; @@ -57,6 +60,8 @@ export class BaseEditorComponent { */ @Input() testRunning: boolean; + @Input() selectedBlock: Block; + /** * Sets a new selected scenaio */ @@ -69,7 +74,7 @@ export class BaseEditorComponent { } @Input() - set uncheckBackgroundCheckboxes (showBackground) { + set uncheckBackgroundCheckboxes(showBackground) { if (showBackground) { this.checkAllSteps(false); } @@ -82,7 +87,7 @@ export class BaseEditorComponent { set newlySelectedStory(story: Story) { this.selectedStory = story; } - + /** * Checks for an example step * @param index @@ -92,6 +97,13 @@ export class BaseEditorComponent { this.checkStep(this.selectedScenario.stepDefinitions.example[index]); } + @Output("blockSelectTriggerEvent") blockSelectTriggerEvent: EventEmitter = new EventEmitter(); + + /** + * Subscribtions for all EventEmitter + */ + expandStepBlock = false; + /** * currently selected scenario */ @@ -102,6 +114,12 @@ export class BaseEditorComponent { */ newStepName = 'New Step'; + /** + * To track changes on edit-block + */ + + saved = true; + lastToFocus; /** @@ -119,17 +137,21 @@ export class BaseEditorComponent { */ allChecked = false; - lastVisitedTemplate: string =''; + lastVisitedTemplate: string = ''; lastCheckedCheckboxIDx; /** * Flag to check how much enable steps left */ activatedSteps = 0; - /** - * If all examples steps are deactivated - */ - allDeactivated: boolean; + /** + * If all examples steps are deactivated + */ + allDeactivated: boolean; + /** +* If selected steps is a reference block +*/ + isReferenceBlock: boolean; /** * Block saved to clipboard */ @@ -152,46 +174,44 @@ export class BaseEditorComponent { copyExampleOptionObservable: Subscription; - constructor(public toastr: ToastrService, + constructor(public toastr: ToastrService, public blockService: BlockService, public exampleService: ExampleService, public scenarioService: ScenarioService, public backgroundService: BackgroundService, - public apiService: ApiService) {} + public apiService: ApiService) { } ngOnInit(): void { this.addBlocktoScenarioObservable = this.blockService.addBlockToScenarioEvent.subscribe(block => { if (this.templateName == 'background' && block[0] == 'background') { - block = block[1]; - Object.keys(block.stepDefinitions).forEach((key, _) => { + //block = block[1]; + Object.keys(block[1].stepDefinitions).forEach((key, _) => { if (key === 'when') { - block.stepDefinitions[key].forEach((step: StepType) => { - //to prevent blocks to be checked after pasting - step.checked = false; + block[1].stepDefinitions[key].forEach((step: StepType) => { this.selectedStory.background.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); }); } }); this.markUnsaved(); } - - if (this.templateName == 'scenario' && block[0] == 'scenario') { - Object.keys(block[1].stepDefinitions).forEach((key, _) => { - if (key != 'example') { - block[1].stepDefinitions[key].forEach((step: StepType, j) => { - //to prevent blocks to be checked after pasting - step.checked = false; - }); - } - }); - block = block[1]; - this.insertStepsWithExamples(block); + if (this.templateName == 'scenario' && block[0] == 'scenario') { + if (block[2]) { + const blockReference: StepType = { + _blockReferenceId: block[1]._id, id: 0, type: block[1].name, stepType: 'when', + pre: '', mid: '', post: '', values: [] + }; + this.selectedScenario.stepDefinitions.when.push(JSON.parse(JSON.stringify(blockReference))); + } else { + block = block[1]; + this.insertStepsWithExamples(block); + } this.markUnsaved(); } + }); - - this.newExampleObservable = this.exampleService.newExampleEvent.subscribe(value => {this.addToValues(value.name, 0,0, 'addingExample', value.step)}); - this.renameExampleObservable = this.exampleService.renameExampleEvent.subscribe(value =>{this.renameExample(value.name, value.column);}); + + this.newExampleObservable = this.exampleService.newExampleEvent.subscribe(value => { this.addToValues(value.name, 0, 0, 'addingExample', value.step) }); + this.renameExampleObservable = this.exampleService.renameExampleEvent.subscribe(value => { this.renameExample(value.name, value.column); }); this.scenarioChangedObservable = this.scenarioService.scenarioChangedEvent.subscribe(() => { this.checkAllSteps(false); }); @@ -200,15 +220,14 @@ export class BaseEditorComponent { }); this.copyExampleOptionObservable = this.apiService.copyStepWithExampleEvent.subscribe(option => { - if(this.clipboardBlock){ + if (this.clipboardBlock) { if (option == 'copy') { this.insertStepsWithExamples(this.clipboardBlock) - } else if (option == 'dontCopy'){ + } else if (option == 'dontCopy') { this.insertStepsWithoutExamples() } } - }); - + }); } ngOnDestroy(): void { @@ -220,14 +239,14 @@ export class BaseEditorComponent { } if (!this.renameExampleObservable.closed) { this.renameExampleObservable.unsubscribe(); - } + } if (!this.scenarioChangedObservable.closed) { this.scenarioChangedObservable.unsubscribe(); - } - if(!this.backgroundChangedObservable.closed) { + } + if (!this.backgroundChangedObservable.closed) { this.backgroundChangedObservable.unsubscribe(); } - if(!this.copyExampleOptionObservable.closed) { + if (!this.copyExampleOptionObservable.closed) { this.copyExampleOptionObservable.unsubscribe(); } @@ -237,7 +256,7 @@ export class BaseEditorComponent { * retrieves the saved block from the session storage */ ngDoCheck(): void { - switch(this.templateName) { + switch (this.templateName) { case 'background': this.clipboardBlock = JSON.parse(sessionStorage.getItem('scenarioBlock')); break; @@ -249,27 +268,30 @@ export class BaseEditorComponent { case 'example': this.clipboardBlock = JSON.parse(sessionStorage.getItem('copiedExampleBlock')); break; + + case 'block-editor': + this.clipboardBlock = JSON.parse(sessionStorage.getItem('copiedEditBlock')) default: break; } - if(this.allChecked) { + if (this.allChecked) { this.checkAllSteps(this.allChecked); - } + } } - ngAfterViewInit(): void { + ngAfterViewInit(): void { this.step_type_input.changes.subscribe(_ => { - this.step_type_input.forEach(in_field => { - if ( in_field.nativeElement.id === this.lastToFocus) { - in_field.nativeElement.focus(); - } - }); - this.lastToFocus = ''; + this.step_type_input.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { + in_field.nativeElement.focus(); + } + }); + this.lastToFocus = ''; }); this.step_type_input1.changes.subscribe(_ => { - this.step_type_input1.forEach(in_field => { - if ( in_field.nativeElement.id === this.lastToFocus) { + this.step_type_input1.forEach(in_field => { + if (in_field.nativeElement.id === this.lastToFocus) { in_field.nativeElement.focus(); } }); @@ -288,7 +310,7 @@ export class BaseEditorComponent { * @param step */ - fillExapleValues (stepType, index, step) { + fillExapleValues(stepType, index, step) { if (!this.selectedScenario.stepDefinitions[stepType][0] || !this.selectedScenario.stepDefinitions[stepType][0].values.some(r => step.values.includes(r))) { this.selectedScenario.stepDefinitions[stepType].push(JSON.parse(JSON.stringify(step))); } @@ -300,7 +322,7 @@ export class BaseEditorComponent { } }); } - + } @@ -313,23 +335,39 @@ export class BaseEditorComponent { * @param stepType * @param step Optional argument */ - addToValues(input: string, stepIndex: number, valueIndex: number, stepType: string, step?:StepType) { + addToValues(input: string, stepIndex: number, valueIndex: number, stepType: string, step?: StepType) { switch (this.templateName) { case 'background': - this.selectedStory.background.stepDefinitions.when[stepIndex].values[valueIndex] = input; + this.selectedStory.background.stepDefinitions.when[stepIndex].values[valueIndex] = input; this.markUnsaved(); break; case 'scenario': //updates scenario steps this.updateScenarioValues(input, stepIndex, valueIndex, stepType); this.markUnsaved(); - break; + break; case 'example': if (stepType == 'addingExample') { this.handleExamples(input, step); this.markUnsaved(); } - break; + case 'block-editor': + switch (stepType) { + case 'given': + this.selectedBlock.stepDefinitions.given[stepIndex].values[valueIndex] = input; + break; + case 'when': + this.selectedBlock.stepDefinitions.when[stepIndex].values[valueIndex] = input; + break; + case 'then': + this.selectedBlock.stepDefinitions.then[stepIndex].values[valueIndex] = input; + break; + default: + console.error('Unknown stepType:', stepType); + break; + } + // this.markUnsaved(); + break; } } @@ -341,11 +379,9 @@ export class BaseEditorComponent { * @param stepType */ updateScenarioValues(input: string, stepIndex: number, valueIndex: number, stepType: string) { - console.log(this.selectedScenario.stepDefinitions); - - switch (stepType) { - case 'given': - if(this.selectedScenario.stepDefinitions.given[stepIndex].isExample[valueIndex]){ + switch (stepType) { + case 'given': + if (this.selectedScenario.stepDefinitions.given[stepIndex].isExample[valueIndex]) { this.selectedScenario.stepDefinitions.given[stepIndex].values[valueIndex] = '<' + input + '>'; } else { @@ -353,28 +389,28 @@ export class BaseEditorComponent { } break; case 'when': - if(this.selectedScenario.stepDefinitions.when[stepIndex].isExample[valueIndex]){ + if (this.selectedScenario.stepDefinitions.when[stepIndex].isExample[valueIndex]) { this.selectedScenario.stepDefinitions.when[stepIndex].values[valueIndex] = '<' + input + '>'; } else { this.selectedScenario.stepDefinitions.when[stepIndex].values[valueIndex] = input; - } + } break; case 'then': - if(this.selectedScenario.stepDefinitions.then[stepIndex].isExample[valueIndex]){ + if (this.selectedScenario.stepDefinitions.then[stepIndex].isExample[valueIndex]) { this.selectedScenario.stepDefinitions.then[stepIndex].values[valueIndex] = '<' + input + '>'; } else { - this.selectedScenario.stepDefinitions.then[stepIndex].values[valueIndex] = input; - } + this.selectedScenario.stepDefinitions.then[stepIndex].values[valueIndex] = input; + } break; case 'example': this.selectedScenario.stepDefinitions.example[stepIndex].values[valueIndex] = input; this.markUnsaved(); } - + } - + /** * Adds step * @param step @@ -382,12 +418,12 @@ export class BaseEditorComponent { * @param templateName * @param step_idx Optional argument */ - addStep(step: StepType, selectedScenario:any, templateName, step_idx?: any) { + addStep(step: StepType, selectedScenario: any, templateName, step_idx?: any) { let lastEl; let newStep; if (templateName == 'background') { newStep = this.createNewStep(step, selectedScenario.background.stepDefinitions); - } + } else { newStep = this.createNewStep(step, selectedScenario.stepDefinitions); } @@ -397,29 +433,29 @@ export class BaseEditorComponent { switch (newStep.stepType) { case 'given': selectedScenario.stepDefinitions.given.push(newStep); - lastEl = selectedScenario.stepDefinitions.given.length-1; - this.lastToFocus = templateName+'_'+step_idx+'_input_pre_'+ lastEl; + lastEl = selectedScenario.stepDefinitions.given.length - 1; + this.lastToFocus = templateName + '_' + step_idx + '_input_pre_' + lastEl; break; case 'when': switch (templateName) { case 'scenario': selectedScenario.stepDefinitions.when.push(newStep); - lastEl = selectedScenario.stepDefinitions.when.length-1; - this.lastToFocus = templateName+'_'+step_idx+'_input_pre_'+ lastEl; + lastEl = selectedScenario.stepDefinitions.when.length - 1; + this.lastToFocus = templateName + '_' + step_idx + '_input_pre_' + lastEl; break; - + case 'background': selectedScenario.background.stepDefinitions.when.push(newStep); - lastEl = selectedScenario.background.stepDefinitions.when.length-1; - this.lastToFocus = templateName+'_step_input_pre_'+ lastEl; + lastEl = selectedScenario.background.stepDefinitions.when.length - 1; + this.lastToFocus = templateName + '_step_input_pre_' + lastEl; break; } break; - + case 'then': selectedScenario.stepDefinitions.then.push(newStep); - lastEl = selectedScenario.stepDefinitions.then.length-1; - this.lastToFocus = templateName+'_'+step_idx+'_input_pre_'+ lastEl; + lastEl = selectedScenario.stepDefinitions.then.length - 1; + this.lastToFocus = templateName + '_' + step_idx + '_input_pre_' + lastEl; break; case 'example': this.addExampleStep(step); @@ -431,17 +467,53 @@ export class BaseEditorComponent { } } - openNewExample(step) { + /** + * Adds a step to the Block + * @param step + * @param position + */ + addStepToBlock(step, position?: number) { + const newStep = this.createNewStep(step, this.selectedBlock.stepDefinitions); + console.log('New created step'); + console.log(newStep); + switch (newStep.stepType) { + case 'given': + if (position) { + this.selectedBlock.stepDefinitions.given.splice(position, 0, newStep); + } else { + this.selectedBlock.stepDefinitions.given.push(newStep); + } + break; + case 'when': + if (position) { + this.selectedBlock.stepDefinitions.when.splice(position, 0, newStep); + } else { + this.selectedBlock.stepDefinitions.when.push(newStep); + } + break; + case 'then': + if (position) { + this.selectedBlock.stepDefinitions.then.splice(position, 0, newStep); + } else { + this.selectedBlock.stepDefinitions.then.push(newStep); + } + break; + } + console.log('Current block'); + console.log(this.selectedBlock); + } + + openNewExample(step) { this.newExampleModal.openNewExampleModal(this.selectedScenario, step); - } - - /** - * Creates a new step - * @param step - * @param stepDefinitions - * @returns - */ + } + + /** + * Creates a new step + * @param step + * @param stepDefinitions + * @returns + */ createNewStep(step: StepType, stepDefinitions: StepDefinition | StepDefinitionBackground, stepType?: string): StepType { const obj = JSON.parse(JSON.stringify(step)); const newId = this.getLastIDinStep(stepDefinitions, obj.stepType) + 1; @@ -457,14 +529,14 @@ export class BaseEditorComponent { }; return newStep; } - - - /** - * Gets the last id in the steps - * @param stepDefs - * @param stepStepType - * @returns - */ + + + /** + * Gets the last id in the steps + * @param stepDefs + * @param stepStepType + * @returns + */ getLastIDinStep(stepDefs: any, stepStepType: string): number { switch (stepStepType) { @@ -478,12 +550,12 @@ export class BaseEditorComponent { return this.buildID(stepDefs.example); } } - - /** - * gets the id of the step - * @param step - * @returns - */ + + /** + * gets the id of the step + * @param step + * @returns + */ buildID(step): number { if (step.length !== 0) { return step[step.length - 1].id; @@ -491,7 +563,7 @@ export class BaseEditorComponent { return 0; } } - + getKeysList(stepDefs: StepDefinition) { if (stepDefs != null) { return Object.keys(stepDefs); @@ -499,27 +571,80 @@ export class BaseEditorComponent { return ''; } } + + /** + * Sort the step types + * @returns + */ - /** - * Sort the step types - * @returns - */ - sortedStepTypes() { + sortedStepTypes(): any[] { if (this.originalStepTypes) { - const sortedStepTypes = this.originalStepTypes; - sortedStepTypes.sort((a, b) => { - return a.id - b.id; - }); - return sortedStepTypes; + const given = []; + const when = []; + const then = []; + + for (const step of this.originalStepTypes) { + if (step.stepType === 'given') { + given.push(step) + } else if (step.stepType === 'when') { + when.push(step); + + } else if (step.stepType === 'then') { + then.push(step); + + } + } + given.sort((a, b) => a.id - b.id); + when.sort((a, b) => a.id - b.id); + then.sort((a, b) => a.id - b.id); + + return [ + { type: 'Header', label: 'Given' }, + ...given, + { type: 'Header', label: 'When' }, + ...when, + { type: 'Header', label: 'Then' }, + ...then + ]; } + return []; } - - /** - * Gets the steps list (For Background: it should be set to 1 in order to enter when-Block) - * @param stepDefs - * @param i number of steptype - * @returns - */ + + + getUniqueSteps(): StepType[] { + let uniqueStepTypes: StepType[] = []; + let addedTypes: Set = new Set(); + let screenshotAdded: boolean = false; + + for (let step of this.sortedStepTypes()) { + let stepCopy = { ...step }; + //for all unique steps set steptype to "when" for edit-block + stepCopy.stepType = 'when'; + + if (stepCopy.type === "Screenshot" && !screenshotAdded) { + stepCopy.pre = "I take a screenshot. Optionally: Focus the page on the element"; + uniqueStepTypes.push(stepCopy); + addedTypes.add(stepCopy.type); + screenshotAdded = true; + } else if (!addedTypes.has(stepCopy.type)) { + uniqueStepTypes.push(stepCopy); + addedTypes.add(stepCopy.type); + } + } + + return uniqueStepTypes; +} + + + + + + /** + * Gets the steps list (For Background: it should be set to 1 in order to enter when-Block) + * @param stepDefs + * @param i number of steptype + * @returns + */ getStepsList(stepDefs: StepDefinition, i: number) { switch (i) { case 0: @@ -531,33 +656,43 @@ export class BaseEditorComponent { } return stepDefs.example; } - - /** Dragging element methods */ - - - /** - * Drag and drop event for a step - * @param event - * @param stepDefs - * @param stepIndex - */ - + + /** + * Gets the steps list (For BlockPreview) + * @param stepDefs + * @returns + */ + getStepsListBlockPreview(stepDefs: StepDefinition) { + const { given = [], when = [], then = [] } = stepDefs || {}; + const allStepValues = [...given, ...when, ...then]; + return allStepValues; + + } + + + /** + * Drag and drop event for a step + * @param event + * @param stepDefs + * @param stepIndex + */ + onDrop(event: CdkDragDrop, stepDefs: StepDefinition, stepIndex: number) { if (this.selectedCount(stepDefs, stepIndex) > 1) { let indices = event.item.data.indices; - let change = event.currentIndex-event.previousIndex; - + let change = event.currentIndex - event.previousIndex; + let newList = [] - - if (change > 0){ - let startOfList = this.getStepsList(stepDefs, stepIndex).slice(0, event.currentIndex+1) + + if (change > 0) { + let startOfList = this.getStepsList(stepDefs, stepIndex).slice(0, event.currentIndex + 1) let middleOfList: StepType[] = [] - let endOfList = this.getStepsList(stepDefs, stepIndex).slice(event.currentIndex+1) + let endOfList = this.getStepsList(stepDefs, stepIndex).slice(event.currentIndex + 1) indices.forEach((element) => { middleOfList.push(element.value) }); - let startOfListFiltered = startOfList.filter( ( el ) => !middleOfList.includes( el ) ); - let endOfListFiltered = endOfList.filter( ( el ) => !middleOfList.includes( el ) ); + let startOfListFiltered = startOfList.filter((el) => !middleOfList.includes(el)); + let endOfListFiltered = endOfList.filter((el) => !middleOfList.includes(el)); startOfListFiltered.push(...middleOfList) startOfListFiltered.push(...endOfListFiltered) newList = startOfListFiltered @@ -568,8 +703,8 @@ export class BaseEditorComponent { indices.forEach((element) => { middleOfList.push(element.value) }); - let startOfListFiltered = startOfList.filter( ( el ) => !middleOfList.includes( el ) ); - let endOfListFiltered = endOfList.filter( ( el ) => !middleOfList.includes( el ) ); + let startOfListFiltered = startOfList.filter((el) => !middleOfList.includes(el)); + let endOfListFiltered = endOfList.filter((el) => !middleOfList.includes(el)); startOfListFiltered.push(...middleOfList) startOfListFiltered.push(...endOfListFiltered) newList = startOfListFiltered @@ -578,18 +713,23 @@ export class BaseEditorComponent { newList = this.getStepsList(stepDefs, stepIndex) } this.updateList(stepIndex, newList); - + } else { moveItemInArray(this.getStepsList(stepDefs, stepIndex), event.previousIndex, event.currentIndex); } this.markUnsaved(); } + onDropBlock(event: CdkDragDrop, stepDefs: StepDefinition, stepIndex: number) { + moveItemInArray(this.getStepsList(stepDefs, stepIndex), event.previousIndex, event.currentIndex); + this.markUnsaved(); + } + /** * Marks story or scenario unsaved depending on template */ - markUnsaved () { - switch(this.templateName) { + markUnsaved() { + switch (this.templateName) { case 'background': this.selectedStory.background.saved = false; this.backgroundService.backgroundReplaced = undefined; @@ -600,16 +740,18 @@ export class BaseEditorComponent { case 'example': this.selectedScenario.saved = false; break; + case 'default': + break; } } /** Updates step definitions list (background & scenario) * @param stepIndex * @param newList List afret dragging - * */ + * */ updateList(stepIndex: number, newList) { - switch(this.templateName) { + switch (this.templateName) { case 'background': this.selectedStory.background.stepDefinitions.when = newList break; @@ -622,9 +764,9 @@ export class BaseEditorComponent { this.selectedScenario.stepDefinitions.then = newList } break; - } + } } - + /** * Maps all selected steps to their index * Sets dragging boolean @@ -635,7 +777,7 @@ export class BaseEditorComponent { dragStarted(event: CdkDragStart, stepDefs, i: number): void { this.dragging = event.source._dragRef; let indices = null; - switch(this.templateName) { + switch (this.templateName) { case 'background': /* indices = stepDefs.when .map(function(element, index) {return {index: index, value: element}}) @@ -677,29 +819,29 @@ export class BaseEditorComponent { getCheckedValues(stepDefs, i) { if (i === 0) { return stepDefs.given - .map(function(element, index) {return {index: index, value: element}}) - .filter(function(element) { return element.value.checked}); + .map(function (element, index) { return { index: index, value: element } }) + .filter(function (element) { return element.value.checked }); } else if (i === 1) { return stepDefs.when - .map(function(element, index) {return {index: index, value: element}}) - .filter(function(element) { return element.value.checked}); + .map(function (element, index) { return { index: index, value: element } }) + .filter(function (element) { return element.value.checked }); } else if (i === 2) { return stepDefs.then - .map(function(element, index) {return {index: index, value: element}}) - .filter(function(element) { return element.value.checked}); + .map(function (element, index) { return { index: index, value: element } }) + .filter(function (element) { return element.value.checked }); } } - - + + /** * Sets dragging boolean */ dragEnded(): void { - this.dragging = null; + this.dragging = null; } - + /** * Checks if step is selected * @param i @@ -708,7 +850,7 @@ export class BaseEditorComponent { */ isSelected(stepDefs, i: number, j: number): any { - switch(this.templateName) { + switch (this.templateName) { case 'background': //return stepDefs.when[j].checked; return this.returnCheckedValue(stepDefs, i, j); @@ -724,7 +866,7 @@ export class BaseEditorComponent { return false; */ return this.returnCheckedValue(stepDefs, i, j); } - + } /** @@ -738,13 +880,13 @@ export class BaseEditorComponent { if (i === 0) { return stepDefs.given[j].checked; } else if (i === 1) { - return stepDefs.when[j].checked; + return stepDefs.when[j].checked; } else if (i === 2) { - return stepDefs.then[j].checked; + return stepDefs.then[j].checked; } return false; } - + /** * Returns count of all selected step from one stepDefinition * @param stepDefs @@ -754,7 +896,7 @@ export class BaseEditorComponent { selectedCount(stepDefs, i: number) { let result - switch(this.templateName) { + switch (this.templateName) { case 'background': //this.selectedStory.background.stepDefinitions.when.forEach(element => { if(element.checked){counter++;} }); result = this.countChecked(stepDefs, i); @@ -773,18 +915,18 @@ export class BaseEditorComponent { return result } - countChecked(stepDefs, i) { + countChecked(stepDefs, i) { let counter = 0 if (i === 0) { - stepDefs.given.forEach(element => { if(element.checked){counter++;} }); + stepDefs.given.forEach(element => { if (element.checked) { counter++; } }); } else if (i === 1) { - stepDefs.when.forEach(element => { if(element.checked){counter++;} }); + stepDefs.when.forEach(element => { if (element.checked) { counter++; } }); } else if (i === 2) { - stepDefs.then.forEach(element => { if(element.checked){counter++;} }); + stepDefs.then.forEach(element => { if (element.checked) { counter++; } }); } return counter } - + /** * Check all steps * @param checkValue @@ -793,10 +935,10 @@ export class BaseEditorComponent { if (checkValue != undefined) { this.allChecked = checkValue; } else { - this.allChecked = !this.allChecked; + this.allChecked = !this.allChecked; } - - switch (this.templateName){ + delete this.isReferenceBlock; + switch (this.templateName) { case 'background': this.checkOnIteration(this.selectedStory.background.stepDefinitions, this.allChecked); break; @@ -808,13 +950,18 @@ export class BaseEditorComponent { case 'example': this.checkOnIteration(this.selectedScenario.stepDefinitions.example, this.allChecked); break; + + case 'block-editor': + this.checkOnIteration(this.selectedBlock.stepDefinitions, this.allChecked); + break; } } - + + checkOnIteration(stepsList, checkValue: boolean) { //background & scenario for (const prop in stepsList) { - if(this.templateName !== 'example' && prop !== 'example') { + if (this.templateName !== 'example' && prop !== 'example') { for (let i = stepsList[prop].length - 1; i >= 0; i--) { this.checkStep(stepsList[prop][i], checkValue); } @@ -823,11 +970,11 @@ export class BaseEditorComponent { for (let i = stepsList.length - 1; i > 0; i--) { this.checkStep(stepsList[i], checkValue); } - } - - } + } + + } } - + /** * Handles checkboxes on click * @param event Click event @@ -837,6 +984,7 @@ export class BaseEditorComponent { */ handleClick(event, step, checkbox_id, checkValue?: boolean) { // if key pressed is shift + delete this.isReferenceBlock; if (event.shiftKey && this.lastVisitedTemplate == this.templateName) { this.checkMany(step, checkbox_id); } else { @@ -846,34 +994,34 @@ export class BaseEditorComponent { this.lastCheckedCheckboxIDx = checkbox_id; this.lastVisitedTemplate = this.templateName; } - + /** * Checks many steps on shift click * @param currentStep * @param checkbox_id */ - + checkMany(currentStep, checkbox_id) { - let newTmp:number = checkbox_id; // current step id + let newTmp: number = checkbox_id; // current step id let lastTmp = this.lastCheckedCheckboxIDx; // Find in this block start and end step let start = Math.min(newTmp, lastTmp); // get starting & ending array element let end = Math.max(newTmp, lastTmp); - + // Check all steps in the list between start and end - switch(this.templateName) { + switch (this.templateName) { case 'scenario': const scenario_val = this.selectedScenario.stepDefinitions[currentStep.stepType][lastTmp].checked; - for (let i = start+1; i <= end; i++) { + for (let i = start + 1; i <= end; i++) { this.selectedScenario.stepDefinitions[currentStep.stepType][i].checked = scenario_val; } break; case 'background': const background_val = this.selectedStory.background.stepDefinitions[currentStep.stepType][lastTmp].checked; - for (let i = start+1; i <= end; i++) { - this.selectedStory.background.stepDefinitions[currentStep.stepType][i].checked = background_val; + for (let i = start + 1; i <= end; i++) { + this.selectedStory.background.stepDefinitions[currentStep.stepType][i].checked = background_val; } break; case 'example': @@ -882,11 +1030,11 @@ export class BaseEditorComponent { setTimeout(() => { this.areAllStepsChecked(); }, 20); - + } - - + + /** * Checks a step * @param step @@ -894,15 +1042,71 @@ export class BaseEditorComponent { */ checkStep(step, checkValue?: boolean) { - if (checkValue != undefined) { - step.checked = checkValue; - } else { - step.checked = !step.checked; - } + switch (this.templateName) { + case 'block-editor': + if (checkValue != null) { + step.checked = checkValue; + } else { + step.checked = !step.checked; + } + let checkCount = 0; + let stepCount = 0; - this.areAllStepsChecked(); + for (const prop in this.selectedBlock.stepDefinitions) { + for (let i = this.selectedBlock.stepDefinitions[prop].length - 1; i >= 0; i--) { + stepCount++; + if (this.selectedBlock.stepDefinitions[prop][i].checked) { + checkCount++; + } + } + } + if (checkCount >= stepCount) { + this.allChecked = true; + } else { + this.allChecked = false; + } + if (checkCount <= 0) { + this.allChecked = false; + this.activeActionBar = false; + } else { + this.activeActionBar = true; + } + break; + default: + if (checkValue !== undefined) { + step.checked = checkValue; + } else { + step.checked = !step.checked; + this.disableSaveBlock(step); + } + this.areAllStepsChecked(); + break; + } } + /** + * Enables/disables "Save steps as Block" if only reference-block-steps selected + */ + disableSaveBlock(step) { + let allSelectedSteps = 0; + let onlyReferenceSteps = 0; + if (this.templateName === 'scenario') { + for (const prop in this.selectedScenario.stepDefinitions) { + if (prop !== 'example') { + for (let i = this.selectedScenario.stepDefinitions[prop].length - 1; i >= 0; i--) { + if (this.selectedScenario.stepDefinitions[prop][i].checked) { + allSelectedSteps++; + } if (this.selectedScenario.stepDefinitions[prop][i].checked && this.selectedScenario.stepDefinitions[prop][i]._blockReferenceId) { + onlyReferenceSteps++; + } + } + } + } + if (onlyReferenceSteps == allSelectedSteps) { + this.isReferenceBlock = true; + } + } + } /** * Enables/disables action bar and checkbox in it depending on whether all steps are checked */ @@ -911,7 +1115,7 @@ export class BaseEditorComponent { let checkCount = 0; let stepCount = 0; - switch(this.templateName) { + switch (this.templateName) { case 'scenario': for (const prop in this.selectedScenario.stepDefinitions) { @@ -935,7 +1139,7 @@ export class BaseEditorComponent { checkCount++; } } - } + } this.updateAllActionBar(checkCount, stepCount); break; @@ -967,17 +1171,17 @@ export class BaseEditorComponent { this.activeActionBar = true; } } - + /** Action bar methods */ - + /** * Save a new block * */ saveBlock(): void { - + //const saveBlock = {given: [], when: [], then: [], example: []}; let saveBlock; @@ -991,8 +1195,8 @@ export class BaseEditorComponent { } } } */ - break; - + break; + case 'scenario': saveBlock = this.addStepsToBlockOnIteration(JSON.parse(JSON.stringify(this.selectedScenario.stepDefinitions))); /* for (const prop in this.selectedScenario.stepDefinitions) { @@ -1009,7 +1213,7 @@ export class BaseEditorComponent { break; } - const block: Block = {name: 'TEST', stepDefinitions: saveBlock}; + const block: Block = { name: 'TEST', stepDefinitions: saveBlock }; this.saveBlockModal.openSaveBlockFormModal(block, this); } @@ -1018,34 +1222,38 @@ export class BaseEditorComponent { * Return examples to steps * */ - reactivateExampleSteps(){ + reactivateExampleSteps() { this.selectedScenario.stepDefinitions.example.forEach((value, index) => { - value.values.forEach((val, i) => {{ + value.values.forEach((val, i) => { + { this.selectedScenario.stepDefinitions.example[index].isExample[i] = true } }) }) - } + } /** * Delete rows or deactivate examples * */ - deleteRows(){ + deleteRows() { this.selectedScenario.stepDefinitions.given.forEach((value, index) => { - value.values.forEach((val, i) => {{ + value.values.forEach((val, i) => { + { this.selectedScenario.stepDefinitions.given[index].isExample[i] = undefined } }) }) this.selectedScenario.stepDefinitions.when.forEach((value, index) => { - value.values.forEach((val, i) => {{ - this.selectedScenario.stepDefinitions.when[index].isExample[i] = undefined + value.values.forEach((val, i) => { + { + this.selectedScenario.stepDefinitions.when[index].isExample[i] = undefined } }) }) this.selectedScenario.stepDefinitions.then.forEach((value, index) => { - value.values.forEach((val, i) => {{ + value.values.forEach((val, i) => { + { this.selectedScenario.stepDefinitions.then[index].isExample[i] = undefined } }) @@ -1055,22 +1263,23 @@ export class BaseEditorComponent { * Deactivates all example steps * */ - deactivateAllExampleSteps(){ + deactivateAllExampleSteps() { this.activatedSteps = 0; - this.allDeactivated = true; + this.allDeactivated = true; this.selectedScenario.stepDefinitions.example.forEach((value, index) => { - value.values.forEach((val, i) => {{ + value.values.forEach((val, i) => { + { this.selectedScenario.stepDefinitions.example[index].isExample[i] = false } }) }) } - /** - * Deactivates all checked step - * - */ + /** + * Deactivates all checked step + * + */ - deactivateStep(): void{ + deactivateStep(): void { switch (this.templateName) { case 'background': this.deactivateOnIteration(this.selectedStory.background.stepDefinitions); @@ -1084,7 +1293,7 @@ export class BaseEditorComponent { //this.selectedStory.background.saved = false; this.markUnsaved(); break; - + case 'scenario': this.deactivateOnIteration(this.selectedScenario.stepDefinitions); /* for (const prop in this.selectedScenario.stepDefinitions) { @@ -1102,7 +1311,7 @@ export class BaseEditorComponent { case 'example': { this.allDeactivated = false; - if ( !this.allDeactivated ) { + if (!this.allDeactivated) { this.reactivateExampleSteps(); } let example = this.selectedScenario.stepDefinitions.example; @@ -1110,12 +1319,11 @@ export class BaseEditorComponent { example.forEach(step => { if (step.checked && this.activatedSteps < totalSteps - 1) { step.deactivated = !step.deactivated; - if(step.deactivated) + if (step.deactivated) this.activatedSteps++; } }); if (this.activatedSteps === totalSteps - 1) { - console.log("All steps are deactivated"); this.deactivateAllExampleSteps(); this.markUnsaved(); return @@ -1124,6 +1332,11 @@ export class BaseEditorComponent { //this.selectedScenario.saved = false; this.markUnsaved(); break; + + case 'block-editor': + this.deactivateOnIteration(this.selectedBlock.stepDefinitions); + break; + default: break; } @@ -1136,18 +1349,19 @@ export class BaseEditorComponent { for (const s in stepsList[prop]) { if (stepsList[prop][s].checked) { stepsList[prop][s].deactivated = !stepsList[prop][s].deactivated; + stepsList[prop][s].checked = !stepsList[prop][s].checked; } } - } + } } } - + /** * Removes a step * */ - removeStep () { + removeStep() { switch (this.templateName) { case 'background': this.removeStepOnIteration(this.selectedStory.background.stepDefinitions); @@ -1178,19 +1392,23 @@ export class BaseEditorComponent { case 'example': for (let i = this.selectedScenario.stepDefinitions.example.length - 1; i > 0; i--) { if (this.selectedScenario.stepDefinitions.example[i].checked) { - if(i - 1 == 0 && this.selectedScenario.stepDefinitions.example.length - 2 == 0) - { + if (i - 1 == 0 && this.selectedScenario.stepDefinitions.example.length - 2 == 0) { this.deleteRows(); this.selectedScenario.stepDefinitions.example = [] this.markUnsaved(); - return + return } - this.selectedScenario.stepDefinitions.example.splice(i,1); + this.selectedScenario.stepDefinitions.example.splice(i, 1); } } this.exampleService.updateExampleTableEmit(); this.markUnsaved(); break; + + case 'block-editor': + this.removeStepOnIteration(this.selectedBlock.stepDefinitions); + break; + default: break; } @@ -1207,20 +1425,26 @@ export class BaseEditorComponent { if (this.templateName !== 'example' && prop !== 'example') { for (let i = stepsList[prop].length - 1; i >= 0; i--) { if (stepsList[prop][i].checked) { + if(stepsList[prop][i]._blockReferenceId){ + this.blockService.checkRefOnRemoveEmitter(stepsList[prop][i]._blockReferenceId); + } stepsList[prop].splice(i, 1); } } } } + if (this.allChecked) { + this.activeActionBar = !this.activeActionBar; + } } - + /** * Copy a block * */ - copyBlock():void { - + copyBlock(): void { + //const copyBlock = {given: [], when: [], then: [], example: []}; let block; let backgroundBlock: Block; @@ -1235,7 +1459,7 @@ export class BaseEditorComponent { } } }*/ - backgroundBlock = {stepDefinitions: block}; + backgroundBlock = { stepDefinitions: block }; sessionStorage.setItem('scenarioBlock', JSON.stringify(backgroundBlock)); this.toastr.success('successfully copied', 'Step(s)'); break; @@ -1252,22 +1476,29 @@ export class BaseEditorComponent { } } }*/ - const scenarioBlock: Block = {stepDefinitions: block}; + const scenarioBlock: Block = { stepDefinitions: block }; sessionStorage.setItem('scenarioBlock', JSON.stringify(scenarioBlock)); this.toastr.success('successfully copied', 'Step(s)'); break; case 'example': - block =[]; + block = []; for (let i = this.selectedScenario.stepDefinitions.example.length - 1; i > 0; i--) { if (this.selectedScenario.stepDefinitions.example[i].checked) { block.push(this.selectedScenario.stepDefinitions.example[i]) } } - const exampleBlock: Block = {stepDefinitions: {given: [], when: [], then: [], example: block}}; + const exampleBlock: Block = { stepDefinitions: { given: [], when: [], then: [], example: block } }; sessionStorage.setItem('copiedExampleBlock', JSON.stringify(exampleBlock)); this.toastr.success('successfully copied', 'Examples'); - break; + break; + + case 'block-editor': + block = this.addStepsToBlockOnIteration(this.selectedBlock.stepDefinitions); + const editBlock: Block = { stepDefinitions: block }; + sessionStorage.setItem('copiedEditBlock', JSON.stringify(editBlock)); + this.toastr.success('successfully copied', 'Step(s)'); + break; default: break; @@ -1282,25 +1513,25 @@ export class BaseEditorComponent { */ addStepsToBlockOnIteration(stepList) { let stepsList = JSON.parse(JSON.stringify(stepList)) - const copyBlock = {given: [], when: [], then: [], example: []}; - const stepsListIterate = {given: [], when: [], then: []}; - let examplesToBeCopied=[] + const copyBlock = { given: [], when: [], then: [], example: [] }; + const stepsListIterate = { given: [], when: [], then: [] }; + let examplesToBeCopied = [] Object.keys(stepsListIterate).forEach((key, _) => { - for (const s in stepsList[key]) { - if (stepsList[key][s].checked) { - copyBlock[key].push(stepsList[key][s]); - stepsList[key][s].values.forEach((value, index) => { - if (stepsList[key][s].isExample[index]) { - examplesToBeCopied.push(value.slice(1,-1)) - } - }); - } + for (const s in stepsList[key]) { + if (stepsList[key][s].checked) { + copyBlock[key].push(stepsList[key][s]); + stepsList[key][s].values.forEach((value, index) => { + if (stepsList[key][s].isExample[index]) { + examplesToBeCopied.push(value.slice(1, -1)) + } + }); } + } }); if (examplesToBeCopied.length > 0) { let indexList = [] stepsList['example'][0].values.forEach((value, index) => { - if (examplesToBeCopied.includes(value)){ + if (examplesToBeCopied.includes(value)) { indexList.push(index) } }); @@ -1313,12 +1544,11 @@ export class BaseEditorComponent { return copyBlock } - /** * Insert block from clipboard * */ - insertCopiedBlock(): void{ + insertCopiedBlock(): void { switch (this.templateName) { case 'background': Object.keys(this.clipboardBlock.stepDefinitions).forEach((key, _) => { @@ -1336,7 +1566,7 @@ export class BaseEditorComponent { case 'scenario': //this.insertStepsWithExamples() - if (this.clipboardBlock.stepDefinitions['example'].length != 0){ + if (this.clipboardBlock.stepDefinitions['example'].length != 0) { this.apiService.nameOfComponent('copyExampleToast'); this.apiService.setToastrOptions('Copy with multiple scenario(s)', 'Copy without multiple scenario(s)'); this.toastr.info('Do you want to copy it?', 'Block contains muliple scenario(s)', { @@ -1347,11 +1577,9 @@ export class BaseEditorComponent { Object.keys(this.clipboardBlock.stepDefinitions).forEach((key, _) => { if (key != 'example') { this.clipboardBlock.stepDefinitions[key].forEach((step: StepType, j) => { - //to prevent blocks to be checked after pasting - step.checked = false; - this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); - }); - } + this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); + }); + } }); this.markUnsaved(); } @@ -1369,6 +1597,16 @@ export class BaseEditorComponent { this.markUnsaved(); break; + case 'block-editor': + Object.keys(this.clipboardBlock.stepDefinitions).forEach((key, _) => { + this.clipboardBlock.stepDefinitions[key].forEach((step: StepType) => { + step.checked = false; + this.selectedBlock.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); + }); + + }); + break; + default: break; } @@ -1379,11 +1617,11 @@ export class BaseEditorComponent { * @param indices indices of names to change * @param num number to append to the name */ - changeExampleName(block, indices, num){ + changeExampleName(block, indices, num) { indices.forEach(index => { let oldName = block.stepDefinitions['example'][0].values[index] let newName - if (num > 1){ + if (num > 1) { newName = oldName.split(' - ')[0] + ' - ' + num } else { newName = oldName + ' - ' + num @@ -1393,8 +1631,8 @@ export class BaseEditorComponent { if (key != 'example') { block.stepDefinitions[key].forEach((step: StepType, i) => { step.values.forEach((value, j) => { - if(value === '<'+oldName+'>'){ - block.stepDefinitions[key][i].values[j] = '<'+newName+'>' + if (value === '<' + oldName + '>') { + block.stepDefinitions[key][i].values[j] = '<' + newName + '>' } }); }); @@ -1407,24 +1645,24 @@ export class BaseEditorComponent { * Inserts copied steps with examples * Checks for unique example names */ - insertStepsWithExamples(block){ - if (this.selectedScenario.stepDefinitions['example'].length!=0) { - let indices = this.selectedScenario.stepDefinitions['example'][0].values.map(x => block.stepDefinitions['example'][0].values.indexOf(x)).filter(x => x!=-1); + insertStepsWithExamples(block) { + if (this.selectedScenario.stepDefinitions['example'].length != 0) { + let indices = this.selectedScenario.stepDefinitions['example'][0].values.map(x => block.stepDefinitions['example'][0].values.indexOf(x)).filter(x => x != -1); let num = 1; while (indices.length > 0) { this.changeExampleName(block, indices, num); num++; - indices = this.selectedScenario.stepDefinitions['example'][0].values.map(x => block.stepDefinitions['example'][0].values.indexOf(x)).filter(x => x!=-1) + indices = this.selectedScenario.stepDefinitions['example'][0].values.map(x => block.stepDefinitions['example'][0].values.indexOf(x)).filter(x => x != -1) } } Object.keys(block.stepDefinitions).forEach((key, _) => { if (key != 'example') { block.stepDefinitions[key].forEach((step: StepType, j) => { - this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); - }); - } else if (key == 'example') { + this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(step))); + }); + } else if (key == 'example') { this.insertCopiedExamples(block) - } + } }); this.markUnsaved(); @@ -1439,14 +1677,14 @@ export class BaseEditorComponent { if (key != 'example') { this.clipboardBlock.stepDefinitions[key].forEach((step: StepType, j) => { let stepCopy = JSON.parse(JSON.stringify(step)) - step.isExample.forEach((isExample, index) =>{ + step.isExample.forEach((isExample, index) => { if (isExample) { stepCopy.isExample[index] = false stepCopy.values[index] = "" } }) - this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(stepCopy))); - }); + this.selectedScenario.stepDefinitions[key].push(JSON.parse(JSON.stringify(stepCopy))); + }); } }); this.markUnsaved(); @@ -1460,12 +1698,12 @@ export class BaseEditorComponent { insertCopiedExamples(block) { const selectedExampleDefs = this.selectedScenario.stepDefinitions['example']; const blockExampleDefs = block.stepDefinitions['example']; - + if (selectedExampleDefs.length === 0) { this.selectedScenario.stepDefinitions['example'] = blockExampleDefs; return; } - + if (selectedExampleDefs.length === blockExampleDefs.length) { this.insertValuesIntoSelectedExamples(selectedExampleDefs, blockExampleDefs); } else if (selectedExampleDefs.length < blockExampleDefs.length) { @@ -1478,7 +1716,7 @@ export class BaseEditorComponent { this.exampleService.updateExampleTableEmit(); this.markUnsaved() } - + insertValuesIntoSelectedExamples(selectedExampleDefs, blockExampleDefs, useSelectedLength = false) { const length = useSelectedLength ? selectedExampleDefs.length : blockExampleDefs.length; for (let i = 0; i < length; i++) { @@ -1487,7 +1725,7 @@ export class BaseEditorComponent { }); } } - + insertNewExamples(selectedExampleDefs, blockExampleDefs) { const selectedLength = selectedExampleDefs.length; for (let i = selectedLength; i < blockExampleDefs.length; i++) { @@ -1502,7 +1740,7 @@ export class BaseEditorComponent { } } } - + insertPlaceholderValues(selectedExampleDefs, length) { selectedExampleDefs.forEach(element => { for (let i = element.values.length; i < length; i++) { @@ -1510,19 +1748,58 @@ export class BaseEditorComponent { } }); } - - - + + + /** * Opens add block modal * */ addBlock() { - const id = localStorage.getItem('id'); + const id = localStorage.getItem('id'); this.addBlockModal.openAddBlockFormModal(this.templateName, id); } + /** + * Block methods + */ + + editBlock() { + this.editBlockModal.openEditBlockModal(); + const x = document.getElementsByClassName('stepBlockContainer')[0]; + x.setAttribute('aria-expanded', 'false'); + } + + /** + * Methods for referenced Blocks (only implemented for scenarios) + */ + + + /** + * Select Block by blockId to get Block Object + * @param blockId: _id of the Block + */ + selectBlock(block) { + this.blockSelectTriggerEvent.emit(block); + this.expandStepBlock = true; + } + + /** + * Unselect Block and reset selected Block + */ + unselectBlock() { + this.expandStepBlock = false; + } + + showUnpackBlockToast(block) { + this.apiService.nameOfComponent('unpackBlock'); + this.blockService.block = block; + this.toastr.warning( + 'Unpacking the Block will remove its reference to the original Block! Do you want to unpack the block?', 'Unpack Block', { + toastComponent: DeleteToast + }); + } /* Example case methods */ @@ -1532,11 +1809,14 @@ export class BaseEditorComponent { * List all examples from scenario * @returns returns all examples in list */ - getExampleList(){ - if(this.selectedScenario.stepDefinitions.example && this.selectedScenario.stepDefinitions.example.length && this.selectedScenario.stepDefinitions.example[0].values.length){ - return this.selectedScenario.stepDefinitions.example[0].values + getExampleList() { + if (this.templateName != 'block-editor') { + if (this.selectedScenario.stepDefinitions.example && this.selectedScenario.stepDefinitions.example.length && this.selectedScenario.stepDefinitions.example[0].values.length) { + return this.selectedScenario.stepDefinitions.example[0].values + } + return undefined } - return undefined + } /** @@ -1566,40 +1846,40 @@ export class BaseEditorComponent { * @param newName * @param index index of example in values array */ - renameExample(newName, index){ + renameExample(newName, index) { if (this.templateName == 'example') { let oldName = this.selectedScenario.stepDefinitions.example[0].values[index] - this.selectedScenario.stepDefinitions.example[0].values[index] = newName - this.uncutInputs[this.uncutInputs.indexOf('<'+oldName+'>')] = '<'+newName+'>'; + this.selectedScenario.stepDefinitions.example[0].values[index] = newName + this.uncutInputs[this.uncutInputs.indexOf('<' + oldName + '>')] = '<' + newName + '>'; this.selectedScenario.stepDefinitions.given.forEach((value, index) => { value.values.forEach((val, i) => { - if(val == '<'+oldName+'>') { - this.selectedScenario.stepDefinitions.given[index].values[i] = '<'+newName+'>' + if (val == '<' + oldName + '>') { + this.selectedScenario.stepDefinitions.given[index].values[i] = '<' + newName + '>' } }) }) this.selectedScenario.stepDefinitions.when.forEach((value, index) => { value.values.forEach((val, i) => { - if(val == '<'+oldName+'>') { - this.selectedScenario.stepDefinitions.when[index].values[i] = '<'+newName+'>' + if (val == '<' + oldName + '>') { + this.selectedScenario.stepDefinitions.when[index].values[i] = '<' + newName + '>' } }) }) this.selectedScenario.stepDefinitions.then.forEach((value, index) => { value.values.forEach((val, i) => { - if(val == '<'+oldName+'>') { - this.selectedScenario.stepDefinitions.then[index].values[i] = '<'+newName+'>' + if (val == '<' + oldName + '>') { + this.selectedScenario.stepDefinitions.then[index].values[i] = '<' + newName + '>' } }) }); this.exampleService.updateExampleTableEmit(); this.markUnsaved(); } - - } + + } /** @@ -1618,7 +1898,7 @@ export class BaseEditorComponent { this.fillExamples(input, step); } - } + } /** * Fill all example values after an example step was added @@ -1661,7 +1941,7 @@ export class BaseEditorComponent { const table = document.getElementsByClassName('mat-mdc-table')[0]; if (table) { table.classList.add('mat-mdc-elevation-z8'); } - } + } /** * Adds an example step @@ -1681,15 +1961,15 @@ export class BaseEditorComponent { /** * Add Value Row */ - addExampleValueRow(){ + addExampleValueRow() { console.log("selected scenario: ", this.selectedScenario.stepDefinitions) let row = JSON.parse(JSON.stringify(this.selectedScenario.stepDefinitions.example[0])) - row.values.forEach((value, index) => { - row.values[index] = 'value' - }); - this.selectedScenario.stepDefinitions.example.push(row) + row.values.forEach((value, index) => { + row.values[index] = 'value' + }); + this.selectedScenario.stepDefinitions.example.push(row) this.exampleService.updateExampleTableEmit(); this.markUnsaved(); - } - + } + } diff --git a/frontend/src/app/delete-toast.ts b/frontend/src/app/delete-toast.ts index 6d56385dd..8e7b1ba40 100644 --- a/frontend/src/app/delete-toast.ts +++ b/frontend/src/app/delete-toast.ts @@ -132,6 +132,12 @@ import { StoryService } from './Services/story.service'; ) { super(toastrService, toastPackage); + this.nameComponent = this.apiService.getNameOfComponent(); + if (this.nameComponent == 'unpackBlock') { + this.deleteString = 'Unpack'; + }else { + this.deleteString = 'Delete'; + } } /** @@ -151,6 +157,12 @@ import { StoryService } from './Services/story.service'; case 'repository': this.projectService.deleteRepositoryEmitter(); break; case 'block': this.blockService.deleteBlockEmitter(); + break; + case 'unpackBlock':{ + const blockToUnpack = this.blockService.block; + this.blockService.unpackBlockEmitter(blockToUnpack); + } + break; } this.remove(); } diff --git a/frontend/src/app/directives/resize-input.directive.ts b/frontend/src/app/directives/resize-input.directive.ts index bb005b301..d1b05665d 100644 --- a/frontend/src/app/directives/resize-input.directive.ts +++ b/frontend/src/app/directives/resize-input.directive.ts @@ -32,12 +32,12 @@ export class ResizeInputDirective { setTimeout(() => { el.nativeElement.classList.forEach((value) => { - if (value == 'scenario' || value == 'background') { + if (value === 'scenario' || value === 'background' || value === 'block-editor') { this.class = value; } }); - if (this.class === 'background' || this.class === 'scenario') { + if (this.class === 'scenario' || this.class === 'background' || this.class === 'block-editor') { this.maxWidth = this.containerEl.offsetWidth; } this.resize('load'); @@ -79,7 +79,7 @@ export class ResizeInputDirective { * @returns */ private setParentWidth() { - if (this.class === 'background' || this.class === 'scenario') { + if (this.class === 'scenario' || this.class === 'background' || this.class === 'block-editor') { return this.parentEl.offsetWidth; } } diff --git a/frontend/src/app/login/login.component.css b/frontend/src/app/login/login.component.css index 532280549..6dda7332f 100644 --- a/frontend/src/app/login/login.component.css +++ b/frontend/src/app/login/login.component.css @@ -43,26 +43,6 @@ h1{ color: var(--light-blue); } -.repositories{ - grid-area: repositories; - grid-row: 3 / 4; - grid-column: 3 / 4; - /*width: 400px;*/ - text-align: center; - margin-right: auto; - margin-bottom: 5%; - margin-left: auto; - padding: 25px; - background-color:rgb(224, 224, 224); - border: 1px solid #999; -} - -.darkTheme .repositories { - background: #455A64; - border: 1px solid #60767b -} - - .howToStart { grid-area: howToStart; grid-row: 2 / 3; @@ -157,38 +137,6 @@ h1{ color: black; } -.repo_img{ - margin-left: 5px; - margin-right: 5px; - max-height: 18px; -} - -.repositoryList{ - list-style: none; -} - -a.repoLink{ - color: var(--ocean-blue); - transition: 0.2s; -} - -.darkTheme a.repoLink { - color: var(--light-blue); -} - -a.repoLink:hover{ - color: var(--marine-blue); -} - -.darkTheme a.repoLink:hover { - color: white; -} - -.wrongLogin{ - background-color: var(--grape); - text-align: center; -} - .loginFont{ color:var(--ocean-blue); } @@ -274,7 +222,7 @@ a.repoLink:hover{ .grid{ display: grid; grid-template-rows: auto; - grid-template-areas: "formular" "repositories"; + grid-template-areas: "formular"; } } @@ -307,25 +255,6 @@ a.repoLink:hover{ padding: 40px; } - .repositories{ - grid-area: repositories; - grid-row: 3 / 4; - grid-column: 3 / 4; - width: 400px; - text-align: center; - margin-right: 100px; - margin-bottom: 80px; - margin-top: -30px; - padding: 15px; - background-color:rgb(224, 224, 224); - border: 1px solid #999; - } - -} - -.darkTheme .repositories { - background-color: #37474F; - border-color:#60767b; } /* .darkTheme button { diff --git a/frontend/src/app/login/login.component.html b/frontend/src/app/login/login.component.html index c69698228..35e7a953f 100644 --- a/frontend/src/app/login/login.component.html +++ b/frontend/src/app/login/login.component.html @@ -13,7 +13,9 @@
-

{{error}}

+
+ Login Failed: {{error}} +

Login

@@ -68,19 +70,4 @@

Login

- -
-
- -
- -
-
diff --git a/frontend/src/app/login/login.component.ts b/frontend/src/app/login/login.component.ts index 988ca10a8..814165777 100644 --- a/frontend/src/app/login/login.component.ts +++ b/frontend/src/app/login/login.component.ts @@ -28,7 +28,7 @@ export class LoginComponent implements OnInit, AfterViewInit { * Login error */ error: string; - defaultErrorMessage = 'Login Failed: Wrong Username Or Password'; + defaultErrorMessage = 'Wrong Username Or Password'; /** * Boolean to see if the repository is loading @@ -217,6 +217,7 @@ export class LoginComponent implements OnInit, AfterViewInit { * Retrieves the repositories / projects of the user */ getRepositories() { + let repoNotSet = false; const value = localStorage.getItem('repository'); const source = localStorage.getItem('source'); const _id = localStorage.getItem('id'); @@ -234,15 +235,14 @@ export class LoginComponent implements OnInit, AfterViewInit { resp.forEach((elem) => { if (elem.value == repository.value && elem.source == repository.source && elem._id == repository._id) { this.router.navigate(['']); - }}); + repoNotSet = true; + } + }); + if(!repoNotSet){ + this.router.navigate(['/accountManagement']); + } this.repositories = resp; this.isLoadingRepositories = false; - setTimeout(() => { - const repositoriesList: HTMLElement = document.getElementById('repositoriesList'); - if (repositoriesList) { - repositoriesList.scrollIntoView(); - } - }, 500); }, (_) => { this.error = this.defaultErrorMessage; @@ -250,19 +250,6 @@ export class LoginComponent implements OnInit, AfterViewInit { }); } - /** - * Selects a repository and redirects the user to the story editor - * @param userRepository selected repository - */ - selectRepository(userRepository: RepositoryContainer) { - const ref: HTMLLinkElement = document.getElementById('githubHref') as HTMLLinkElement; - ref.href = 'https://github.com/' + userRepository.value; - localStorage.setItem('repository', userRepository.value); - localStorage.setItem('source', userRepository.source); - localStorage.setItem('id', userRepository._id); - this.router.navigate(['']); - } - /** * Loggs in the user with Github */ diff --git a/frontend/src/app/modals/add-block-form/add-block-form.component.css b/frontend/src/app/modals/add-block-form/add-block-form.component.css index d5ac87143..b6705bc4f 100644 --- a/frontend/src/app/modals/add-block-form/add-block-form.component.css +++ b/frontend/src/app/modals/add-block-form/add-block-form.component.css @@ -14,6 +14,9 @@ display: flex; justify-content: center; } +.disabled { + color: gray !important; +} .blockButton{ border: none; @@ -78,7 +81,7 @@ td.mat-mdc-cell:first-of-type, td.mat-mdc-footer-cell:first-of-type{ } .previewContainer label{ cursor: text; - + word-break: break-word; } .previewContainer div{ cursor: default; @@ -100,7 +103,23 @@ td.mat-mdc-cell:first-of-type, td.mat-mdc-footer-cell:first-of-type{ margin-right: 5px; } -.mat-mdc-table{ +#addBlockFooter{ + justify-content: space-between; +} + + +#addAsReferenceLabel{ + margin-right: 5px; + margin-top: 3px +} + +#addAsReferenceCheckbox{ + margin-left: 5px; + margin-right: 10px; + margin-top: 3px +} + +.mat-table{ font-family: Klavika, sans-serif; } diff --git a/frontend/src/app/modals/add-block-form/add-block-form.component.html b/frontend/src/app/modals/add-block-form/add-block-form.component.html index 1c13832b2..8ab7607a1 100644 --- a/frontend/src/app/modals/add-block-form/add-block-form.component.html +++ b/frontend/src/app/modals/add-block-form/add-block-form.component.html @@ -7,8 +7,8 @@ Add a saved Block. These Steps will get appended to the steps of this story. - -
@@ -28,10 +28,10 @@
Preview:
- -
- - \ No newline at end of file + diff --git a/frontend/src/app/modals/add-block-form/add-block-form.component.ts b/frontend/src/app/modals/add-block-form/add-block-form.component.ts index 6f5134b5e..787622af6 100644 --- a/frontend/src/app/modals/add-block-form/add-block-form.component.ts +++ b/frontend/src/app/modals/add-block-form/add-block-form.component.ts @@ -17,7 +17,7 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { @ViewChild('addBlockFormModal') addBlockFormModal: any; @ViewChild('newTitle') newTitleLabel: HTMLElement; - @ViewChild('saveBlockButton') saveBlockButton: HTMLElement; + /** * Saved blocks @@ -37,6 +37,10 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { * If blocks are saved */ blockSaved: boolean; + /** + * If save button is disable + */ + saveBlockButtonDisable: boolean; /** * Steps of the current block @@ -68,6 +72,11 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { */ clipboardBlock: Block; + /** + * Boolean, wether Block should be added as reference + */ + addAsReference: boolean; + modalReference: NgbModalRef; deleteBlockObservable: Subscription; @@ -83,7 +92,7 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { }); this.deleteBlockObservable = this.blockService.deleteBlockEvent.subscribe(_ => { this.blockDeleted(this.selectedBlock); - }); + }); } ngOnDestroy() { @@ -127,7 +136,17 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { */ deleteBlock() { this.apiService.nameOfComponent('block'); - this.toastr.warning('Are your sure you want to delete this block? It cannot be restored.', 'Delete Block?', { + let warningMessage = ''; + let warningQuestion = ''; + if(this.selectedBlock.usedAsReference){ + warningMessage = "This block has a references and you're going to delete them. It cannot be restored." + warningQuestion = 'Delete this block and all its references?' + } + else { + warningMessage = 'Are you sure you want to delete this block? It cannot be restored.'; + warningQuestion = 'Delete Block?'; + } + this.toastr.warning(warningMessage, warningQuestion, { toastComponent: DeleteToast }); } @@ -141,6 +160,9 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { this.blockService .deleteBlock(block._id) .subscribe((resp) => { + if(block.usedAsReference){ + this.blockService.deleteReferenceEmitter(block); + } this.blocks.splice(this.blocks.findIndex(x => x === this.selectedBlock), 1); this.stepList = []; this.selectedBlock = null; @@ -166,17 +188,21 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { * Check if a new block name is valid */ checkName(){ - this.saveBlockButton = document.getElementById("saveBlockButton"); - this.newblockName = this.newTitleLabel.textContent.replace(/\s/g, ''); - if(this.newblockName.trim().length === 0){ - this.saveBlockButton.setAttribute('disabled', 'true'); - this.toastr.warning('', 'The field can not be empty. Enter the name.', {}); + this.newblockName = this.newTitleLabel.textContent; + if (this.newTitleLabel.getAttribute('contentEditable') === 'true') { + const pattern = /^\S(.*\S)?$/; + if (!pattern.test(this.newblockName)) { + this.saveBlockButtonDisable = true; + return; + } } - else if((this.newblockName && !this.blocks.find(i => i.name === this.newblockName)) || (this.selectedBlock ? this.blocks.find(g => g._id === this.selectedBlock._id && g.name === this.newblockName) : false)) { - this.saveBlockButton.removeAttribute('disabled'); - } - else { - this.saveBlockButton.setAttribute('disabled', 'true'); + if (this.newblockName.trim().length === 0) { + this.saveBlockButtonDisable = true; + this.toastr.warning('', 'The field can not be empty. Enter the name.', {}); + } else if ((!this.blocks.find(i => i.name === this.newblockName)) || (this.selectedBlock ? this.blocks.find(g => g._id === this.selectedBlock._id && g.name === this.newblockName) : false)) { + this.saveBlockButtonDisable = false; + } else { + this.saveBlockButtonDisable = true; this.toastr.warning('', 'This name exists already. Enter unique name.', {}); } } @@ -202,7 +228,7 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { //to avoid an error if the user selects another block when new name is undefined if(this.newTitleLabel !== undefined) { - if(this.newTitleLabel.textContent.replace(/\s/g, '').trim().length === 0) + if(this.newTitleLabel.textContent.trim().length === 0) this.newTitleLabel.textContent = this.selectedBlock.name; } } @@ -212,7 +238,7 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { */ copiedBlock() { if (this.clipboardBlock) { - this.blockService.addBlockToScenario(this.clipboardBlock, this.correspondingComponent); + this.blockService.addBlockToScenario(this.clipboardBlock, this.correspondingComponent, false); } } @@ -220,7 +246,8 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { * Adds a block to saved blocks */ addBlockFormSubmit() { - this.blockService.addBlockToScenario(this.selectedBlock, this.correspondingComponent); + this.blockService.addBlockToScenario(this.selectedBlock, this.correspondingComponent, this.addAsReference); + delete this.addAsReference; this.modalReference.close(); } @@ -228,26 +255,33 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { * Update block when title is changed */ updateBlock(){ - if(this.newblockName == undefined){//if user has not entered anything, name saves without changes - this.newblockName = this.selectedBlock.name; - } else{ - this.selectedBlock.name = this.newblockName; - this.blockService - .updateBlock(this.selectedBlock) - .subscribe(_ => { - this.updateBlocksEventEmitter(); - this.toastr.success('successfully saved', 'Block'); - }); + if(this.saveBlockButtonDisable == false){ + if(this.newblockName == undefined){//if user has not entered anything, name saves without changes + this.newblockName = this.selectedBlock.name; + } else{ + this.selectedBlock.name = this.newblockName; + this.blockService + .updateBlock(this.selectedBlock) + .subscribe(_ => { + this.updateBlocksEventEmitter(); + this.toastr.success('successfully saved', 'Block'); + }); + } + this.newTitleLabel.setAttribute('contentEditable', 'false'); + this.newTitleLabel.style.border = 'none'; + this.blockSaved = true; } - this.newTitleLabel.setAttribute('contentEditable', 'false'); - this.newTitleLabel.style.border = 'none'; - this.blockSaved = true; + } updateBlocksEventEmitter() { this.blockService.updateBlocksEvent.emit(); } + checkAddAsReference() { + this.addAsReference = (!this.addAsReference); + } + enterSubmit(event) { this.addBlockFormSubmit(); } @@ -255,4 +289,8 @@ export class AddBlockFormComponent implements OnInit,OnDestroy { onClickSubmit() { this.addBlockFormSubmit(); } + closeModal(){ + delete this.addAsReference; + this.modalReference.close(); + } } diff --git a/frontend/src/app/modals/create-new-story/create-new-story.component.html b/frontend/src/app/modals/create-new-story/create-new-story.component.html index b4689ad56..89e7d4ddf 100644 --- a/frontend/src/app/modals/create-new-story/create-new-story.component.html +++ b/frontend/src/app/modals/create-new-story/create-new-story.component.html @@ -3,7 +3,7 @@ diff --git a/frontend/src/app/modals/create-new-story/create-new-story.component.ts b/frontend/src/app/modals/create-new-story/create-new-story.component.ts index d94625a74..ef2183cc0 100644 --- a/frontend/src/app/modals/create-new-story/create-new-story.component.ts +++ b/frontend/src/app/modals/create-new-story/create-new-story.component.ts @@ -68,11 +68,16 @@ export class CreateNewStoryComponent { const story = {title, description}; this.storyService.createCustomStoryEvent({repositoryContainer, story}); } + this.storyForm.reset({storyTitle:'', storyDescription:''}); this.modalReference.close(); } storyUnique() { this.storyService.storyUnique('submitCreateNewStory', this.storyForm.value.storyTitle, this.stories, this.story); } + close(modal){ + this.storyForm.reset({storyTitle:'', storyDescription:''}); + modal.dismiss('Cross click'); + } } diff --git a/frontend/src/app/modals/edit-block/edit-block.component.css b/frontend/src/app/modals/edit-block/edit-block.component.css new file mode 100644 index 000000000..3fea787b9 --- /dev/null +++ b/frontend/src/app/modals/edit-block/edit-block.component.css @@ -0,0 +1,62 @@ +.warningIcon { + color: white !important; +} + +.BlockHeaderContainer { + display: grid; + grid-template-columns: 1fr auto 1fr auto; + padding: 0 20px; +} + +.BlockHeaderWarning { + display: flex; + justify-content: space-evenly; + text-align: center; + background-color: #ff9800; + color: white; + margin: 2.5% auto; + align-items: center; + font-size: 14px; + border-radius: 15px; + padding: 10px 0; + width: 70%; + max-width: 600px; + min-width: 300px; +} + +@media screen and (min-width: 500px) { + + + ::ng-deep body>ngb-modal-window>.edit-block.modal-dialog { + + max-width: none !important; + + } + +} + + +::ng-deep body>ngb-modal-window>.edit-block.modal-dialog { + + width: 55%; + + min-width: 1000px; + +} + +::ng-deep body>ngb-modal-window.modal { + + z-index: 1; + +} + +:host ::ng-deep .darkTheme.uk-dropdown { + background-color: #60767b; + color: var(--light-blue); +} + +.infoIconHeader{ + color: var(--white); + margin-left: 5px; + margin-right: 5px; +} \ No newline at end of file diff --git a/frontend/src/app/modals/edit-block/edit-block.component.html b/frontend/src/app/modals/edit-block/edit-block.component.html new file mode 100644 index 000000000..f34c16400 --- /dev/null +++ b/frontend/src/app/modals/edit-block/edit-block.component.html @@ -0,0 +1,49 @@ + + + +
+
+
+ warning +

Changes to a Block will be applied to EVERY Scenario referencing this Block.
+ This might result in Breaking Changes!

+ warning +
+
+
+ Block + + A Block contains Steps and can be used across + Scenarios. + Scenarios, that have a Reference to the Block will be changed, if the Block is changed. + + +
+
+ # {{selectedBlock.name + }} +
+
+
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/modals/edit-block/edit-block.component.spec.ts b/frontend/src/app/modals/edit-block/edit-block.component.spec.ts new file mode 100644 index 000000000..9c1acb1e0 --- /dev/null +++ b/frontend/src/app/modals/edit-block/edit-block.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditBlockComponent } from './edit-block.component'; + +describe('EditBlockComponent', () => { + let component: EditBlockComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ EditBlockComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EditBlockComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/modals/edit-block/edit-block.component.ts b/frontend/src/app/modals/edit-block/edit-block.component.ts new file mode 100644 index 000000000..658a9581f --- /dev/null +++ b/frontend/src/app/modals/edit-block/edit-block.component.ts @@ -0,0 +1,100 @@ +import { Component, Input, ViewChild } from '@angular/core'; +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'; +import { Block } from 'src/app/model/Block'; +import { BlockService } from 'src/app/Services/block.service'; +import { StepType } from 'src/app/model/StepType'; +import { ThemingService } from '../../Services/theming.service'; +import { Subscription } from 'rxjs'; + +@Component({ + selector: 'app-edit-block', + templateUrl: './edit-block.component.html', + styleUrls: ['./edit-block.component.css'] +}) +export class EditBlockComponent { + + @ViewChild('editBlockModal') editBlockModal: any; + + /** + * Original step types not sorted or changed + */ + @Input() originalStepTypes: StepType[]; + + /** + * Currently selected block + */ + @Input() selectedBlock: Block; + + isDark: boolean; + + clipboardBlock: Block = null; + + modalReference: NgbModalRef; + + testRunning = false; + + readonly TEMPLATE_NAME = 'block-editor'; + themeObservable: Subscription; + /** + * Subscriptions for all EventEmitter + */ + + constructor(private modalService: NgbModal, public blockService: BlockService, public themeService: ThemingService) { + + } + + ngOnInit() { + this.isDark = this.themeService.isDarkMode(); + this.themeObservable = this.themeService.themeChanged.subscribe(() => { + this.isDark = this.themeService.isDarkMode(); + }); + } + + /** + * Opens the edit block form modal + */ + openEditBlockModal() { + this.modalReference = this.modalService.open(this.editBlockModal, { ariaLabelledBy: 'modal-basic-title', modalDialogClass: 'edit-block' }); + this.clipboardBlock = JSON.parse(sessionStorage.getItem('copiedBlock')); + Object.keys(this.selectedBlock.stepDefinitions).forEach((key, _) => { + this.selectedBlock.stepDefinitions[key].forEach((step: StepType) => { + //to prevent blocks to be checked after pasting + step.checked = false; + }); + }); + } + + editBlockSubmit() { + console.log(this.selectedBlock); + this.blockService.editBlock(this.selectedBlock).subscribe((resp) => { + + console.log(resp); + + this.updateBlocksEventEmitter(); + + }); + + console.log('successfully subscirbed'); + this.modalReference.close(); + console.log('successfully closed'); + } + + updateBlocksEventEmitter() { + + this.blockService.updateBlocksEvent.emit(); + + } + + enterSubmit(event) { + if (event.keyCode === 13) { + this.editBlockSubmit(); + } + } + + onClickSubmit() { + console.log('Go to editBlockSubmit function') + this.editBlockSubmit(); + } +} + + diff --git a/frontend/src/app/modals/save-block-form/save-block-form.component.ts b/frontend/src/app/modals/save-block-form/save-block-form.component.ts index fea718f81..17e44f7bc 100644 --- a/frontend/src/app/modals/save-block-form/save-block-form.component.ts +++ b/frontend/src/app/modals/save-block-form/save-block-form.component.ts @@ -121,13 +121,23 @@ export class SaveBlockFormComponent implements OnInit, OnDestroy { this.stepListSaveBlock = []; this.stepListSaveBlockExample = []; this.displayedColumnsSaveBlockExample = []; + let toastrShown = false; Object.keys(this.block.stepDefinitions).forEach((key, _) => { this.block.stepDefinitions[key].forEach((step: StepType) => { if(step.stepType != 'example'){ - this.stepListSaveBlock.push(step); + if(step._blockReferenceId && !toastrShown){ + this.toastr.info("Please Note: To avoid complexity issues embedded blocks aren't allowed to be saved in another blocks ","You've selected at least one reference block to save in another block.", { + timeOut: 8000, + extendedTimeOut: 3000 + }); + toastrShown = true; + }else if(!step._blockReferenceId){ + this.stepListSaveBlock.push(step); + } } else { this.stepListSaveBlockExample.push(step); } + }); }); if(this.stepListSaveBlockExample.length > 0) { @@ -186,6 +196,7 @@ export class SaveBlockFormComponent implements OnInit, OnDestroy { if (this.backgroundService.backgroundReplaced && this.backgroundService.backgroundReplaced !== undefined){ this.block.isBackground = true; } + this.block.stepDefinitions.when = this.block.stepDefinitions.when.filter((step) => !step._blockReferenceId); this.blockService.saveBlock(this.block).subscribe((resp) => { console.log(resp); this.updateBlocksEventEmitter(); diff --git a/frontend/src/app/model/Block.ts b/frontend/src/app/model/Block.ts index bbbf8a534..5a60e1a53 100644 --- a/frontend/src/app/model/Block.ts +++ b/frontend/src/app/model/Block.ts @@ -42,5 +42,9 @@ export interface Block { * If block was saved as a background */ isBackground ?: boolean; + /** + * If the block is used as a reference + */ + usedAsReference?: boolean; -} \ No newline at end of file +} diff --git a/frontend/src/app/model/Scenario.ts b/frontend/src/app/model/Scenario.ts index 2fc9bf228..fece8fc4a 100644 --- a/frontend/src/app/model/Scenario.ts +++ b/frontend/src/app/model/Scenario.ts @@ -54,4 +54,9 @@ export interface Scenario { * Selected emultaor to execute the test with */ emulator?: string; + /** + * If scenario has a reference among a steps + */ + hasRefBlock?: boolean; + } diff --git a/frontend/src/app/model/StepDefinition.ts b/frontend/src/app/model/StepDefinition.ts index 35f4b6cde..f7c63c674 100644 --- a/frontend/src/app/model/StepDefinition.ts +++ b/frontend/src/app/model/StepDefinition.ts @@ -12,7 +12,7 @@ export interface StepDefinition { /** * When Steps */ - when: StepType[]; + when: StepType []; /** * Then Steps diff --git a/frontend/src/app/model/StepType.ts b/frontend/src/app/model/StepType.ts index a2063e974..9baecc315 100644 --- a/frontend/src/app/model/StepType.ts +++ b/frontend/src/app/model/StepType.ts @@ -2,10 +2,10 @@ * Interface of the step type */ export interface StepType { - /** - * Object id of the step type in the database + /** + * Object id of the reference block step / determine whether the step is a reference */ - _id?: string; + _blockReferenceId?: string; /** * Importance of the step type, the lower, the higher the importance -> the higher the step is the higher ordered it is in the list of available steps @@ -53,9 +53,9 @@ export interface StepType { values: string[]; /** - * List of booleans defining if value is a example + * List of booleans defining if value is a example. Not defined in Blocks. */ - isExample: boolean[]; + isExample?: boolean[]; /** * Outdated currently not used @@ -71,4 +71,5 @@ export interface StepType { * deactivated currently not used */ deactivated?: boolean; + } diff --git a/frontend/src/app/scenario-editor/scenario-editor.component.html b/frontend/src/app/scenario-editor/scenario-editor.component.html index ec172c814..907555925 100644 --- a/frontend/src/app/scenario-editor/scenario-editor.component.html +++ b/frontend/src/app/scenario-editor/scenario-editor.component.html @@ -52,8 +52,8 @@
- + diff --git a/frontend/src/app/scenario-editor/scenario-editor.component.ts b/frontend/src/app/scenario-editor/scenario-editor.component.ts index b7fe1c344..be6f3e855 100644 --- a/frontend/src/app/scenario-editor/scenario-editor.component.ts +++ b/frontend/src/app/scenario-editor/scenario-editor.component.ts @@ -9,7 +9,8 @@ import { RenameScenarioComponent } from '../modals/rename-scenario/rename-scenar import { Subscription } from 'rxjs'; import { CreateScenarioComponent } from '../modals/create-scenario/create-scenario.component'; import { ScenarioService } from '../Services/scenario.service'; - +import { BaseEditorComponent } from '../base-editor/base-editor.component'; +import { BlockService } from '../Services/block.service'; /** @@ -31,6 +32,7 @@ export class ScenarioEditorComponent implements OnInit{ */ constructor( public apiService: ApiService, + public blockService: BlockService, public scenarioService: ScenarioService, public toastr: ToastrService ) { @@ -58,7 +60,6 @@ export class ScenarioEditorComponent implements OnInit{ if (this.selectedStory && scenario) { this.selectScenario(scenario); } - } testRunning; @@ -104,10 +105,11 @@ export class ScenarioEditorComponent implements OnInit{ readonly TEMPLATE_NAME ='scenario'; /** - * Subscribtions for all EventEmitter + * Subscriptions for all EventEmitter */ runSaveOptionObservable: Subscription; renameScenarioObservable: Subscription; + updateRefObservable: Subscription; @Input() isDark: boolean; @@ -116,11 +118,22 @@ export class ScenarioEditorComponent implements OnInit{ */ @ViewChild('renameScenarioModal') renameScenarioModal: RenameScenarioComponent; @ViewChild('createScenarioModal') createScenarioModal: CreateScenarioComponent; + @ViewChild('baseEditor') baseEditor: BaseEditorComponent; /** * Original step types not sorted or changed */ @Input() originalStepTypes: StepType[]; + + /** + * List of Blocks + */ + blocks: Block []; + + /** + * Currently selected block + */ + selectedBlock: Block; /** * Event emitter to delete the scenario @@ -153,13 +166,21 @@ export class ScenarioEditorComponent implements OnInit{ * Subscribes to all necessary events */ ngOnInit() { + const id = localStorage.getItem('id'); + this.blockService.getBlocks(id).subscribe((resp) => { + this.blocks = resp; + }); this.runSaveOptionObservable = this.apiService.runSaveOptionEvent.subscribe(option => { if (option == 'saveScenario') { this.saveRunOption(); } }); - this.renameScenarioObservable = this.scenarioService.renameScenarioEvent.subscribe(newName => this.renameScenario(newName)); + this.updateRefObservable = this.blockService.updateBlocksEvent.subscribe(_ => { + this.blockService.getBlocks(id).subscribe((resp) => { + this.blocks = resp; + }); + }); } ngOnDestroy() { @@ -185,7 +206,7 @@ export class ScenarioEditorComponent implements OnInit{ * @returns */ updateScenario() { - + delete this.selectedScenario.hasRefBlock delete this.selectedScenario.saved; let steps = this.selectedScenario.stepDefinitions['given']; steps = steps.concat(this.selectedScenario.stepDefinitions['when']); @@ -212,21 +233,64 @@ export class ScenarioEditorComponent implements OnInit{ console.log('There are undefined steps here'); } this.selectedScenario.lastTestPassed = null; + this.checkOnReferences(this.selectedScenario); return new Promise((resolve, _reject) => {this.scenarioService .updateScenario(this.selectedStory._id, this.selectedScenario) .subscribe(_resp => { + this.updateReferences(this.selectedScenario); this.scenarioService.scenarioChangedEmitter(); this.toastr.success('successfully saved', 'Scenario'); resolve(); }); }); } + /** + * Update/Check for reference + * @param scenario + * @param blocks + */ + updateReferences(scenario){ + const stepsReferences = []; + for (const prop in scenario.stepDefinitions) { + for (const step of scenario.stepDefinitions[prop]) { + for (const block of this.blocks) { + if(block._id === step._blockReferenceId && block.usedAsReference == undefined){ + stepsReferences.push(step); + block.usedAsReference = true; + this.blockService.updateBlock(block) + .subscribe(_ => { + this.blockService.updateBlocksEvent.emit(); + }); + } + } + } + } + //If the reference was deleted + if(stepsReferences.length == 0){ + this.blockService.stepAsReference(); + } + return this.blocks; + + } addScenarioToStory(event) { const scenarioName = event; this.addScenarioEvent.emit(scenarioName); } - + /** + * Checking if the scenario has a reference when saving + * @param scenario + */ + checkOnReferences(scenario){ + for (const prop in scenario.stepDefinitions) { + for (const step of scenario.stepDefinitions[prop]) { + if(step._blockReferenceId){ + this.selectedScenario.hasRefBlock = true; + } + } + } + return this.selectedScenario; + } /** * Emitts the delete scenario event * @param event @@ -374,4 +438,9 @@ export class ScenarioEditorComponent implements OnInit{ console.log(this.createScenarioModal) this.createScenarioModal.openCreateScenarioModal(this.selectedStory); } + + blockSelectTrigger(block) { + this.selectedBlock = this.blocks.find(i => i._id == block._blockReferenceId); + block.stepDefinitions = this.selectedBlock.stepDefinitions; + } } diff --git a/frontend/src/app/story-editor/story-editor.component.css b/frontend/src/app/story-editor/story-editor.component.css index 80bfde466..95531349c 100644 --- a/frontend/src/app/story-editor/story-editor.component.css +++ b/frontend/src/app/story-editor/story-editor.component.css @@ -389,7 +389,6 @@ color: var(--light-blue); } */ -:host ::ng-deep .darkTheme .scenarioBarContainer, :host ::ng-deep .darkTheme .exampleBarContainer{ background-color: #546E7A !important; border-bottom: 1px solid #B0BEC5 !important; diff --git a/frontend/src/app/story-editor/story-editor.component.ts b/frontend/src/app/story-editor/story-editor.component.ts index 94f0c5bc8..fe0fcd031 100644 --- a/frontend/src/app/story-editor/story-editor.component.ts +++ b/frontend/src/app/story-editor/story-editor.component.ts @@ -250,6 +250,9 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ renameBackgroundObservable: Subscription; updateObservable: Subscription; applyBackgroundChangesObservable: Subscription; + checkReferenceObservable: Subscription; + deleteReferenceObservable: Subscription; + unpackBlockObservable: Subscription; @Input() isDark: boolean; @@ -379,13 +382,13 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ this.storyDeleted(); }); - this.storiesErrorObservable = this.apiService.storiesErrorEvent.subscribe(_ => { - this.storiesError = true; - this.showEditor = false; + this.storiesErrorObservable = this.apiService.storiesErrorEvent.subscribe(_ => { + this.storiesError = true; + this.showEditor = false; - window.localStorage.removeItem('login') - this.router.navigate(['/login']); - }); + window.localStorage.removeItem('login') + this.router.navigate(['/login']); + }); this.deleteScenarioObservable = this.scenarioService.deleteScenarioEvent.subscribe(() => { this.deleteScenario(this.selectedScenario); @@ -409,9 +412,9 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ this.isDark = this.themeService.isDarkMode(); }); - this.getBackendUrlObservable = this.apiService.getBackendUrlEvent.subscribe(() => { - this.loadStepTypes(); - }); + this.getBackendUrlObservable = this.apiService.getBackendUrlEvent.subscribe(() => { + this.loadStepTypes(); + }); this.renameBackgroundObservable = this.backgroundService.renameBackgroundEvent.subscribe((newName) => { this.renameBackground(newName); @@ -428,6 +431,28 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ console.log("Updated blocks:", this.blocks); }); }); + //Event when deleting references among steps + this.checkReferenceObservable = this.blockService.checkRefOnRemoveEvent.subscribe(blockReferenceId => { + const id = localStorage.getItem('id'); + this.blockService.getBlocks(id).subscribe((resp) => { + this.blocks = resp; + this.blockService.removeReferenceForStep(this.blocks, this.stories, blockReferenceId) + }); + }); + //Event when the entire reference block is deleted. Unpacking steps in all stories + this.deleteReferenceObservable = this.blockService.deleteReferenceEvent.subscribe(block => { + this.blockService.deteleBlockReference(block, this.stories); + }); + //Event when unpacking steps + this.unpackBlockObservable = this.blockService.unpackBlockEvent.subscribe((block) => { + this.blockService.unpackScenarioWithBlock(block, this.selectedScenario); + const id = localStorage.getItem('id'); + this.blockService.getBlocks(id).subscribe((resp) => { + this.blocks = resp; + this.blockService.removeReferenceForStep(this.blocks, this.stories, block._id) + }); + this.selectedScenario.saved = false; + }); this.applyBackgroundChangesObservable = this.backgroundService.applyChangesBackgroundEvent.subscribe(option => { if (option == 'toCurrentBackground') { this.toastr.info('Please enter a new Background name to save your changes'); @@ -470,6 +495,9 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ if (!this.applyBackgroundChangesObservable.closed) { this.applyBackgroundChangesObservable.unsubscribe(); } + if (!this.unpackBlockObservable.closed) { + this.unpackBlockObservable.unsubscribe(); + } } @@ -553,7 +581,7 @@ export class StoryEditorComponent implements OnInit, OnDestroy{ .deleteScenario(this.selectedStory._id, scenario) .subscribe(_ => { this.scenarioDeleted(); - this.toastr.error('', 'Scenario deleted'); + this.toastr.error('', 'Scenario deleted'); }); } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index fce54c1b5..b232291a8 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -25,9 +25,9 @@ $cucumber-frontend-warn: mat.define-palette($mat-red); $cucumber-frontend-theme: mat.define-light-theme(( color: ( primary: $cucumber-frontend-primary, - accent: $cucumber-frontend-accent, - warn: $cucumber-frontend-warn, - ) + accent: $cucumber-frontend-accent, + warn: $cucumber-frontend-warn, + ) )); // Include theme styles for core and each component used in your app. @@ -45,11 +45,10 @@ $dark-theme: mat.define-dark-theme( ( color: ( primary: $dark-primary, - accent: $dark-accent, - warn: $dark-warn, - ), - ) -); + accent: $dark-accent, + warn: $dark-warn, + ), + )); /* You can add global styles to this file, and also import other style files */ @import "../node_modules/uikit/src/scss/variables-theme.scss"; @@ -59,618 +58,661 @@ $dark-theme: mat.define-dark-theme( @import url('https://fonts.googleapis.com/css?family=Open+Sans|Roboto&display=swap'); @import url("https://fonts.googleapis.com/icon?family=Material+Icons"); - body{ - overflow-y:scroll; - font-family: 'Klavika', sans-serif; - } - @font-face { - font-family: Klavika, sans-serif; - src: local(Klavika-Light), url(fonts/Klavika-Light.otf) format("opentype"); - font-weight: 300; - font-style: normal; - font-stretch: normal; - } - @font-face { - font-family: Klavika, sans-serif; - src: local(Klavika-Regular), url(fonts/Klavika-Regular.otf) format("opentype"); - font-weight: normal; - font-style: normal; - font-stretch: normal; - } - @font-face { - font-family: Klavika, sans-serif; - src: local(Klavika-Medium), url(fonts/Klavika-Medium.otf) format("opentype"); - font-weight: 500; - font-style: normal; - font-stretch: normal; - } - - :root{ - --ocean-blue: #0071b9; - --grape: #782f40; - --white: #ffffff; - --black: #000000; - --baby-poo: #a79300; - --brown-grey: #887d75; - --grey-blue: #678b97; - --sunflower-yellow: #f9e700; - --sienna: #995219; - --pumpkin-orange: #ef8200; - --light-peach: #dcd9d7; - --very-light-pink: #f3f2f1; - --brown: #594800; - --marine-blue: #003754; - --charcoal-grey: #364445; - --pale: #e8e4e1; - --grape-purple: #40171d; - --dark-forest-green: #002e19; - --violet: #7e4654; - --grey: #505555; - --dark-grey: #857971; - --light-grey: #E8E4E1; - --light-blue: #a0d9ff; - --deep-grey : #60767b; - /* selfdefined dark theme button palette */ - --button : #607d8b; - --buttonHover : #546E7A; - --buttonDisabled: #90A4AE; - --border : #B0BEC5; - } - - .LargeHeadline1 { - font-family: Klavika, sans-serif; - font-size: 56px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.14; - letter-spacing: normal; - color: var(--black); - } - .LargeHeadline2 { - font-family: Klavika, sans-serif; - font-size: 40px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: normal; - color: var(--black); - } - .MediumHeadline1 { - font-family: Klavika, sans-serif; - font-size: 40px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.1; - letter-spacing: normal; - color: var(--black); - } - .LargeHeadline3 { - font-family: Klavika, sans-serif; - font-size: 32px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--black); - } - .LargeSublineH1 { - font-family: Klavika, sans-serif; - font-size: 32px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--black); - } - .LargeCategory1 { - font-family: Klavika, sans-serif; - font-size: 32px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: 1.6px; - color: var(--brown-grey); - } - .SmallHeadline1 { - font-family: Klavika, sans-serif; - font-size: 28px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.14; - letter-spacing: normal; - color: var(--black); - } - .LargeIntro { - font-family: Klavika, sans-serif; - font-size: 28px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.29; - letter-spacing: normal; - color: var(--black); - } - .MediumHeadline3 { - font-family: Klavika, sans-serif; - font-size: 26px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.23; - letter-spacing: normal; - color: var(--black); - } - .MediumSublineH1 { - font-family: Klavika, sans-serif; - font-size: 26px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.23; - letter-spacing: normal; - color: var(--black); - } - .LargeHeadline4 { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: normal; - color: var(--black); - } - .Largeaccordeon1 { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: normal; - color: var(--ocean-blue); - } - .LargeSublineH4 { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: normal; - color: var(--black); - } - .Largenavigation2 { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: normal; - color: var(--brown-grey); - } - .MediumCategory1 { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: 1.2px; - color: var(--brown-grey); - } - .MediumIntro { - font-family: Klavika, sans-serif; - font-size: 24px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--black); - } - .MediumHeadline4 { - font-family: Klavika, sans-serif; - font-size: 22px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.18; - letter-spacing: normal; - color: var(--black); - } - .MediumSubline1H4 { - font-family: Klavika, sans-serif; - font-size: 22px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.18; - letter-spacing: normal; - color: var(--black); - } - .darkTheme .MediumSubline1H4 { - color: white; - } - - .LargeHeadline5 { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: normal; - color: var(--black); - } - - .Mediumaccordeon1 { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: normal; - color: var(--ocean-blue); - } - .LargeBody1compact { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: normal; - color: var(--black); - } - .LargeBody1regularLH { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.6; - letter-spacing: normal; - color: var(--black); - } - .Mediumnavigation2 { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: normal; - color: var(--brown-grey); - } - .SmallCategory1 { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.2; - letter-spacing: 1px; - color: var(--brown-grey); - } - .SmallIntro { - font-family: Klavika, sans-serif; - font-size: 20px; - font-weight: 300; - font-stretch: normal; - font-style: normal; - line-height: 1.3; - letter-spacing: normal; - color: var(--black); - } - .Largeaccordeon2 { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.11; - letter-spacing: normal; - color: var(--ocean-blue); - } - - .darkTheme .Largeaccordeon2 { - color: var(--light-blue); - } - - .Largenavigation1 { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.11; - letter-spacing: normal; - text-align: center; - color: var(--brown-grey); - } - .MediumHeadline5 { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.22; - letter-spacing: normal; - color: var(--black); - } - - .darkTheme .MediumHeadline5 { - color: white; - } - - .Smallaccordeon1 { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.22; - letter-spacing: normal; - color: var(--ocean-blue); - } - .MediumBody1compact { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.22; - letter-spacing: normal; - color: var(--black); - } - .MediumBody1regularLH { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.56; - letter-spacing: normal; - color: var(--black); - } - .Smallnavigation2 { - font-family: Klavika, sans-serif; - font-size: 18px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.22; - letter-spacing: normal; - color: var(--brown-grey); - } - .LargeButton { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.38; - letter-spacing: normal; - text-align: center; - color: var(--black); - } - .LargeLink { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.38; - letter-spacing: normal; - color: var(--black); - } - .Mediumaccordeon2 { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--ocean-blue); - } - - .darkTheme .Mediumaccordeon2 { - color: var(--light-blue); - } - - .Mediumnavigation1 { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - text-align: center; - color: var(--brown-grey); - } - .SmallHeadline5 { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--black); - } - - .darkTheme .SmallHeadline5 { - color: white; - } - - .Smallnavigation1 { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--brown-grey); - } - .LargeBody2 { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.38; - letter-spacing: normal; - color: var(--black); - } - - .darkTheme .LargeBody2 { - color: var(--white); - } - - .SmallBody1compact { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.25; - letter-spacing: normal; - color: var(--black); - } - .SmallBody1regularLH { - font-family: Klavika, sans-serif; - font-size: 16px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.5; - letter-spacing: normal; - color: var(--black); - } - .darkTheme .SmallBody1regularLH { - color: white; - } - - .MediumButton1 { - font-family: Klavika, sans-serif; - font-size: 14px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.29; - letter-spacing: normal; - color: var(--black); - } - .SmallButton { - font-family: Klavika, sans-serif; - font-size: 14px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.29; - letter-spacing: normal; - text-align: center; - //color: var(--black); - color:var(--white); - background: var(--ocean-blue); - cursor: pointer; - } - - .darkTheme .SmallButton { - background: var(--button); - border-color: var(--button); - } - - .SmallButton:hover{ - background: var(--marine-blue); - } - - .darkTheme .SmallButton:hover { - background : var(--buttonHover); - } - - .SmallIcontext { - font-family: Klavika, sans-serif; - font-size: 14px; - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.14; - letter-spacing: normal; - text-align: center; - color: var(--brown-grey); - } - .Largecaption { - font-family: Klavika, sans-serif; - font-size: 14px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.14; - letter-spacing: normal; - color: var(--black); - } - .MediumBody2 { - font-family: Klavika, sans-serif; - font-size: 14px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.29; - letter-spacing: normal; - color: var(--black); - } - .LargeCategory2 { - font-family: Klavika, sans-serif; - /* font-size: 12px; */ - font-weight: 500; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: 0.6px; - color: var(--black); - } - .Mediumcaption { - font-family: Klavika, sans-serif; - font-size: 12px; - font-weight: normal; - font-stretch: normal; - font-style: normal; - line-height: 1.17; - letter-spacing: normal; - text-align: center; - color: var(--black); - } - - - .normalButton{ - color:var(--white); - padding: 5px 10px !important; - font-size: 15px; - cursor: pointer; - transition: 0.2s; - background: var(--ocean-blue); - border: none; - } - -.normalButton:hover{ +body { + overflow-y: scroll; + font-family: 'Klavika', sans-serif; +} + +@font-face { + font-family: Klavika, sans-serif; + src: local(Klavika-Light), url(fonts/Klavika-Light.otf) format("opentype"); + font-weight: 300; + font-style: normal; + font-stretch: normal; +} + +@font-face { + font-family: Klavika, sans-serif; + src: local(Klavika-Regular), url(fonts/Klavika-Regular.otf) format("opentype"); + font-weight: normal; + font-style: normal; + font-stretch: normal; +} + +@font-face { + font-family: Klavika, sans-serif; + src: local(Klavika-Medium), url(fonts/Klavika-Medium.otf) format("opentype"); + font-weight: 500; + font-style: normal; + font-stretch: normal; +} + +:root { + --ocean-blue: #0071b9; + --grape: #782f40; + --white: #ffffff; + --black: #000000; + --baby-poo: #a79300; + --brown-grey: #887d75; + --grey-blue: #678b97; + --sunflower-yellow: #f9e700; + --sienna: #995219; + --pumpkin-orange: #ef8200; + --light-peach: #dcd9d7; + --very-light-pink: #f3f2f1; + --brown: #594800; + --marine-blue: #003754; + --charcoal-grey: #364445; + --pale: #e8e4e1; + --grape-purple: #40171d; + --dark-forest-green: #002e19; + --violet: #7e4654; + --grey: #505555; + --dark-grey: #857971; + --light-grey: #E8E4E1; + --light-blue: #a0d9ff; + --deep-grey: #60767b; + /* selfdefined dark theme button palette */ + --button: #607d8b; + --buttonHover: #546E7A; + --buttonDisabled: #90A4AE; + --border: #B0BEC5; +} + +.LargeHeadline1 { + font-family: Klavika, sans-serif; + font-size: 56px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.14; + letter-spacing: normal; + color: var(--black); +} + +.LargeHeadline2 { + font-family: Klavika, sans-serif; + font-size: 40px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: normal; + color: var(--black); +} + +.MediumHeadline1 { + font-family: Klavika, sans-serif; + font-size: 40px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.1; + letter-spacing: normal; + color: var(--black); +} + +.LargeHeadline3 { + font-family: Klavika, sans-serif; + font-size: 32px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--black); +} + +.LargeSublineH1 { + font-family: Klavika, sans-serif; + font-size: 32px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--black); +} + +.LargeCategory1 { + font-family: Klavika, sans-serif; + font-size: 32px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: 1.6px; + color: var(--brown-grey); +} + +.SmallHeadline1 { + font-family: Klavika, sans-serif; + font-size: 28px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.14; + letter-spacing: normal; + color: var(--black); +} + +.LargeIntro { + font-family: Klavika, sans-serif; + font-size: 28px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.29; + letter-spacing: normal; + color: var(--black); +} + +.MediumHeadline3 { + font-family: Klavika, sans-serif; + font-size: 26px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.23; + letter-spacing: normal; + color: var(--black); +} + +.MediumSublineH1 { + font-family: Klavika, sans-serif; + font-size: 26px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.23; + letter-spacing: normal; + color: var(--black); +} + +.LargeHeadline4 { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: normal; + color: var(--black); +} + +.Largeaccordeon1 { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: normal; + color: var(--ocean-blue); +} + +.LargeSublineH4 { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: normal; + color: var(--black); +} + +.Largenavigation2 { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: normal; + color: var(--brown-grey); +} + +.MediumCategory1 { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: 1.2px; + color: var(--brown-grey); +} + +.MediumIntro { + font-family: Klavika, sans-serif; + font-size: 24px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--black); +} + +.MediumHeadline4 { + font-family: Klavika, sans-serif; + font-size: 22px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.18; + letter-spacing: normal; + color: var(--black); +} + +.MediumSubline1H4 { + font-family: Klavika, sans-serif; + font-size: 22px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.18; + letter-spacing: normal; + color: var(--black); +} + +.darkTheme .MediumSubline1H4 { + color: white; +} + +.LargeHeadline5 { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: normal; + color: var(--black); +} + +.Mediumaccordeon1 { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: normal; + color: var(--ocean-blue); +} + +.LargeBody1compact { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: normal; + color: var(--black); +} + +.LargeBody1regularLH { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.6; + letter-spacing: normal; + color: var(--black); +} + +.Mediumnavigation2 { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: normal; + color: var(--brown-grey); +} + +.SmallCategory1 { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.2; + letter-spacing: 1px; + color: var(--brown-grey); +} + +.SmallIntro { + font-family: Klavika, sans-serif; + font-size: 20px; + font-weight: 300; + font-stretch: normal; + font-style: normal; + line-height: 1.3; + letter-spacing: normal; + color: var(--black); +} + +.Largeaccordeon2 { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.11; + letter-spacing: normal; + color: var(--ocean-blue); +} + +.darkTheme .Largeaccordeon2 { + color: var(--light-blue); +} + +.Largenavigation1 { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.11; + letter-spacing: normal; + text-align: center; + color: var(--brown-grey); +} + +.MediumHeadline5 { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.22; + letter-spacing: normal; + color: var(--black); +} + +.darkTheme .MediumHeadline5 { + color: white; +} + +.Smallaccordeon1 { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.22; + letter-spacing: normal; + color: var(--ocean-blue); +} + +.MediumBody1compact { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.22; + letter-spacing: normal; + color: var(--black); +} + +.MediumBody1regularLH { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.56; + letter-spacing: normal; + color: var(--black); +} + +.Smallnavigation2 { + font-family: Klavika, sans-serif; + font-size: 18px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.22; + letter-spacing: normal; + color: var(--brown-grey); +} + +.LargeButton { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.38; + letter-spacing: normal; + text-align: center; + color: var(--black); +} + +.LargeLink { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.38; + letter-spacing: normal; + color: var(--black); +} + +.Mediumaccordeon2 { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--ocean-blue); +} + +.darkTheme .Mediumaccordeon2 { + color: var(--light-blue); +} + +.Mediumnavigation1 { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + text-align: center; + color: var(--brown-grey); +} + +.SmallHeadline5 { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--black); +} + +.darkTheme .SmallHeadline5 { + color: white; +} + +.Smallnavigation1 { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--brown-grey); +} + +.LargeBody2 { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.38; + letter-spacing: normal; + color: var(--black); +} + +.darkTheme .LargeBody2 { + color: var(--white); +} + +.SmallBody1compact { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.25; + letter-spacing: normal; + color: var(--black); +} + +.SmallBody1regularLH { + font-family: Klavika, sans-serif; + font-size: 16px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.5; + letter-spacing: normal; + color: var(--black); +} + +.darkTheme .SmallBody1regularLH { + color: white; +} + +.MediumButton1 { + font-family: Klavika, sans-serif; + font-size: 14px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.29; + letter-spacing: normal; + color: var(--black); +} + +.SmallButton { + font-family: Klavika, sans-serif; + font-size: 14px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.29; + letter-spacing: normal; + text-align: center; + //color: var(--black); + color: var(--white); + background: var(--ocean-blue); + cursor: pointer; +} + +.darkTheme .SmallButton { + background: var(--button); + border-color: var(--button); +} + +.SmallButton:hover { background: var(--marine-blue); } -.normalButton:disabled{ +.darkTheme .SmallButton:hover { + background: var(--buttonHover); +} + +.SmallIcontext { + font-family: Klavika, sans-serif; + font-size: 14px; + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.14; + letter-spacing: normal; + text-align: center; + color: var(--brown-grey); +} + +.Largecaption { + font-family: Klavika, sans-serif; + font-size: 14px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.14; + letter-spacing: normal; + color: var(--black); +} + +.MediumBody2 { + font-family: Klavika, sans-serif; + font-size: 14px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.29; + letter-spacing: normal; + color: var(--black); +} + +.LargeCategory2 { + font-family: Klavika, sans-serif; + /* font-size: 12px; */ + font-weight: 500; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: 0.6px; + color: var(--black); +} + +.Mediumcaption { + font-family: Klavika, sans-serif; + font-size: 12px; + font-weight: normal; + font-stretch: normal; + font-style: normal; + line-height: 1.17; + letter-spacing: normal; + text-align: center; + color: var(--black); +} + + +.normalButton { + color: var(--white); + padding: 5px 10px !important; + font-size: 15px; + cursor: pointer; + transition: 0.2s; + background: var(--ocean-blue); + border: none; +} + +.normalButton:hover { + background: var(--marine-blue); +} + +.normalButton:disabled { opacity: 20%; pointer-events: none; } @@ -679,15 +721,15 @@ $dark-theme: mat.define-dark-theme( background: var(--buttonHover); } -.deleteButton{ +.deleteButton { background: var(--grape) !important; } -.deleteButton:hover{ +.deleteButton:hover { background: var(--grape-purple) !important; } -.infoIcon{ +.infoIcon { grid-area: storyHeader; color: #999; padding-top: 3px !important; @@ -704,11 +746,12 @@ $dark-theme: mat.define-dark-theme( span[rel=def] { display: none; } -.mdc-circular-progress__circle-clipper{ + +.mdc-circular-progress__circle-clipper { display: revert; } -.def:hover span[rel=def]{ +.def:hover span[rel=def] { left: 19px; font-size: small; color: white; @@ -736,11 +779,11 @@ span[rel=def] { border: transparent; } -span.notPassed{ +span.notPassed { background-color: red !important; } -span.passed{ +span.passed { background-color: green !important; } @@ -748,7 +791,7 @@ span.passed{ background-color: #4CAF50 !important; } -a.passed{ +a.passed { color: green !important; } @@ -756,15 +799,15 @@ a.passed{ color: #4CAF50 !important; } -a.notPassed{ +a.notPassed { color: red !important; } -.darkTheme a.notPassed{ +.darkTheme a.notPassed { color: red !important; } -div.passed{ +div.passed { color: green !important; } @@ -772,19 +815,44 @@ div.passed{ color: #4CAF50 !important; } -div.notPassed{ +div.notPassed { color: red !important; } +.dropdownHeader { + font-weight: bold; + padding: 10px 0; + border-top: 1px solid #ddd; + margin-top: 5px; + text-decoration: underline; +} + +.darkTheme .uk-dropdown .dropdownHeader { + color: white; +} + + .darkTheme .uk-dropdown { background: #546E7A; color: var(--light-blue); } -#static{ + +.uk-nav>li>.dropdownStep { + color: var(--ocean-blue); +} + +.darkTheme .uk-nav>li>.dropdownStep { + color: var(--light-blue) !important; +} + + +#static { position: static; } -.darkTheme select, .darkTheme input, .darkTheme textarea { +.darkTheme select, +.darkTheme input, +.darkTheme textarea { background: transparent; border-color: #60767b; color: white; @@ -799,7 +867,7 @@ div.notPassed{ color: white; } -.darkTheme select > option { +.darkTheme select>option { background-color: #60767b; color: white !important; } @@ -812,17 +880,18 @@ div.notPassed{ background-color: var(--light-grey); } -.mat-app-background .darkTheme, +.mat-app-background .darkTheme, .mat-app-background.darkTheme { background-color: map-get($mat-blue-grey, 800); } -.darkTheme .Smallnavigation1, .darkTheme .Smallnavigation2, -.darkTheme span{ +.darkTheme .Smallnavigation1, +.darkTheme .Smallnavigation2, +.darkTheme span { color: var(--white); } -a.repoLink{ +a.repoLink { color: var(--ocean-blue); transition: 0.2s; } @@ -831,7 +900,7 @@ a.repoLink{ color: var(--light-blue); } -a.repoLink:hover{ +a.repoLink:hover { color: var(--marine-blue); } @@ -840,21 +909,21 @@ a.repoLink:hover{ } .darkTheme a { - color:var(--light-blue) !important; + color: var(--light-blue) !important; } a:hover { - color:var(--black) !important; + color: var(--black) !important; } .darkTheme a:hover { - color:var(--white) !important; -} + color: var(--white) !important; +} .darkTheme span.infoIcon { color: var(--white) !important; -} +} .darkTheme em { color: var(--light-blue) !important; @@ -866,10 +935,10 @@ a:hover { } .darkTheme label { - color:var(--white); + color: var(--white); } -.darkTheme hr{ +.darkTheme hr { /* border: 1px solid #60767b; */ border-color: #60767b !important; } @@ -879,7 +948,7 @@ a:hover { } .darkTheme ::placeholder { - color: rgb(255,255,255,70%) !important; + color: rgb(255, 255, 255, 70%) !important; } ::-webkit-scrollbar { @@ -894,7 +963,7 @@ a:hover { .darkTheme ::-webkit-scrollbar-thumb, .darkTheme::-webkit-scrollbar-thumb { - background: map-get($mat-blue-gray-palette, 200)!important; + background: map-get($mat-blue-gray-palette, 200) !important; border: 1px 1px 1px 1px black; } @@ -905,11 +974,12 @@ a:hover { } -.uk-input, .uk-select:not([multiple]):not([size]) { +.uk-input, +.uk-select:not([multiple]):not([size]) { height: 32px; } -#searchBarIcons{ +#searchBarIcons { color: var(--brown-grey); vertical-align: middle; } @@ -924,17 +994,31 @@ a:hover { opacity: 96%; } +.darkTheme .scenarioBarContainer { + background-color: #546E7A !important; + border-bottom: 1px solid #B0BEC5 !important; +} + .modal-backdrop { - z-index: 1 !important; -} + z-index: 1 !important; +} .mat-tooltip { font-style: italic !important; } -button:focus, input[type="button"]:focus, input[type="submit"]:focus { +button:focus, +input[type="button"]:focus, +input[type="submit"]:focus { outline: none; -} +} + +html, +body { + height: 100%; +} -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } +body { + margin: 0; + font-family: Roboto, "Helvetica Neue", sans-serif; +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index c1cfaf5d0..76549cb5e 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -14,7 +14,7 @@ "node_modules/@types" ], "lib": [ - "es2017", + "ES2022", "dom" ], "paths": {