From 90058f131a31bbb6e356b8f4b31efc503b51ea29 Mon Sep 17 00:00:00 2001 From: LianaHus Date: Thu, 14 Mar 2019 16:17:30 +0100 Subject: [PATCH] - refactored file explorer and file panel - fixed bug for empty file during create gist - landing page restyleing --- package.json | 2 +- src/app/files/file-explorer.js | 277 ++++++++++++++++++- src/app/files/styles/file-explorer-styles.js | 16 ++ src/app/panels/file-panel.js | 11 - src/app/panels/styles/file-panel-styles.js | 16 -- src/app/tabs/compile-tab.js | 5 +- src/app/ui/landing-page/generate.js | 78 ++++++ src/app/ui/landing-page/landing-page.js | 52 ++-- 8 files changed, 394 insertions(+), 63 deletions(-) create mode 100644 src/app/ui/landing-page/generate.js diff --git a/package.json b/package.json index f45b38f644..a38f5c364b 100644 --- a/package.json +++ b/package.json @@ -177,7 +177,7 @@ "remixd": "remixd -s ./contracts --remix-ide http://127.0.0.1:8080", "selenium": "execr --silent selenium-standalone start", "selenium-install": "selenium-standalone install", - "serve": "execr --silent http-server .", + "serve": "http-server .", "serve_debugger": "execr --silent http-server src/app/debugger/remix-debugger", "sourcemap": "exorcist --root ../ build/app.js.map > build/app.js", "start": "npm-run-all -lpr serve watch onchange remixd", diff --git a/src/app/files/file-explorer.js b/src/app/files/file-explorer.js index 712e1db7c6..0eca855352 100644 --- a/src/app/files/file-explorer.js +++ b/src/app/files/file-explorer.js @@ -1,19 +1,22 @@ +/* global FileReader */ +var async = require('async') +var Gists = require('gists') +var modalDialogCustom = require('../ui/modal-dialog-custom') +var tooltip = require('../ui/tooltip') +var QueryParams = require('../../lib/query-params') +var helper = require('../../lib/helper') var yo = require('yo-yo') var Treeview = require('../ui/TreeView') var modalDialog = require('../ui/modaldialog') -var modalDialogCustom = require('../ui/modal-dialog-custom') var EventManager = require('../../lib/events') var contextMenu = require('../ui/contextMenu') var addTooltip = require('../ui/tooltip') -var helper = require('../../lib/helper') - var css = require('./styles/file-explorer-styles') - var globalRegistry = require('../../global/registry') - +var queryParams = new QueryParams() let MENU_HANDLE -function fileExplorer (localRegistry, files) { +function fileExplorer (localRegistry, files, menuItems) { var self = this this.events = new EventManager() // file provider backend @@ -22,6 +25,35 @@ function fileExplorer (localRegistry, files) { this.focusElement = null // path currently focused on this.focusPath = null + let allItems = + [ + { action: 'createNewFile', + title: 'Create New File in the Browser Storage Explorer', + icon: 'fa fa-plus-circle' + }, + { action: 'publishToGist', + title: 'Publish all [browser] explorer files to a github gist', + icon: 'fa fa-github' + }, + { action: 'copyFiles', + title: 'Copy all files to another instance of Remix IDE', + icon: 'fa fa-files-o' + }, + { action: 'uploadFile', + title: 'Add Local file to the Browser Storage Explorer', + icon: 'fa fa-folder-open' + }, + { action: 'updateGist', + title: 'Update the current [gist] explorer', + icon: 'fa fa-github' + } + ] + // menu items + this.menuItems = allItems.filter( + (item) => { + if (menuItems) return menuItems.find((name) => { return name === item.action }) + } + ) self._components = {} self._components.registry = localRegistry || globalRegistry @@ -31,6 +63,8 @@ function fileExplorer (localRegistry, files) { fileManager: self._components.registry.get('filemanager').api } + self._components.registry.put(`fileexplorer${files.type}`, this) + // warn if file changed outside of Remix function remixdDialog () { return yo`
This file has been changed outside of Remix IDE.
` @@ -122,13 +156,19 @@ function fileExplorer (localRegistry, files) { }, formatSelf: function formatSelf (key, data, li) { var isRoot = data.path === self.files.type - return yo`` + return yo` +
+ + ${isRoot ? self.renderMenuItems() : ''} +
+ ` } }) @@ -276,6 +316,217 @@ fileExplorer.prototype.init = function () { return this.container } +fileExplorer.prototype.publishToGist = function (fileProviderName) { + modalDialogCustom.confirm( + null, + 'Are you sure you want to publish all your files anonymously as a public gist on github.com?', + () => { this.toGist(fileProviderName) } + ) +} + +fileExplorer.prototype.uploadFile = function (event) { + // TODO The file explorer is merely a view on the current state of + // the files module. Please ask the user here if they want to overwrite + // a file and then just use `files.add`. The file explorer will + // pick that up via the 'fileAdded' event from the files module. + + let self = this + + ;[...event.target.files].forEach((file) => { + let files = this.files + function loadFile () { + var fileReader = new FileReader() + fileReader.onload = function (event) { + if (helper.checkSpecialChars(file.name)) { + modalDialogCustom.alert('Special characters are not allowed') + return + } + var success = files.set(name, event.target.result) + if (!success) { + modalDialogCustom.alert('Failed to create file ' + name) + } else { + self.events.trigger('focus', [name]) + } + } + fileReader.readAsText(file) + } + var name = files.type + '/' + file.name + files.exists(name, (error, exist) => { + if (error) console.log(error) + if (!exist) { + loadFile() + } else { + modalDialogCustom.confirm(null, `The file ${name} already exists! Would you like to overwrite it?`, () => { loadFile() }) + } + }) + }) +} + +fileExplorer.prototype.toGist = function (id) { + let proccedResult = function (error, data) { + if (error) { + modalDialogCustom.alert('Failed to manage gist: ' + error) + } else { + if (data.html_url) { + modalDialogCustom.confirm(null, `The gist is at ${data.html_url}. Would you like to open it in a new window?`, () => { + window.open(data.html_url, '_blank') + }) + } else { + modalDialogCustom.alert(data.message + ' ' + data.documentation_url + ' ' + JSON.stringify(data.errors, null, '\t')) + } + } + } + + this.packageFiles(this.files, (error, packaged) => { + if (error) { + console.log(error) + modalDialogCustom.alert('Failed to create gist: ' + error) + } else { + var tokenAccess = this._deps.config.get('settings/gist-access-token') + if (!tokenAccess) { + modalDialogCustom.alert( + 'Remix requires an access token (which includes gists creation permission). Please go to the settings tab to create one.' + ) + } else { + var description = 'Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. \n Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=' + + queryParams.get().version + + '&optimize=' + + queryParams.get().optimize + + '&gist=' + var gists = new Gists({ + token: tokenAccess + }) + if (id) { + tooltip('Saving gist (' + id + ') ...') + gists.edit({ + description: description, + public: true, + files: packaged, + id: id + }, (error, result) => { + proccedResult(error, result) + }) + } else { + tooltip('Creating a new gist ...') + gists.create({ + description: description, + public: true, + files: packaged + }, (error, result) => { + proccedResult(error, result) + }) + } + } + } + }) +} + +// return all the files, except the temporary/readonly ones.. +fileExplorer.prototype.packageFiles = function (filesProvider, callback) { + var ret = {} + filesProvider.resolveDirectory(filesProvider.type, (error, files) => { + if (error) callback(error) + else { + async.eachSeries(Object.keys(files), (path, cb) => { + filesProvider.get(path, (error, content) => { + if (/^\s+$/.test(content)) { + content = '// this line is added to create a gist. Empty file is not allowed.' + } + if (error) cb(error) + else { + ret[path] = { content } + cb() + } + }) + }, (error) => { + callback(error, ret) + }) + } + }) +} + +// ------------------ copy files -------------- +fileExplorer.prototype.copyFiles = function () { + let self = this + modalDialogCustom.prompt( + null, + 'To which other remix-ide instance do you want to copy over all files?', + 'https://remix.ethereum.org', + (target) => { + doCopy(target) + } + ) + function doCopy (target) { + // package only files from the browser storage. + self.packageFiles(self.files, (error, packaged) => { + if (error) { + console.log(error) + } else { + let iframe = yo` + + ` + iframe.onload = function () { + iframe.contentWindow.postMessage(['loadFiles', packaged], '*') + tooltip('Files copied successfully.') + } + document.querySelector('body').appendChild(iframe) + } + }) + } +} + +// ------------------ gist publish -------------- +fileExplorer.prototype.updateGist = function () { + var gistId = this.files.id + if (!gistId) { + tooltip('no gist content is currently loaded.') + } else { + this.toGist(gistId) + } +} + +fileExplorer.prototype.createNewFile = function () { + let self = this + modalDialogCustom.prompt(null, 'File Name', 'Untitled.sol', (input) => { + helper.createNonClashingName(input, self.files, (error, newName) => { + if (error) return modalDialogCustom.alert('Failed to create file ' + newName + ' ' + error) + if (!self.files.set(newName, '')) { + modalDialogCustom.alert('Failed to create file ' + newName) + } else { + var file = self.files.type + '/' + newName + self._deps.fileManager.switchFile(file) + if (file.includes('_test.sol')) { + self.event.trigger('newTestFileCreated', [file]) + } + } + }) + }, null, true) +} + +fileExplorer.prototype.renderMenuItems = function () { + let items = '' + if (this.menuItems) { + items = this.menuItems.map(({action, title, icon}) => { + if (action === 'uploadFile') { + return yo` + + ` + } else { + return yo` + { this[ action ]() }} class="newFile ${css.newFile}" title=${title}> + + + ` + } + }) + } + return yo`${items}` +} + fileExplorer.prototype.ensureRoot = function (cb) { cb = cb || (() => {}) var self = this diff --git a/src/app/files/styles/file-explorer-styles.js b/src/app/files/styles/file-explorer-styles.js index 0811e6e56c..fb15bec58c 100644 --- a/src/app/files/styles/file-explorer-styles.js +++ b/src/app/files/styles/file-explorer-styles.js @@ -16,6 +16,22 @@ var css = csjs` cursor : pointer; } .file { + padding : 4px; + } + .newFile { + padding : 4px; + } + .newFile i { + cursor : pointer; + } + .newFile i:hover { + color : var(--secondary) + } + .menu { + margin-left : 20px; + } + .items { + display : inline } .hasFocus { } diff --git a/src/app/panels/file-panel.js b/src/app/panels/file-panel.js index 154171f562..88ee950f56 100644 --- a/src/app/panels/file-panel.js +++ b/src/app/panels/file-panel.js @@ -1,20 +1,9 @@ -/* global FileReader */ -var async = require('async') -var $ = require('jquery') var yo = require('yo-yo') var CompilerMetadata = require('../files/compiler-metadata') var EventManager = require('../../lib/events') -var Gists = require('gists') var FileExplorer = require('../files/file-explorer') -var modalDialogCustom = require('../ui/modal-dialog-custom') -var tooltip = require('../ui/tooltip') -var QueryParams = require('../../lib/query-params') -var queryParams = new QueryParams() -var helper = require('../../lib/helper') var { RemixdHandle } = require('../files/remixd-handle.js') - var globalRegistry = require('../../global/registry') - var css = require('./styles/file-panel-styles') import { ApiFactory } from 'remix-plugin' diff --git a/src/app/panels/styles/file-panel-styles.js b/src/app/panels/styles/file-panel-styles.js index 25cefbf469..4ccec900d7 100644 --- a/src/app/panels/styles/file-panel-styles.js +++ b/src/app/panels/styles/file-panel-styles.js @@ -14,22 +14,6 @@ var css = csjs` position : relative; width : 100%; } - .menu { - margin-top : -0.2em; - flex-shrink : 0; - display : flex; - flex-direction : row; - min-width : 160px; - } - .newFile { - padding : 10px; - } - .newFile i { - cursor : pointer; - } - .newFile i:hover { - color : var(--secondary) - } .gist { padding : 10px; } diff --git a/src/app/tabs/compile-tab.js b/src/app/tabs/compile-tab.js index 677516c52a..b3ce18b238 100644 --- a/src/app/tabs/compile-tab.js +++ b/src/app/tabs/compile-tab.js @@ -208,9 +208,8 @@ class CompileTab extends ApiFactory { } publish () { - const selectContractNames = this._view.contractNames - if (selectContractNames.children.length > 0 && selectContractNames.selectedIndex >= 0) { - var contract = this.data.contractsDetails[selectContractNames.children[selectContractNames.selectedIndex].innerHTML] + if (this.selectedContract) { + var contract = this.data.contractsDetails[this.selectedContract] if (contract.metadata === undefined || contract.metadata.length === 0) { modalDialogCustom.alert('This contract may be abstract, may not implement an abstract parent\'s methods completely or not invoke an inherited contract\'s constructor correctly.') } else { diff --git a/src/app/ui/landing-page/generate.js b/src/app/ui/landing-page/generate.js new file mode 100644 index 0000000000..1e260e8872 --- /dev/null +++ b/src/app/ui/landing-page/generate.js @@ -0,0 +1,78 @@ +/* global */ +import LandingPage from './landing-page' +import Section from './section' +import { defaultWorkspaces } from './workspace' +var globalRegistry = require('../../../global/registry') + +export function homepageProfile () { + return { + displayName: 'Home', + name: 'home', + methods: [], + events: [], + description: ' - ', + icon: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/PjwhRE9DVFlQRSBzdmcgIFBVQkxJQyAnLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4nICAnaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkJz48c3ZnIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDUwIDUwIiBoZWlnaHQ9IjUwcHgiIGlkPSJMYXllcl8xIiB2ZXJzaW9uPSIxLjEiIHZpZXdCb3g9IjAgMCA1MCA1MCIgd2lkdGg9IjUwcHgiIHhtbDpzcGFjZT0icHJlc2VydmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiPjxyZWN0IGZpbGw9Im5vbmUiIGhlaWdodD0iNTAiIHdpZHRoPSI1MCIvPjxnPjxwYXRoIGQ9IiAgIE0yNSwxQzExLjc0NSwxLDEsMTEuNzQ1LDEsMjVzMTAuNzQ1LDI0LDI0LDI0czI0LTEwLjc0NSwyNC0yNFMzOC4yNTUsMSwyNSwxTDI1LDF6IiBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2Utd2lkdGg9IjIiLz48L2c+PHBhdGggZD0iICBNNDAuNjk2LDYuODMyYzAsMC0xMy4xNjksOC4yMTItMTEuNTMyLDIyLjMzMmMxLjE0Miw5Ljg1OCwxMS45MzUsMTMuMzc3LDExLjkzNSwxMy4zNzciIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMi4wNTgzIi8+PHBhdGggZD0iICBNNy4zODUsOC45MTNjMCwwLDMuMDQxLDYuNDc2LDMuMDQxLDE4LjE2OWMwLDkuMjQ2LTMuNTgzLDEyLjkxMS0zLjU4MywxMi45MTEiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMi4wNTgzIi8+PHBhdGggZD0iICBNMS44NTIsMjIuOTMyYzAsMCw2LjQ5Myw2LjIzMiwyMy4xNDgsNi4yMzJzMjMuNDM4LTYuMjQ2LDIzLjQzOC02LjI0NiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyLjA1ODMiLz48cGF0aCBkPSIgIE0yNS42NDgsMS41NDhjMCwwLTYuODk1LDcuOTM1LTYuODk1LDIzLjQ1MkMxOC43NTQsNDAuNTE4LDI1LDQ4LjYyNSwyNSw0OC42MjUiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMDAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMi4wNTgzIi8+PC9zdmc+', + prefferedLocation: 'mainPanel' + } +} + +export function generateHomePage (appManager, appStore) { + var actions1 = [ + { label: 'New file', + type: 'callback', + payload: () => { + let fileManager = globalRegistry.get('fileexplorerbrowser').api + fileManager.creatNewFile() + } + }, + {label: 'Import from GitHub', type: `callback`, payload: () => { this.alert(`-imported from GitHub-`) }}, + {label: 'Import from gist', type: `callback`, payload: () => { this.alert(`-imported from gist-`) }} + ] + + var actions3 = [ + {label: 'Remix documentation', type: `link`, payload: `https://remix.readthedocs.io/en/latest/#`}, + {label: 'GitHub repository', type: `link`, payload: `https://github.com/ethereum/remix-ide`}, + {label: 'Access local file system with remixd', type: `link`, payload: `https://remix.readthedocs.io/en/latest/tutorial_remixd_filesystem.html`}, + {label: 'npm module for remixd', type: `link`, payload: `https://www.npmjs.com/package/remixd`}, + {label: 'Medium posts', type: `link`, payload: `https://medium.com/remix-ide`}, + {label: 'Tutorials', type: `link`, payload: `https://github.com/ethereum/remix-workshops`} + ] + + var actions4 = [ + {label: 'Remix plugins & modules', type: `link`, payload: `https://github.com/ethereum/remix-plugin/blob/master/readme.md`}, + {label: 'Repository on GitHub', type: `link`, payload: `https://github.com/ethereum/remix-plugin`}, + {label: 'Examples', type: `link`, payload: `https://github.com/ethereum/remix-plugin/tree/master/examples`}, + {label: 'Build a plugin for Remix', type: `link`, payload: `https://medium.com/remix-ide/build-a-plugin-for-remix-90d43b209c5a`} + ] + + var actions5 = [ + {label: 'Gitter channel', type: `link`, payload: `https://gitter.im/ethereum/remix`}, + {label: 'Stack Overflow', type: `link`, payload: `https://stackoverflow.com/questions/tagged/remix`}, + {label: 'Reddit', type: `link`, payload: `https://www.reddit.com/r/ethdev/search?q=remix&restrict_sr=1`} + ] + + var sectionStart = new Section('Start', actions1) + var sectionLearn = new Section('Learn', actions3) + var sectionPlugins = new Section('Plugins', actions4) + var sectionHelp = new Section('Help', actions5) + + var sectionsWorkspaces = [] + sectionsWorkspaces.push({ + label: 'Close All Modules', + type: 'callback', + payload: () => { + appStore.getActives() + .filter(({profile}) => !profile.required) + .forEach((profile) => { appManager.deactivateOne(profile.name) }) + }}) + defaultWorkspaces(appManager).forEach((workspace) => { + sectionsWorkspaces.push({ + label: workspace.title, + type: 'callback', + payload: () => { workspace.activate() } + }) + }) + var sectionWorkspace = new Section('Workspaces', sectionsWorkspaces) + + return new LandingPage([sectionWorkspace, sectionStart, sectionLearn, sectionPlugins, sectionHelp]) +} diff --git a/src/app/ui/landing-page/landing-page.js b/src/app/ui/landing-page/landing-page.js index 5c3c1c9510..8735efea78 100644 --- a/src/app/ui/landing-page/landing-page.js +++ b/src/app/ui/landing-page/landing-page.js @@ -1,30 +1,43 @@ var yo = require('yo-yo') var csjs = require('csjs-inject') + var css = csjs` - .container { + .container { position : static; - box-sizing : border-box; - display : flex; flex-direction : column; flex-wrap : wrap; + justify-content : unset; align-items : center; align-content : space-around; - - border : 2px solid black; width : 400px; - padding : 50px; + padding : 20px; + background-color: var(--primary); font-family : "Lucida Console", Monaco, monospace; + font-size : 16px; } - .logo { - position : absolute; - opacity : 0.3; - z-index : 0; - } + .section { z-index : 10; } + .span { + font-size : 16px; + cursor : pointer; + color: var(--secondary) + } + .im { + display : inline-block; + max-width : 150px; + max-height : 160px; + width : 100%; + height : 100%; + padding : 20px; + background-color: var(--primary); + } + .im:hover { + } +} ` import { defaultWorkspaces } from './workspace' @@ -103,17 +116,18 @@ export class LandingPage extends ApiFactory { } render () { - var totalLook = yo` -
- + let logo = 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAxNi4wLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+DQo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4Ig0KCSB3aWR0aD0iNTEycHgiIGhlaWdodD0iNTEycHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA1MTIgNTEyIiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxnPg0KCTxwYXRoIGZpbGw9IiM0MTQwNDIiIGQ9Ik03MC41ODIsNDI4LjkwNGMwLjgxMSwwLDEuNjIyLDAuMjg1LDIuNDM3LDAuODUzYzAuODExLDAuNTcxLDEuMjE4LDEuMzQsMS4yMTgsMi4zMTQNCgkJYzAsMi4yNzctMS4wNTksMy40OTYtMy4xNjgsMy42NTZjLTUuMDM4LDAuODE0LTkuMzgxLDIuMzU2LTEzLjAzNyw0LjYzYy0zLjY1NSwyLjI3Ni02LjY2Myw1LjExNy05LjAxNiw4LjUyOA0KCQljLTIuMzU3LDMuNDExLTQuMTA0LDcuMjcyLTUuMjM5LDExLjU3NWMtMS4xMzksNC4zMDctMS43MDYsOC44MTQtMS43MDYsMTMuNTI0djMyLjY1M2MwLDIuMjczLTEuMTM5LDMuNDExLTMuNDEyLDMuNDExDQoJCWMtMi4yNzcsMC0zLjQxMi0xLjEzOC0zLjQxMi0zLjQxMXYtNzQuMzIzYzAtMi4yNzMsMS4xMzUtMy40MTEsMy40MTItMy40MTFjMi4yNzMsMCwzLjQxMiwxLjEzOCwzLjQxMiwzLjQxMXYxNS4xMDgNCgkJYzEuNDYyLTIuNDM3LDMuMjA2LTQuNzUyLDUuMjM5LTYuOTQ1YzIuMDI5LTIuMTkzLDQuMjY0LTQuMTQzLDYuNzAxLTUuODQ4YzIuNDM3LTEuNzA2LDUuMDc2LTMuMDg1LDcuOTE5LTQuMTQzDQoJCUM2NC43NzEsNDI5LjQzMyw2Ny42NTgsNDI4LjkwNCw3MC41ODIsNDI4LjkwNHoiLz4NCgk8cGF0aCBmaWxsPSIjNDE0MDQyIiBkPSJNMTM3Ljc3Myw0MjcuMTk4YzUuNjg1LDAsMTAuOTY2LDEuMTgxLDE1LjgzOSwzLjUzNGM0Ljg3NCwyLjM1Niw5LjA1NSw1LjQ4MiwxMi41NSw5LjM4MQ0KCQljMy40OTIsMy44OTksNi4yMTQsOC40MDcsOC4xNjQsMTMuNTI0YzEuOTQ5LDUuMTE3LDIuOTI0LDEwLjQ0LDIuOTI0LDE1Ljk2MWMwLDAuOTc2LTAuMzY2LDEuNzktMS4wOTcsMi40MzgNCgkJYy0wLjczMSwwLjY1LTEuNTgzLDAuOTc1LTIuNTU5LDAuOTc1aC02Ny45ODdjMC40ODcsNC4yMjYsMS41ODQsOC4yODUsMy4yOSwxMi4xODRjMS43MDYsMy44OTksMy45MzcsNy4zMTIsNi43MDEsMTAuMjM0DQoJCWMyLjc2MSwyLjkyNSw2LjAwOCw1LjI4MSw5Ljc0OCw3LjA2N2MzLjczNSwxLjc4OSw3Ljg3NywyLjY4MSwxMi40MjgsMi42ODFjMTIuMDIxLDAsMjEuMzYtNC43OSwyOC4wMjMtMTQuMzc3DQoJCWMwLjY0Ny0xLjEzNiwxLjYyMi0xLjcwNiwyLjkyNC0xLjcwNmMyLjI3MywwLDMuNDEyLDEuMTM5LDMuNDEyLDMuNDEyYzAsMC4xNjMtMC4xNjQsMC43My0wLjQ4NywxLjcwNQ0KCQljLTMuNDEyLDYuMDEzLTguMjA1LDEwLjQ3OS0xNC4zNzcsMTMuNDAyYy02LjE3NiwyLjkyNC0xMi42NzEsNC4zODctMTkuNDk1LDQuMzg3Yy01LjY4OSwwLTEwLjkyOC0xLjE4MS0xNS43MTgtMy41MzMNCgkJYy00Ljc5My0yLjM1NC04LjkzNi01LjQ4My0xMi40MjgtOS4zODJjLTMuNDk1LTMuODk5LTYuMjE0LTguNDA3LTguMTYzLTEzLjUyNGMtMS45NS01LjExOC0yLjkyNC0xMC40MzctMi45MjQtMTUuOTYyDQoJCWMwLTUuNTIxLDAuOTc1LTEwLjg0NCwyLjkyNC0xNS45NjFjMS45NDktNS4xMTcsNC42NjgtOS42MjUsOC4xNjMtMTMuNTI0YzMuNDkyLTMuODk4LDcuNjM0LTcuMDI0LDEyLjQyOC05LjM4MQ0KCQlDMTI2Ljg0Niw0MjguMzc5LDEzMi4wODQsNDI3LjE5OCwxMzcuNzczLDQyNy4xOTh6IE0xNjkuOTQsNDY2LjE4OGMtMC4zMjgtNC4yMjMtMS4zNDEtOC4yODUtMy4wNDYtMTIuMTg0DQoJCWMtMS43MDYtMy44OTktMy45ODItNy4zMTItNi44MjMtMTAuMjM1Yy0yLjg0NC0yLjkyNC02LjE3NS01LjI3Ny05Ljk5MS03LjA2N2MtMy44MTktMS43ODUtNy45Mi0yLjY4LTEyLjMwNi0yLjY4DQoJCWMtNC41NSwwLTguNjkyLDAuODk1LTEyLjQyOCwyLjY4Yy0zLjczOSwxLjc5LTYuOTg3LDQuMTQ0LTkuNzQ4LDcuMDY3Yy0yLjc2NCwyLjkyNC00Ljk5NSw2LjMzNi02LjcwMSwxMC4yMzUNCgkJYy0xLjcwNiwzLjg5OC0yLjgwMiw3Ljk2MS0zLjI5LDEyLjE4NEgxNjkuOTR6Ii8+DQoJPHBhdGggZmlsbD0iIzQxNDA0MiIgZD0iTTMwNC42OSw0MjcuNDQxYzUuMDM0LDAsOS41MDQsMS4wMTgsMTMuNDAyLDMuMDQ3YzMuODk5LDIuMDMzLDcuMTg5LDQuNjcyLDkuODcsNy45Mg0KCQljMi42OCwzLjI1MSw0LjcwOSw3LjA2Niw2LjA5MiwxMS40NTJjMS4zNzksNC4zODcsMi4wNyw4Ljg1NiwyLjA3LDEzLjQwMnY0My42MmMwLDAuOTc1LTAuMzY1LDEuNzg5LTEuMDk3LDIuNDM4DQoJCWMtMC43MywwLjY0Ni0xLjUwMywwLjk3NS0yLjMxMywwLjk3NWMtMi4yNzYsMC0zLjQxMi0xLjE0LTMuNDEyLTMuNDEydi00My42MmMwLTMuNTcxLTAuNTI5LTcuMTA0LTEuNTg0LTEwLjYNCgkJYy0xLjA1OS0zLjQ5MS0yLjYwMi02LjYxOC00LjYzLTkuMzgyYy0yLjAzMy0yLjc2MS00LjU5Mi00Ljk1My03LjY3Ny02LjU4Yy0zLjA4OC0xLjYyMS02LjY2Mi0yLjQzNi0xMC43MjItMi40MzYNCgkJYy01LjIsMC05LjU4NywxLjIxOC0xMy4xNTksMy42NTRjLTMuNTc0LDIuNDM4LTYuNDU3LDUuNTY2LTguNjUsOS4zODJjLTIuMTkzLDMuODE5LTMuODE4LDguMDQyLTQuODc0LDEyLjY3Mg0KCQljLTEuMDU5LDQuNjMtMS41ODQsOS4wNTgtMS41ODQsMTMuMjh2MzMuNjI5YzAsMC45NzUtMC4zNjUsMS43ODktMS4wOTYsMi40MzhjLTAuNzMxLDAuNjQ2LTEuNTA1LDAuOTc1LTIuMzE1LDAuOTc1DQoJCWMtMi4yNzYsMC0zLjQxMS0xLjE0LTMuNDExLTMuNDEydi00My42MmMwLTMuNTcxLTAuNTMtNy4xMDQtMS41ODUtMTAuNmMtMS4wNTgtMy40OTEtMi42MDEtNi42MTgtNC42MjktOS4zODINCgkJYy0yLjAzNC0yLjc2MS00LjU5Mi00Ljk1My03LjY3Ny02LjU4Yy0zLjA4Ny0xLjYyMS02LjY2My0yLjQzNi0xMC43MjItMi40MzZjLTUuMDM3LDAtOS4zNDQsMC44OTUtMTIuOTE1LDIuNjgNCgkJYy0zLjU3NSwxLjc5LTYuNTQyLDQuMjY2LTguODk1LDcuNDMzYy0yLjM1NywzLjE2Ny00LjA2Myw2Ljk0NC01LjExNywxMS4zMzFjLTEuMDU5LDQuMzg2LTEuNTg0LDkuMS0xLjU4NCwxNC4xMzR2My44OTl2MC4yNDMNCgkJdjMyLjg5N2MwLDIuMjcyLTEuMTM4LDMuNDEyLTMuNDEyLDMuNDEyYy0yLjI3NiwwLTMuNDExLTEuMTQtMy40MTEtMy40MTJ2LTc0LjU2N2MwLTIuMjczLDEuMTM1LTMuNDExLDMuNDExLTMuNDExDQoJCWMyLjI3MywwLDMuNDEyLDEuMTM4LDMuNDEyLDMuNDExdjEyLjQyOGMyLjkyNC01LjE5Nyw2Ljg2MS05LjM4MiwxMS44MTktMTIuNTVjNC45NTQtMy4xNjcsMTAuNTE3LTQuNzUyLDE2LjY5Mi00Ljc1Mg0KCQljNi45ODMsMCwxMi45OTUsMS45OTEsMTguMDMyLDUuOTdjNS4wMzMsMy45ODMsOC42ODgsOS4yMjMsMTAuOTY2LDE1LjcxOWMyLjc2LTYuMzM2LDYuNzM5LTExLjUzMywxMS45NC0xNS41OTYNCgkJQzI5MS4xMjUsNDI5LjQ3NSwyOTcuMzgsNDI3LjQ0MSwzMDQuNjksNDI3LjQ0MXoiLz4NCgk8cGF0aCBmaWxsPSIjNDE0MDQyIiBkPSJNMzc4Ljc1Myw0MjkuMzkyYzAuODExLDAsMS41ODQsMC4zNjUsMi4zMTQsMS4wOTdjMC43MzEsMC43MywxLjA5NywxLjUwNCwxLjA5NywyLjMxNHY3NC4wOA0KCQljMCwwLjgxNC0wLjM2NSwxLjU4NC0xLjA5NywyLjMxNWMtMC43MywwLjczLTEuNTA0LDEuMDk3LTIuMzE0LDEuMDk3Yy0wLjk3NSwwLTEuNzktMC4zNjYtMi40MzgtMS4wOTcNCgkJYy0wLjY1LTAuNzMxLTAuOTc1LTEuNTAxLTAuOTc1LTIuMzE1di03NC4wOGMwLTAuODExLDAuMzI0LTEuNTg0LDAuOTc1LTIuMzE0QzM3Ni45NjMsNDI5Ljc1NywzNzcuNzc4LDQyOS4zOTIsMzc4Ljc1Myw0MjkuMzkyeiINCgkJLz4NCgk8cGF0aCBmaWxsPSIjNDE0MDQyIiBkPSJNNDczLjM0LDQyOC42NmMyLjI3MywwLDMuNDEyLDEuMTM5LDMuNDEyLDMuNDExbC0wLjQ4NywxLjk1bC0yNC4zNjgsMzUuMzM0bDI0LjM2OCwzNS41NzcNCgkJYzAuMzIzLDAuOTc2LDAuNDg3LDEuNjI2LDAuNDg3LDEuOTVjMCwyLjI3Mi0xLjEzOSwzLjQxMi0zLjQxMiwzLjQxMmMtMS4zMDIsMC0yLjE5My0wLjQ4OC0yLjY4LTEuNDYzbC0yMi45MDYtMzMuMzg0DQoJCWwtMjIuNjYzLDMzLjM4NGMtMC44MTQsMC45NzUtMS43OSwxLjQ2My0yLjkyNCwxLjQ2M2MtMi4yNzcsMC0zLjQxMS0xLjE0LTMuNDExLTMuNDEyYzAtMC4zMjQsMC4xNTktMC45NzUsMC40ODYtMS45NQ0KCQlsMjQuMzY5LTM1LjU3N2wtMjQuMzY5LTM1LjMzNGwtMC40ODYtMS45NWMwLTIuMjcyLDEuMTM0LTMuNDExLDMuNDExLTMuNDExYzEuMTM0LDAsMi4xMDksMC40ODcsMi45MjQsMS40NjJsMjIuNjYzLDMzLjE0MQ0KCQlsMjIuOTA2LTMzLjE0MUM0NzEuMTQ2LDQyOS4xNDcsNDcyLjAzOCw0MjguNjYsNDczLjM0LDQyOC42NnoiLz4NCjwvZz4NCjxnPg0KCTxnPg0KCQk8ZyBvcGFjaXR5PSIwLjQ1Ij4NCgkJCTxnPg0KCQkJCTxwb2x5Z29uIGZpbGw9IiMwMTAxMDEiIHBvaW50cz0iMTUwLjczNCwxOTYuMjEyIDI1NS45NjksMzQ0LjUwOCAyNTUuOTY5LDI1OC4zODcgCQkJCSIvPg0KCQkJPC9nPg0KCQk8L2c+DQoJCTxnIG9wYWNpdHk9IjAuOCI+DQoJCQk8Zz4NCgkJCQk8cG9seWdvbiBmaWxsPSIjMDEwMTAxIiBwb2ludHM9IjI1NS45NjksMjU4LjM4NyAyNTUuOTY5LDM0NC41MDggMzYxLjI2NywxOTYuMjEyIAkJCQkiLz4NCgkJCTwvZz4NCgkJPC9nPg0KCQk8ZyBvcGFjaXR5PSIwLjYiPg0KCQkJPGc+DQoJCQkJPHBvbHlnb24gZmlsbD0iIzAxMDEwMSIgcG9pbnRzPSIyNTUuOTY5LDEyNi43ODEgMTUwLjczMywxNzQuNjExIDI1NS45NjksMjM2LjgxOCAzNjEuMjA0LDE3NC42MTEgCQkJCSIvPg0KCQkJPC9nPg0KCQk8L2c+DQoJCTxnIG9wYWNpdHk9IjAuNDUiPg0KCQkJPGc+DQoJCQkJPHBvbHlnb24gZmlsbD0iIzAxMDEwMSIgcG9pbnRzPSIxNTAuNzM0LDE3NC42MTIgMjU1Ljk2OSwyMzYuODE4IDI1NS45NjksMTI2Ljc4MiAyNTUuOTY5LDAuMDAxIAkJCQkiLz4NCgkJCTwvZz4NCgkJPC9nPg0KCQk8ZyBvcGFjaXR5PSIwLjgiPg0KCQkJPGc+DQoJCQkJPHBvbHlnb24gZmlsbD0iIzAxMDEwMSIgcG9pbnRzPSIyNTUuOTY5LDAgMjU1Ljk2OSwxMjYuNzgxIDI1NS45NjksMjM2LjgxOCAzNjEuMjA0LDE3NC42MTEgCQkJCSIvPg0KCQkJPC9nPg0KCQk8L2c+DQoJPC9nPg0KPC9nPg0KPC9zdmc+DQo=' + let totalLook = yo` +
+
` - for (var i = 0; i < this.sections.length; i++) { + for (let i = 0; i < this.sections.length; i++) { totalLook.appendChild(yo` -
- ${this.sections[i].render()} -
- `) +
+ ${this.sections[i].render()} +
+ `) } return totalLook }