element', () => {
+ // https://on.cypress.io/select
+
+ // at first, no option should be selected
+ cy.get('.action-select')
+ .should('have.value', '--Select a fruit--')
+
+ // Select option(s) with matching text content
+ cy.get('.action-select').select('apples')
+ // confirm the apples were selected
+ // note that each value starts with "fr-" in our HTML
+ cy.get('.action-select').should('have.value', 'fr-apples')
+
+ cy.get('.action-select-multiple')
+ .select(['apples', 'oranges', 'bananas'])
+ // when getting multiple values, invoke "val" method first
+ .invoke('val')
+ .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
+
+ // Select option(s) with matching value
+ cy.get('.action-select').select('fr-bananas')
+ // can attach an assertion right away to the element
+ .should('have.value', 'fr-bananas')
+
+ cy.get('.action-select-multiple')
+ .select(['fr-apples', 'fr-oranges', 'fr-bananas'])
+ .invoke('val')
+ .should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
+
+ // assert the selected values include oranges
+ cy.get('.action-select-multiple')
+ .invoke('val').should('include', 'fr-oranges')
+ })
+
+ it('.scrollIntoView() - scroll an element into view', () => {
+ // https://on.cypress.io/scrollintoview
+
+ // normally all of these buttons are hidden,
+ // because they're not within
+ // the viewable area of their parent
+ // (we need to scroll to see them)
+ cy.get('#scroll-horizontal button')
+ .should('not.be.visible')
+
+ // scroll the button into view, as if the user had scrolled
+ cy.get('#scroll-horizontal button').scrollIntoView()
+ .should('be.visible')
+
+ cy.get('#scroll-vertical button')
+ .should('not.be.visible')
+
+ // Cypress handles the scroll direction needed
+ cy.get('#scroll-vertical button').scrollIntoView()
+ .should('be.visible')
+
+ cy.get('#scroll-both button')
+ .should('not.be.visible')
+
+ // Cypress knows to scroll to the right and down
+ cy.get('#scroll-both button').scrollIntoView()
+ .should('be.visible')
+ })
+
+ it('.trigger() - trigger an event on a DOM element', () => {
+ // https://on.cypress.io/trigger
+
+ // To interact with a range input (slider)
+ // we need to set its value & trigger the
+ // event to signal it changed
+
+ // Here, we invoke jQuery's val() method to set
+ // the value and trigger the 'change' event
+ cy.get('.trigger-input-range')
+ .invoke('val', 25)
+ .trigger('change')
+ .get('input[type=range]').siblings('p')
+ .should('have.text', '25')
+ })
+
+ it('cy.scrollTo() - scroll the window or element to a position', () => {
+ // https://on.cypress.io/scrollto
+
+ // You can scroll to 9 specific positions of an element:
+ // -----------------------------------
+ // | topLeft top topRight |
+ // | |
+ // | |
+ // | |
+ // | left center right |
+ // | |
+ // | |
+ // | |
+ // | bottomLeft bottom bottomRight |
+ // -----------------------------------
+
+ // if you chain .scrollTo() off of cy, we will
+ // scroll the entire window
+ cy.scrollTo('bottom')
+
+ cy.get('#scrollable-horizontal').scrollTo('right')
+
+ // or you can scroll to a specific coordinate:
+ // (x axis, y axis) in pixels
+ cy.get('#scrollable-vertical').scrollTo(250, 250)
+
+ // or you can scroll to a specific percentage
+ // of the (width, height) of the element
+ cy.get('#scrollable-both').scrollTo('75%', '25%')
+
+ // control the easing of the scroll (default is 'swing')
+ cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
+
+ // control the duration of the scroll (in ms)
+ cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/aliasing.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/aliasing.spec.js
new file mode 100644
index 0000000000..a02fb2bb93
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/aliasing.spec.js
@@ -0,0 +1,39 @@
+///
+
+context('Aliasing', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/aliasing')
+ })
+
+ it('.as() - alias a DOM element for later use', () => {
+ // https://on.cypress.io/as
+
+ // Alias a DOM element for use later
+ // We don't have to traverse to the element
+ // later in our code, we reference it with @
+
+ cy.get('.as-table').find('tbody>tr')
+ .first().find('td').first()
+ .find('button').as('firstBtn')
+
+ // when we reference the alias, we place an
+ // @ in front of its name
+ cy.get('@firstBtn').click()
+
+ cy.get('@firstBtn')
+ .should('have.class', 'btn-success')
+ .and('contain', 'Changed')
+ })
+
+ it('.as() - alias a route for later use', () => {
+ // Alias the route to wait for its response
+ cy.intercept('GET', '**/comments/*').as('getComment')
+
+ // we have code that gets a comment when
+ // the button is clicked in scripts.js
+ cy.get('.network-btn').click()
+
+ // https://on.cypress.io/wait
+ cy.wait('@getComment').its('response.statusCode').should('eq', 200)
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/assertions.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/assertions.spec.js
new file mode 100644
index 0000000000..5ba93d1db6
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/assertions.spec.js
@@ -0,0 +1,177 @@
+///
+
+context('Assertions', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/assertions')
+ })
+
+ describe('Implicit Assertions', () => {
+ it('.should() - make an assertion about the current subject', () => {
+ // https://on.cypress.io/should
+ cy.get('.assertion-table')
+ .find('tbody tr:last')
+ .should('have.class', 'success')
+ .find('td')
+ .first()
+ // checking the text of the element in various ways
+ .should('have.text', 'Column content')
+ .should('contain', 'Column content')
+ .should('have.html', 'Column content')
+ // chai-jquery uses "is()" to check if element matches selector
+ .should('match', 'td')
+ // to match text content against a regular expression
+ // first need to invoke jQuery method text()
+ // and then match using regular expression
+ .invoke('text')
+ .should('match', /column content/i)
+
+ // a better way to check element's text content against a regular expression
+ // is to use "cy.contains"
+ // https://on.cypress.io/contains
+ cy.get('.assertion-table')
+ .find('tbody tr:last')
+ // finds first element with text content matching regular expression
+ .contains('td', /column content/i)
+ .should('be.visible')
+
+ // for more information about asserting element's text
+ // see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-element’s-text-contents
+ })
+
+ it('.and() - chain multiple assertions together', () => {
+ // https://on.cypress.io/and
+ cy.get('.assertions-link')
+ .should('have.class', 'active')
+ .and('have.attr', 'href')
+ .and('include', 'cypress.io')
+ })
+ })
+
+ describe('Explicit Assertions', () => {
+ // https://on.cypress.io/assertions
+ it('expect - make an assertion about a specified subject', () => {
+ // We can use Chai's BDD style assertions
+ expect(true).to.be.true
+ const o = { foo: 'bar' }
+
+ expect(o).to.equal(o)
+ expect(o).to.deep.equal({ foo: 'bar' })
+ // matching text using regular expression
+ expect('FooBar').to.match(/bar$/i)
+ })
+
+ it('pass your own callback function to should()', () => {
+ // Pass a function to should that can have any number
+ // of explicit assertions within it.
+ // The ".should(cb)" function will be retried
+ // automatically until it passes all your explicit assertions or times out.
+ cy.get('.assertions-p')
+ .find('p')
+ .should(($p) => {
+ // https://on.cypress.io/$
+ // return an array of texts from all of the p's
+ // @ts-ignore TS6133 unused variable
+ const texts = $p.map((i, el) => Cypress.$(el).text())
+
+ // jquery map returns jquery object
+ // and .get() convert this to simple array
+ const paragraphs = texts.get()
+
+ // array should have length of 3
+ expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
+
+ // use second argument to expect(...) to provide clear
+ // message with each assertion
+ expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
+ 'Some text from first p',
+ 'More text from second p',
+ 'And even more text from third p',
+ ])
+ })
+ })
+
+ it('finds element by class name regex', () => {
+ cy.get('.docs-header')
+ .find('div')
+ // .should(cb) callback function will be retried
+ .should(($div) => {
+ expect($div).to.have.length(1)
+
+ const className = $div[0].className
+
+ expect(className).to.match(/heading-/)
+ })
+ // .then(cb) callback is not retried,
+ // it either passes or fails
+ .then(($div) => {
+ expect($div, 'text content').to.have.text('Introduction')
+ })
+ })
+
+ it('can throw any error', () => {
+ cy.get('.docs-header')
+ .find('div')
+ .should(($div) => {
+ if ($div.length !== 1) {
+ // you can throw your own errors
+ throw new Error('Did not find 1 element')
+ }
+
+ const className = $div[0].className
+
+ if (!className.match(/heading-/)) {
+ throw new Error(`Could not find class "heading-" in ${className}`)
+ }
+ })
+ })
+
+ it('matches unknown text between two elements', () => {
+ /**
+ * Text from the first element.
+ * @type {string}
+ */
+ let text
+
+ /**
+ * Normalizes passed text,
+ * useful before comparing text with spaces and different capitalization.
+ * @param {string} s Text to normalize
+ */
+ const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
+
+ cy.get('.two-elements')
+ .find('.first')
+ .then(($first) => {
+ // save text from the first element
+ text = normalizeText($first.text())
+ })
+
+ cy.get('.two-elements')
+ .find('.second')
+ .should(($div) => {
+ // we can massage text before comparing
+ const secondText = normalizeText($div.text())
+
+ expect(secondText, 'second text').to.equal(text)
+ })
+ })
+
+ it('assert - assert shape of an object', () => {
+ const person = {
+ name: 'Joe',
+ age: 20,
+ }
+
+ assert.isObject(person, 'value is object')
+ })
+
+ it('retries the should callback until assertions pass', () => {
+ cy.get('#random-number')
+ .should(($div) => {
+ const n = parseFloat($div.text())
+
+ expect(n).to.be.gte(1).and.be.lte(10)
+ })
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/connectors.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/connectors.spec.js
new file mode 100644
index 0000000000..ae8799181d
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/connectors.spec.js
@@ -0,0 +1,97 @@
+///
+
+context('Connectors', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/connectors')
+ })
+
+ it('.each() - iterate over an array of elements', () => {
+ // https://on.cypress.io/each
+ cy.get('.connectors-each-ul>li')
+ .each(($el, index, $list) => {
+ console.log($el, index, $list)
+ })
+ })
+
+ it('.its() - get properties on the current subject', () => {
+ // https://on.cypress.io/its
+ cy.get('.connectors-its-ul>li')
+ // calls the 'length' property yielding that value
+ .its('length')
+ .should('be.gt', 2)
+ })
+
+ it('.invoke() - invoke a function on the current subject', () => {
+ // our div is hidden in our script.js
+ // $('.connectors-div').hide()
+
+ // https://on.cypress.io/invoke
+ cy.get('.connectors-div').should('be.hidden')
+ // call the jquery method 'show' on the 'div.container'
+ .invoke('show')
+ .should('be.visible')
+ })
+
+ it('.spread() - spread an array as individual args to callback function', () => {
+ // https://on.cypress.io/spread
+ const arr = ['foo', 'bar', 'baz']
+
+ cy.wrap(arr).spread((foo, bar, baz) => {
+ expect(foo).to.eq('foo')
+ expect(bar).to.eq('bar')
+ expect(baz).to.eq('baz')
+ })
+ })
+
+ describe('.then()', () => {
+ it('invokes a callback function with the current subject', () => {
+ // https://on.cypress.io/then
+ cy.get('.connectors-list > li')
+ .then(($lis) => {
+ expect($lis, '3 items').to.have.length(3)
+ expect($lis.eq(0), 'first item').to.contain('Walk the dog')
+ expect($lis.eq(1), 'second item').to.contain('Feed the cat')
+ expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
+ })
+ })
+
+ it('yields the returned value to the next command', () => {
+ cy.wrap(1)
+ .then((num) => {
+ expect(num).to.equal(1)
+
+ return 2
+ })
+ .then((num) => {
+ expect(num).to.equal(2)
+ })
+ })
+
+ it('yields the original subject without return', () => {
+ cy.wrap(1)
+ .then((num) => {
+ expect(num).to.equal(1)
+ // note that nothing is returned from this callback
+ })
+ .then((num) => {
+ // this callback receives the original unchanged value 1
+ expect(num).to.equal(1)
+ })
+ })
+
+ it('yields the value yielded by the last Cypress command inside', () => {
+ cy.wrap(1)
+ .then((num) => {
+ expect(num).to.equal(1)
+ // note how we run a Cypress command
+ // the result yielded by this Cypress command
+ // will be passed to the second ".then"
+ cy.wrap(2)
+ })
+ .then((num) => {
+ // this callback receives the value yielded by "cy.wrap(2)"
+ expect(num).to.equal(2)
+ })
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/cookies.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/cookies.spec.js
new file mode 100644
index 0000000000..31587ff907
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/cookies.spec.js
@@ -0,0 +1,77 @@
+///
+
+context('Cookies', () => {
+ beforeEach(() => {
+ Cypress.Cookies.debug(true)
+
+ cy.visit('https://example.cypress.io/commands/cookies')
+
+ // clear cookies again after visiting to remove
+ // any 3rd party cookies picked up such as cloudflare
+ cy.clearCookies()
+ })
+
+ it('cy.getCookie() - get a browser cookie', () => {
+ // https://on.cypress.io/getcookie
+ cy.get('#getCookie .set-a-cookie').click()
+
+ // cy.getCookie() yields a cookie object
+ cy.getCookie('token').should('have.property', 'value', '123ABC')
+ })
+
+ it('cy.getCookies() - get browser cookies', () => {
+ // https://on.cypress.io/getcookies
+ cy.getCookies().should('be.empty')
+
+ cy.get('#getCookies .set-a-cookie').click()
+
+ // cy.getCookies() yields an array of cookies
+ cy.getCookies().should('have.length', 1).should((cookies) => {
+ // each cookie has these properties
+ expect(cookies[0]).to.have.property('name', 'token')
+ expect(cookies[0]).to.have.property('value', '123ABC')
+ expect(cookies[0]).to.have.property('httpOnly', false)
+ expect(cookies[0]).to.have.property('secure', false)
+ expect(cookies[0]).to.have.property('domain')
+ expect(cookies[0]).to.have.property('path')
+ })
+ })
+
+ it('cy.setCookie() - set a browser cookie', () => {
+ // https://on.cypress.io/setcookie
+ cy.getCookies().should('be.empty')
+
+ cy.setCookie('foo', 'bar')
+
+ // cy.getCookie() yields a cookie object
+ cy.getCookie('foo').should('have.property', 'value', 'bar')
+ })
+
+ it('cy.clearCookie() - clear a browser cookie', () => {
+ // https://on.cypress.io/clearcookie
+ cy.getCookie('token').should('be.null')
+
+ cy.get('#clearCookie .set-a-cookie').click()
+
+ cy.getCookie('token').should('have.property', 'value', '123ABC')
+
+ // cy.clearCookies() yields null
+ cy.clearCookie('token').should('be.null')
+
+ cy.getCookie('token').should('be.null')
+ })
+
+ it('cy.clearCookies() - clear browser cookies', () => {
+ // https://on.cypress.io/clearcookies
+ cy.getCookies().should('be.empty')
+
+ cy.get('#clearCookies .set-a-cookie').click()
+
+ cy.getCookies().should('have.length', 1)
+
+ // cy.clearCookies() yields null
+ cy.clearCookies()
+
+ cy.getCookies().should('be.empty')
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/cypress_api.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/cypress_api.spec.js
new file mode 100644
index 0000000000..ec8ceaeda4
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/cypress_api.spec.js
@@ -0,0 +1,202 @@
+///
+
+context('Cypress.Commands', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ // https://on.cypress.io/custom-commands
+
+ it('.add() - create a custom command', () => {
+ Cypress.Commands.add('console', {
+ prevSubject: true,
+ }, (subject, method) => {
+ // the previous subject is automatically received
+ // and the commands arguments are shifted
+
+ // allow us to change the console method used
+ method = method || 'log'
+
+ // log the subject to the console
+ // @ts-ignore TS7017
+ console[method]('The subject is', subject)
+
+ // whatever we return becomes the new subject
+ // we don't want to change the subject so
+ // we return whatever was passed in
+ return subject
+ })
+
+ // @ts-ignore TS2339
+ cy.get('button').console('info').then(($button) => {
+ // subject is still $button
+ })
+ })
+})
+
+context('Cypress.Cookies', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ // https://on.cypress.io/cookies
+ it('.debug() - enable or disable debugging', () => {
+ Cypress.Cookies.debug(true)
+
+ // Cypress will now log in the console when
+ // cookies are set or cleared
+ cy.setCookie('fakeCookie', '123ABC')
+ cy.clearCookie('fakeCookie')
+ cy.setCookie('fakeCookie', '123ABC')
+ cy.clearCookie('fakeCookie')
+ cy.setCookie('fakeCookie', '123ABC')
+ })
+
+ it('.preserveOnce() - preserve cookies by key', () => {
+ // normally cookies are reset after each test
+ cy.getCookie('fakeCookie').should('not.be.ok')
+
+ // preserving a cookie will not clear it when
+ // the next test starts
+ cy.setCookie('lastCookie', '789XYZ')
+ Cypress.Cookies.preserveOnce('lastCookie')
+ })
+
+ it('.defaults() - set defaults for all cookies', () => {
+ // now any cookie with the name 'session_id' will
+ // not be cleared before each new test runs
+ Cypress.Cookies.defaults({
+ preserve: 'session_id',
+ })
+ })
+})
+
+context('Cypress.arch', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Get CPU architecture name of underlying OS', () => {
+ // https://on.cypress.io/arch
+ expect(Cypress.arch).to.exist
+ })
+})
+
+context('Cypress.config()', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Get and set configuration options', () => {
+ // https://on.cypress.io/config
+ let myConfig = Cypress.config()
+
+ expect(myConfig).to.have.property('animationDistanceThreshold', 5)
+ expect(myConfig).to.have.property('baseUrl', null)
+ expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
+ expect(myConfig).to.have.property('requestTimeout', 5000)
+ expect(myConfig).to.have.property('responseTimeout', 30000)
+ expect(myConfig).to.have.property('viewportHeight', 660)
+ expect(myConfig).to.have.property('viewportWidth', 1000)
+ expect(myConfig).to.have.property('pageLoadTimeout', 60000)
+ expect(myConfig).to.have.property('waitForAnimations', true)
+
+ expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
+
+ // this will change the config for the rest of your tests!
+ Cypress.config('pageLoadTimeout', 20000)
+
+ expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
+
+ Cypress.config('pageLoadTimeout', 60000)
+ })
+})
+
+context('Cypress.dom', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ // https://on.cypress.io/dom
+ it('.isHidden() - determine if a DOM element is hidden', () => {
+ let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
+ let visibleP = Cypress.$('.dom-p p.visible').get(0)
+
+ // our first paragraph has css class 'hidden'
+ expect(Cypress.dom.isHidden(hiddenP)).to.be.true
+ expect(Cypress.dom.isHidden(visibleP)).to.be.false
+ })
+})
+
+context('Cypress.env()', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ // We can set environment variables for highly dynamic values
+
+ // https://on.cypress.io/environment-variables
+ it('Get environment variables', () => {
+ // https://on.cypress.io/env
+ // set multiple environment variables
+ Cypress.env({
+ host: 'veronica.dev.local',
+ api_server: 'http://localhost:8888/v1/',
+ })
+
+ // get environment variable
+ expect(Cypress.env('host')).to.eq('veronica.dev.local')
+
+ // set environment variable
+ Cypress.env('api_server', 'http://localhost:8888/v2/')
+ expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
+
+ // get all environment variable
+ expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
+ expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
+ })
+})
+
+context('Cypress.log', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Control what is printed to the Command Log', () => {
+ // https://on.cypress.io/cypress-log
+ })
+})
+
+context('Cypress.platform', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Get underlying OS name', () => {
+ // https://on.cypress.io/platform
+ expect(Cypress.platform).to.be.exist
+ })
+})
+
+context('Cypress.version', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Get current version of Cypress being run', () => {
+ // https://on.cypress.io/version
+ expect(Cypress.version).to.be.exist
+ })
+})
+
+context('Cypress.spec', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/cypress-api')
+ })
+
+ it('Get current spec information', () => {
+ // https://on.cypress.io/spec
+ // wrap the object so we can inspect it easily by clicking in the command log
+ cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/files.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/files.spec.js
new file mode 100644
index 0000000000..b8273430c4
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/files.spec.js
@@ -0,0 +1,88 @@
+///
+
+/// JSON fixture file can be loaded directly using
+// the built-in JavaScript bundler
+// @ts-ignore
+const requiredExample = require('../../fixtures/example')
+
+context('Files', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/files')
+ })
+
+ beforeEach(() => {
+ // load example.json fixture file and store
+ // in the test context object
+ cy.fixture('example.json').as('example')
+ })
+
+ it('cy.fixture() - load a fixture', () => {
+ // https://on.cypress.io/fixture
+
+ // Instead of writing a response inline you can
+ // use a fixture file's content.
+
+ // when application makes an Ajax request matching "GET **/comments/*"
+ // Cypress will intercept it and reply with the object in `example.json` fixture
+ cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment')
+
+ // we have code that gets a comment when
+ // the button is clicked in scripts.js
+ cy.get('.fixture-btn').click()
+
+ cy.wait('@getComment').its('response.body')
+ .should('have.property', 'name')
+ .and('include', 'Using fixtures to represent data')
+ })
+
+ it('cy.fixture() or require - load a fixture', function () {
+ // we are inside the "function () { ... }"
+ // callback and can use test context object "this"
+ // "this.example" was loaded in "beforeEach" function callback
+ expect(this.example, 'fixture in the test context')
+ .to.deep.equal(requiredExample)
+
+ // or use "cy.wrap" and "should('deep.equal', ...)" assertion
+ cy.wrap(this.example)
+ .should('deep.equal', requiredExample)
+ })
+
+ it('cy.readFile() - read file contents', () => {
+ // https://on.cypress.io/readfile
+
+ // You can read a file and yield its contents
+ // The filePath is relative to your project's root.
+ cy.readFile('cypress.json').then((json) => {
+ expect(json).to.be.an('object')
+ })
+ })
+
+ it('cy.writeFile() - write to a file', () => {
+ // https://on.cypress.io/writefile
+
+ // You can write to a file
+
+ // Use a response from a request to automatically
+ // generate a fixture file for use later
+ cy.request('https://jsonplaceholder.cypress.io/users')
+ .then((response) => {
+ cy.writeFile('cypress/fixtures/users.json', response.body)
+ })
+
+ cy.fixture('users').should((users) => {
+ expect(users[0].name).to.exist
+ })
+
+ // JavaScript arrays and objects are stringified
+ // and formatted into text.
+ cy.writeFile('cypress/fixtures/profile.json', {
+ id: 8739,
+ name: 'Jane',
+ email: 'jane@example.com',
+ })
+
+ cy.fixture('profile').should((profile) => {
+ expect(profile.name).to.eq('Jane')
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/local_storage.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/local_storage.spec.js
new file mode 100644
index 0000000000..534d8bd9d3
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/local_storage.spec.js
@@ -0,0 +1,52 @@
+///
+
+context('Local Storage', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/local-storage')
+ })
+ // Although local storage is automatically cleared
+ // in between tests to maintain a clean state
+ // sometimes we need to clear the local storage manually
+
+ it('cy.clearLocalStorage() - clear all data in local storage', () => {
+ // https://on.cypress.io/clearlocalstorage
+ cy.get('.ls-btn').click().should(() => {
+ expect(localStorage.getItem('prop1')).to.eq('red')
+ expect(localStorage.getItem('prop2')).to.eq('blue')
+ expect(localStorage.getItem('prop3')).to.eq('magenta')
+ })
+
+ // clearLocalStorage() yields the localStorage object
+ cy.clearLocalStorage().should((ls) => {
+ expect(ls.getItem('prop1')).to.be.null
+ expect(ls.getItem('prop2')).to.be.null
+ expect(ls.getItem('prop3')).to.be.null
+ })
+
+ cy.get('.ls-btn').click().should(() => {
+ expect(localStorage.getItem('prop1')).to.eq('red')
+ expect(localStorage.getItem('prop2')).to.eq('blue')
+ expect(localStorage.getItem('prop3')).to.eq('magenta')
+ })
+
+ // Clear key matching string in Local Storage
+ cy.clearLocalStorage('prop1').should((ls) => {
+ expect(ls.getItem('prop1')).to.be.null
+ expect(ls.getItem('prop2')).to.eq('blue')
+ expect(ls.getItem('prop3')).to.eq('magenta')
+ })
+
+ cy.get('.ls-btn').click().should(() => {
+ expect(localStorage.getItem('prop1')).to.eq('red')
+ expect(localStorage.getItem('prop2')).to.eq('blue')
+ expect(localStorage.getItem('prop3')).to.eq('magenta')
+ })
+
+ // Clear keys matching regex in Local Storage
+ cy.clearLocalStorage(/prop1|2/).should((ls) => {
+ expect(ls.getItem('prop1')).to.be.null
+ expect(ls.getItem('prop2')).to.be.null
+ expect(ls.getItem('prop3')).to.eq('magenta')
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/location.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/location.spec.js
new file mode 100644
index 0000000000..299867da07
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/location.spec.js
@@ -0,0 +1,32 @@
+///
+
+context('Location', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/location')
+ })
+
+ it('cy.hash() - get the current URL hash', () => {
+ // https://on.cypress.io/hash
+ cy.hash().should('be.empty')
+ })
+
+ it('cy.location() - get window.location', () => {
+ // https://on.cypress.io/location
+ cy.location().should((location) => {
+ expect(location.hash).to.be.empty
+ expect(location.href).to.eq('https://example.cypress.io/commands/location')
+ expect(location.host).to.eq('example.cypress.io')
+ expect(location.hostname).to.eq('example.cypress.io')
+ expect(location.origin).to.eq('https://example.cypress.io')
+ expect(location.pathname).to.eq('/commands/location')
+ expect(location.port).to.eq('')
+ expect(location.protocol).to.eq('https:')
+ expect(location.search).to.be.empty
+ })
+ })
+
+ it('cy.url() - get the current URL', () => {
+ // https://on.cypress.io/url
+ cy.url().should('eq', 'https://example.cypress.io/commands/location')
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/misc.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/misc.spec.js
new file mode 100644
index 0000000000..7222bf4bd1
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/misc.spec.js
@@ -0,0 +1,104 @@
+///
+
+context('Misc', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/misc')
+ })
+
+ it('.end() - end the command chain', () => {
+ // https://on.cypress.io/end
+
+ // cy.end is useful when you want to end a chain of commands
+ // and force Cypress to re-query from the root element
+ cy.get('.misc-table').within(() => {
+ // ends the current chain and yields null
+ cy.contains('Cheryl').click().end()
+
+ // queries the entire table again
+ cy.contains('Charles').click()
+ })
+ })
+
+ it('cy.exec() - execute a system command', () => {
+ // execute a system command.
+ // so you can take actions necessary for
+ // your test outside the scope of Cypress.
+ // https://on.cypress.io/exec
+
+ // we can use Cypress.platform string to
+ // select appropriate command
+ // https://on.cypress/io/platform
+ cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
+
+ // on CircleCI Windows build machines we have a failure to run bash shell
+ // https://github.com/cypress-io/cypress/issues/5169
+ // so skip some of the tests by passing flag "--env circle=true"
+ const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
+
+ if (isCircleOnWindows) {
+ cy.log('Skipping test on CircleCI')
+
+ return
+ }
+
+ // cy.exec problem on Shippable CI
+ // https://github.com/cypress-io/cypress/issues/6718
+ const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
+
+ if (isShippable) {
+ cy.log('Skipping test on ShippableCI')
+
+ return
+ }
+
+ cy.exec('echo Jane Lane')
+ .its('stdout').should('contain', 'Jane Lane')
+
+ if (Cypress.platform === 'win32') {
+ cy.exec('print cypress.json')
+ .its('stderr').should('be.empty')
+ } else {
+ cy.exec('cat cypress.json')
+ .its('stderr').should('be.empty')
+
+ cy.exec('pwd')
+ .its('code').should('eq', 0)
+ }
+ })
+
+ it('cy.focused() - get the DOM element that has focus', () => {
+ // https://on.cypress.io/focused
+ cy.get('.misc-form').find('#name').click()
+ cy.focused().should('have.id', 'name')
+
+ cy.get('.misc-form').find('#description').click()
+ cy.focused().should('have.id', 'description')
+ })
+
+ context('Cypress.Screenshot', function () {
+ it('cy.screenshot() - take a screenshot', () => {
+ // https://on.cypress.io/screenshot
+ cy.screenshot('my-image')
+ })
+
+ it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
+ Cypress.Screenshot.defaults({
+ blackout: ['.foo'],
+ capture: 'viewport',
+ clip: { x: 0, y: 0, width: 200, height: 200 },
+ scale: false,
+ disableTimersAndAnimations: true,
+ screenshotOnRunFailure: true,
+ onBeforeScreenshot () { },
+ onAfterScreenshot () { },
+ })
+ })
+ })
+
+ it('cy.wrap() - wrap an object', () => {
+ // https://on.cypress.io/wrap
+ cy.wrap({ foo: 'bar' })
+ .should('have.property', 'foo')
+ .and('include', 'bar')
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/navigation.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/navigation.spec.js
new file mode 100644
index 0000000000..b85a46890c
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/navigation.spec.js
@@ -0,0 +1,56 @@
+///
+
+context('Navigation', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io')
+ cy.get('.navbar-nav').contains('Commands').click()
+ cy.get('.dropdown-menu').contains('Navigation').click()
+ })
+
+ it('cy.go() - go back or forward in the browser\'s history', () => {
+ // https://on.cypress.io/go
+
+ cy.location('pathname').should('include', 'navigation')
+
+ cy.go('back')
+ cy.location('pathname').should('not.include', 'navigation')
+
+ cy.go('forward')
+ cy.location('pathname').should('include', 'navigation')
+
+ // clicking back
+ cy.go(-1)
+ cy.location('pathname').should('not.include', 'navigation')
+
+ // clicking forward
+ cy.go(1)
+ cy.location('pathname').should('include', 'navigation')
+ })
+
+ it('cy.reload() - reload the page', () => {
+ // https://on.cypress.io/reload
+ cy.reload()
+
+ // reload the page without using the cache
+ cy.reload(true)
+ })
+
+ it('cy.visit() - visit a remote url', () => {
+ // https://on.cypress.io/visit
+
+ // Visit any sub-domain of your current domain
+
+ // Pass options to the visit
+ cy.visit('https://example.cypress.io/commands/navigation', {
+ timeout: 50000, // increase total time for the visit to resolve
+ onBeforeLoad (contentWindow) {
+ // contentWindow is the remote page's window object
+ expect(typeof contentWindow === 'object').to.be.true
+ },
+ onLoad (contentWindow) {
+ // contentWindow is the remote page's window object
+ expect(typeof contentWindow === 'object').to.be.true
+ },
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/network_requests.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/network_requests.spec.js
new file mode 100644
index 0000000000..11213a0e85
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/network_requests.spec.js
@@ -0,0 +1,163 @@
+///
+
+context('Network Requests', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/network-requests')
+ })
+
+ // Manage HTTP requests in your app
+
+ it('cy.request() - make an XHR request', () => {
+ // https://on.cypress.io/request
+ cy.request('https://jsonplaceholder.cypress.io/comments')
+ .should((response) => {
+ expect(response.status).to.eq(200)
+ // the server sometimes gets an extra comment posted from another machine
+ // which gets returned as 1 extra object
+ expect(response.body).to.have.property('length').and.be.oneOf([500, 501])
+ expect(response).to.have.property('headers')
+ expect(response).to.have.property('duration')
+ })
+ })
+
+ it('cy.request() - verify response using BDD syntax', () => {
+ cy.request('https://jsonplaceholder.cypress.io/comments')
+ .then((response) => {
+ // https://on.cypress.io/assertions
+ expect(response).property('status').to.equal(200)
+ expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501])
+ expect(response).to.include.keys('headers', 'duration')
+ })
+ })
+
+ it('cy.request() with query parameters', () => {
+ // will execute request
+ // https://jsonplaceholder.cypress.io/comments?postId=1&id=3
+ cy.request({
+ url: 'https://jsonplaceholder.cypress.io/comments',
+ qs: {
+ postId: 1,
+ id: 3,
+ },
+ })
+ .its('body')
+ .should('be.an', 'array')
+ .and('have.length', 1)
+ .its('0') // yields first element of the array
+ .should('contain', {
+ postId: 1,
+ id: 3,
+ })
+ })
+
+ it('cy.request() - pass result to the second request', () => {
+ // first, let's find out the userId of the first user we have
+ cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
+ .its('body') // yields the response object
+ .its('0') // yields the first element of the returned list
+ // the above two commands its('body').its('0')
+ // can be written as its('body.0')
+ // if you do not care about TypeScript checks
+ .then((user) => {
+ expect(user).property('id').to.be.a('number')
+ // make a new post on behalf of the user
+ cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
+ userId: user.id,
+ title: 'Cypress Test Runner',
+ body: 'Fast, easy and reliable testing for anything that runs in a browser.',
+ })
+ })
+ // note that the value here is the returned value of the 2nd request
+ // which is the new post object
+ .then((response) => {
+ expect(response).property('status').to.equal(201) // new entity created
+ expect(response).property('body').to.contain({
+ title: 'Cypress Test Runner',
+ })
+
+ // we don't know the exact post id - only that it will be > 100
+ // since JSONPlaceholder has built-in 100 posts
+ expect(response.body).property('id').to.be.a('number')
+ .and.to.be.gt(100)
+
+ // we don't know the user id here - since it was in above closure
+ // so in this test just confirm that the property is there
+ expect(response.body).property('userId').to.be.a('number')
+ })
+ })
+
+ it('cy.request() - save response in the shared test context', () => {
+ // https://on.cypress.io/variables-and-aliases
+ cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
+ .its('body').its('0') // yields the first element of the returned list
+ .as('user') // saves the object in the test context
+ .then(function () {
+ // NOTE 👀
+ // By the time this callback runs the "as('user')" command
+ // has saved the user object in the test context.
+ // To access the test context we need to use
+ // the "function () { ... }" callback form,
+ // otherwise "this" points at a wrong or undefined object!
+ cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
+ userId: this.user.id,
+ title: 'Cypress Test Runner',
+ body: 'Fast, easy and reliable testing for anything that runs in a browser.',
+ })
+ .its('body').as('post') // save the new post from the response
+ })
+ .then(function () {
+ // When this callback runs, both "cy.request" API commands have finished
+ // and the test context has "user" and "post" objects set.
+ // Let's verify them.
+ expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
+ })
+ })
+
+ it('cy.intercept() - route responses to matching requests', () => {
+ // https://on.cypress.io/intercept
+
+ let message = 'whoa, this comment does not exist'
+
+ // Listen to GET to comments/1
+ cy.intercept('GET', '**/comments/*').as('getComment')
+
+ // we have code that gets a comment when
+ // the button is clicked in scripts.js
+ cy.get('.network-btn').click()
+
+ // https://on.cypress.io/wait
+ cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
+
+ // Listen to POST to comments
+ cy.intercept('POST', '**/comments').as('postComment')
+
+ // we have code that posts a comment when
+ // the button is clicked in scripts.js
+ cy.get('.network-post').click()
+ cy.wait('@postComment').should(({ request, response }) => {
+ expect(request.body).to.include('email')
+ expect(request.headers).to.have.property('content-type')
+ expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()')
+ })
+
+ // Stub a response to PUT comments/ ****
+ cy.intercept({
+ method: 'PUT',
+ url: '**/comments/*',
+ }, {
+ statusCode: 404,
+ body: { error: message },
+ headers: { 'access-control-allow-origin': '*' },
+ delayMs: 500,
+ }).as('putComment')
+
+ // we have code that puts a comment when
+ // the button is clicked in scripts.js
+ cy.get('.network-put').click()
+
+ cy.wait('@putComment')
+
+ // our 404 statusCode logic in scripts.js executed
+ cy.get('.network-put-comment').should('contain', message)
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/querying.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/querying.spec.js
new file mode 100644
index 0000000000..00970480f6
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/querying.spec.js
@@ -0,0 +1,114 @@
+///
+
+context('Querying', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/querying')
+ })
+
+ // The most commonly used query is 'cy.get()', you can
+ // think of this like the '$' in jQuery
+
+ it('cy.get() - query DOM elements', () => {
+ // https://on.cypress.io/get
+
+ cy.get('#query-btn').should('contain', 'Button')
+
+ cy.get('.query-btn').should('contain', 'Button')
+
+ cy.get('#querying .well>button:first').should('contain', 'Button')
+ // ↲
+ // Use CSS selectors just like jQuery
+
+ cy.get('[data-test-id="test-example"]').should('have.class', 'example')
+
+ // 'cy.get()' yields jQuery object, you can get its attribute
+ // by invoking `.attr()` method
+ cy.get('[data-test-id="test-example"]')
+ .invoke('attr', 'data-test-id')
+ .should('equal', 'test-example')
+
+ // or you can get element's CSS property
+ cy.get('[data-test-id="test-example"]')
+ .invoke('css', 'position')
+ .should('equal', 'static')
+
+ // or use assertions directly during 'cy.get()'
+ // https://on.cypress.io/assertions
+ cy.get('[data-test-id="test-example"]')
+ .should('have.attr', 'data-test-id', 'test-example')
+ .and('have.css', 'position', 'static')
+ })
+
+ it('cy.contains() - query DOM elements with matching content', () => {
+ // https://on.cypress.io/contains
+ cy.get('.query-list')
+ .contains('bananas')
+ .should('have.class', 'third')
+
+ // we can pass a regexp to `.contains()`
+ cy.get('.query-list')
+ .contains(/^b\w+/)
+ .should('have.class', 'third')
+
+ cy.get('.query-list')
+ .contains('apples')
+ .should('have.class', 'first')
+
+ // passing a selector to contains will
+ // yield the selector containing the text
+ cy.get('#querying')
+ .contains('ul', 'oranges')
+ .should('have.class', 'query-list')
+
+ cy.get('.query-button')
+ .contains('Save Form')
+ .should('have.class', 'btn')
+ })
+
+ it('.within() - query DOM elements within a specific element', () => {
+ // https://on.cypress.io/within
+ cy.get('.query-form').within(() => {
+ cy.get('input:first').should('have.attr', 'placeholder', 'Email')
+ cy.get('input:last').should('have.attr', 'placeholder', 'Password')
+ })
+ })
+
+ it('cy.root() - query the root DOM element', () => {
+ // https://on.cypress.io/root
+
+ // By default, root is the document
+ cy.root().should('match', 'html')
+
+ cy.get('.query-ul').within(() => {
+ // In this within, the root is now the ul DOM element
+ cy.root().should('have.class', 'query-ul')
+ })
+ })
+
+ it('best practices - selecting elements', () => {
+ // https://on.cypress.io/best-practices#Selecting-Elements
+ cy.get('[data-cy=best-practices-selecting-elements]').within(() => {
+ // Worst - too generic, no context
+ cy.get('button').click()
+
+ // Bad. Coupled to styling. Highly subject to change.
+ cy.get('.btn.btn-large').click()
+
+ // Average. Coupled to the `name` attribute which has HTML semantics.
+ cy.get('[name=submission]').click()
+
+ // Better. But still coupled to styling or JS event listeners.
+ cy.get('#main').click()
+
+ // Slightly better. Uses an ID but also ensures the element
+ // has an ARIA role attribute
+ cy.get('#main[role=button]').click()
+
+ // Much better. But still coupled to text content that may change.
+ cy.contains('Submit').click()
+
+ // Best. Insulated from all changes.
+ cy.get('[data-cy=submit]').click()
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/spies_stubs_clocks.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/spies_stubs_clocks.spec.js
new file mode 100644
index 0000000000..18b643ecd5
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/spies_stubs_clocks.spec.js
@@ -0,0 +1,205 @@
+///
+// remove no check once Cypress.sinon is typed
+// https://github.com/cypress-io/cypress/issues/6720
+
+context('Spies, Stubs, and Clock', () => {
+ it('cy.spy() - wrap a method in a spy', () => {
+ // https://on.cypress.io/spy
+ cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+
+ const obj = {
+ foo () {},
+ }
+
+ const spy = cy.spy(obj, 'foo').as('anyArgs')
+
+ obj.foo()
+
+ expect(spy).to.be.called
+ })
+
+ it('cy.spy() retries until assertions pass', () => {
+ cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+
+ const obj = {
+ /**
+ * Prints the argument passed
+ * @param x {any}
+ */
+ foo (x) {
+ console.log('obj.foo called with', x)
+ },
+ }
+
+ cy.spy(obj, 'foo').as('foo')
+
+ setTimeout(() => {
+ obj.foo('first')
+ }, 500)
+
+ setTimeout(() => {
+ obj.foo('second')
+ }, 2500)
+
+ cy.get('@foo').should('have.been.calledTwice')
+ })
+
+ it('cy.stub() - create a stub and/or replace a function with stub', () => {
+ // https://on.cypress.io/stub
+ cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+
+ const obj = {
+ /**
+ * prints both arguments to the console
+ * @param a {string}
+ * @param b {string}
+ */
+ foo (a, b) {
+ console.log('a', a, 'b', b)
+ },
+ }
+
+ const stub = cy.stub(obj, 'foo').as('foo')
+
+ obj.foo('foo', 'bar')
+
+ expect(stub).to.be.called
+ })
+
+ it('cy.clock() - control time in the browser', () => {
+ // https://on.cypress.io/clock
+
+ // create the date in UTC so its always the same
+ // no matter what local timezone the browser is running in
+ const now = new Date(Date.UTC(2017, 2, 14)).getTime()
+
+ cy.clock(now)
+ cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+ cy.get('#clock-div').click()
+ .should('have.text', '1489449600')
+ })
+
+ it('cy.tick() - move time in the browser', () => {
+ // https://on.cypress.io/tick
+
+ // create the date in UTC so its always the same
+ // no matter what local timezone the browser is running in
+ const now = new Date(Date.UTC(2017, 2, 14)).getTime()
+
+ cy.clock(now)
+ cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
+ cy.get('#tick-div').click()
+ .should('have.text', '1489449600')
+
+ cy.tick(10000) // 10 seconds passed
+ cy.get('#tick-div').click()
+ .should('have.text', '1489449610')
+ })
+
+ it('cy.stub() matches depending on arguments', () => {
+ // see all possible matchers at
+ // https://sinonjs.org/releases/latest/matchers/
+ const greeter = {
+ /**
+ * Greets a person
+ * @param {string} name
+ */
+ greet (name) {
+ return `Hello, ${name}!`
+ },
+ }
+
+ cy.stub(greeter, 'greet')
+ .callThrough() // if you want non-matched calls to call the real method
+ .withArgs(Cypress.sinon.match.string).returns('Hi')
+ .withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
+
+ expect(greeter.greet('World')).to.equal('Hi')
+ // @ts-ignore
+ expect(() => greeter.greet(42)).to.throw('Invalid name')
+ expect(greeter.greet).to.have.been.calledTwice
+
+ // non-matched calls goes the actual method
+ // @ts-ignore
+ expect(greeter.greet()).to.equal('Hello, undefined!')
+ })
+
+ it('matches call arguments using Sinon matchers', () => {
+ // see all possible matchers at
+ // https://sinonjs.org/releases/latest/matchers/
+ const calculator = {
+ /**
+ * returns the sum of two arguments
+ * @param a {number}
+ * @param b {number}
+ */
+ add (a, b) {
+ return a + b
+ },
+ }
+
+ const spy = cy.spy(calculator, 'add').as('add')
+
+ expect(calculator.add(2, 3)).to.equal(5)
+
+ // if we want to assert the exact values used during the call
+ expect(spy).to.be.calledWith(2, 3)
+
+ // let's confirm "add" method was called with two numbers
+ expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
+
+ // alternatively, provide the value to match
+ expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
+
+ // match any value
+ expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
+
+ // match any value from a list
+ expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
+
+ /**
+ * Returns true if the given number is event
+ * @param {number} x
+ */
+ const isEven = (x) => x % 2 === 0
+
+ // expect the value to pass a custom predicate function
+ // the second argument to "sinon.match(predicate, message)" is
+ // shown if the predicate does not pass and assertion fails
+ expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
+
+ /**
+ * Returns a function that checks if a given number is larger than the limit
+ * @param {number} limit
+ * @returns {(x: number) => boolean}
+ */
+ const isGreaterThan = (limit) => (x) => x > limit
+
+ /**
+ * Returns a function that checks if a given number is less than the limit
+ * @param {number} limit
+ * @returns {(x: number) => boolean}
+ */
+ const isLessThan = (limit) => (x) => x < limit
+
+ // you can combine several matchers using "and", "or"
+ expect(spy).to.be.calledWith(
+ Cypress.sinon.match.number,
+ Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')),
+ )
+
+ expect(spy).to.be.calledWith(
+ Cypress.sinon.match.number,
+ Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)),
+ )
+
+ // matchers can be used from BDD assertions
+ cy.get('@add').should('have.been.calledWith',
+ Cypress.sinon.match.number, Cypress.sinon.match(3))
+
+ // you can alias matchers for shorter test code
+ const { match: M } = Cypress.sinon
+
+ cy.get('@add').should('have.been.calledWith', M.number, M(3))
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/traversal.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/traversal.spec.js
new file mode 100644
index 0000000000..0a3b9d3306
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/traversal.spec.js
@@ -0,0 +1,121 @@
+///
+
+context('Traversal', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/traversal')
+ })
+
+ it('.children() - get child DOM elements', () => {
+ // https://on.cypress.io/children
+ cy.get('.traversal-breadcrumb')
+ .children('.active')
+ .should('contain', 'Data')
+ })
+
+ it('.closest() - get closest ancestor DOM element', () => {
+ // https://on.cypress.io/closest
+ cy.get('.traversal-badge')
+ .closest('ul')
+ .should('have.class', 'list-group')
+ })
+
+ it('.eq() - get a DOM element at a specific index', () => {
+ // https://on.cypress.io/eq
+ cy.get('.traversal-list>li')
+ .eq(1).should('contain', 'siamese')
+ })
+
+ it('.filter() - get DOM elements that match the selector', () => {
+ // https://on.cypress.io/filter
+ cy.get('.traversal-nav>li')
+ .filter('.active').should('contain', 'About')
+ })
+
+ it('.find() - get descendant DOM elements of the selector', () => {
+ // https://on.cypress.io/find
+ cy.get('.traversal-pagination')
+ .find('li').find('a')
+ .should('have.length', 7)
+ })
+
+ it('.first() - get first DOM element', () => {
+ // https://on.cypress.io/first
+ cy.get('.traversal-table td')
+ .first().should('contain', '1')
+ })
+
+ it('.last() - get last DOM element', () => {
+ // https://on.cypress.io/last
+ cy.get('.traversal-buttons .btn')
+ .last().should('contain', 'Submit')
+ })
+
+ it('.next() - get next sibling DOM element', () => {
+ // https://on.cypress.io/next
+ cy.get('.traversal-ul')
+ .contains('apples').next().should('contain', 'oranges')
+ })
+
+ it('.nextAll() - get all next sibling DOM elements', () => {
+ // https://on.cypress.io/nextall
+ cy.get('.traversal-next-all')
+ .contains('oranges')
+ .nextAll().should('have.length', 3)
+ })
+
+ it('.nextUntil() - get next sibling DOM elements until next el', () => {
+ // https://on.cypress.io/nextuntil
+ cy.get('#veggies')
+ .nextUntil('#nuts').should('have.length', 3)
+ })
+
+ it('.not() - remove DOM elements from set of DOM elements', () => {
+ // https://on.cypress.io/not
+ cy.get('.traversal-disabled .btn')
+ .not('[disabled]').should('not.contain', 'Disabled')
+ })
+
+ it('.parent() - get parent DOM element from DOM elements', () => {
+ // https://on.cypress.io/parent
+ cy.get('.traversal-mark')
+ .parent().should('contain', 'Morbi leo risus')
+ })
+
+ it('.parents() - get parent DOM elements from DOM elements', () => {
+ // https://on.cypress.io/parents
+ cy.get('.traversal-cite')
+ .parents().should('match', 'blockquote')
+ })
+
+ it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
+ // https://on.cypress.io/parentsuntil
+ cy.get('.clothes-nav')
+ .find('.active')
+ .parentsUntil('.clothes-nav')
+ .should('have.length', 2)
+ })
+
+ it('.prev() - get previous sibling DOM element', () => {
+ // https://on.cypress.io/prev
+ cy.get('.birds').find('.active')
+ .prev().should('contain', 'Lorikeets')
+ })
+
+ it('.prevAll() - get all previous sibling DOM elements', () => {
+ // https://on.cypress.io/prevall
+ cy.get('.fruits-list').find('.third')
+ .prevAll().should('have.length', 2)
+ })
+
+ it('.prevUntil() - get all previous sibling DOM elements until el', () => {
+ // https://on.cypress.io/prevuntil
+ cy.get('.foods-list').find('#nuts')
+ .prevUntil('#veggies').should('have.length', 3)
+ })
+
+ it('.siblings() - get all sibling DOM elements', () => {
+ // https://on.cypress.io/siblings
+ cy.get('.traversal-pills .active')
+ .siblings().should('have.length', 2)
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/utilities.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/utilities.spec.js
new file mode 100644
index 0000000000..24e61a6a7c
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/utilities.spec.js
@@ -0,0 +1,110 @@
+///
+
+context('Utilities', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/utilities')
+ })
+
+ it('Cypress._ - call a lodash method', () => {
+ // https://on.cypress.io/_
+ cy.request('https://jsonplaceholder.cypress.io/users')
+ .then((response) => {
+ let ids = Cypress._.chain(response.body).map('id').take(3).value()
+
+ expect(ids).to.deep.eq([1, 2, 3])
+ })
+ })
+
+ it('Cypress.$ - call a jQuery method', () => {
+ // https://on.cypress.io/$
+ let $li = Cypress.$('.utility-jquery li:first')
+
+ cy.wrap($li)
+ .should('not.have.class', 'active')
+ .click()
+ .should('have.class', 'active')
+ })
+
+ it('Cypress.Blob - blob utilities and base64 string conversion', () => {
+ // https://on.cypress.io/blob
+ cy.get('.utility-blob').then(($div) => {
+ // https://github.com/nolanlawson/blob-util#imgSrcToDataURL
+ // get the dataUrl string for the javascript-logo
+ return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
+ .then((dataUrl) => {
+ // create an element and set its src to the dataUrl
+ let img = Cypress.$(' ', { src: dataUrl })
+
+ // need to explicitly return cy here since we are initially returning
+ // the Cypress.Blob.imgSrcToDataURL promise to our test
+ // append the image
+ $div.append(img)
+
+ cy.get('.utility-blob img').click()
+ .should('have.attr', 'src', dataUrl)
+ })
+ })
+ })
+
+ it('Cypress.minimatch - test out glob patterns against strings', () => {
+ // https://on.cypress.io/minimatch
+ let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
+ matchBase: true,
+ })
+
+ expect(matching, 'matching wildcard').to.be.true
+
+ matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
+ matchBase: true,
+ })
+
+ expect(matching, 'comments').to.be.false
+
+ // ** matches against all downstream path segments
+ matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
+ matchBase: true,
+ })
+
+ expect(matching, 'comments').to.be.true
+
+ // whereas * matches only the next path segment
+
+ matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
+ matchBase: false,
+ })
+
+ expect(matching, 'comments').to.be.false
+ })
+
+ it('Cypress.Promise - instantiate a bluebird promise', () => {
+ // https://on.cypress.io/promise
+ let waited = false
+
+ /**
+ * @return Bluebird
+ */
+ function waitOneSecond () {
+ // return a promise that resolves after 1 second
+ // @ts-ignore TS2351 (new Cypress.Promise)
+ return new Cypress.Promise((resolve, reject) => {
+ setTimeout(() => {
+ // set waited to true
+ waited = true
+
+ // resolve with 'foo' string
+ resolve('foo')
+ }, 1000)
+ })
+ }
+
+ cy.then(() => {
+ // return a promise to cy.then() that
+ // is awaited until it resolves
+ // @ts-ignore TS7006
+ return waitOneSecond().then((str) => {
+ expect(str).to.eq('foo')
+ expect(waited).to.be.true
+ })
+ })
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/viewport.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/viewport.spec.js
new file mode 100644
index 0000000000..dbcd7eeddd
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/viewport.spec.js
@@ -0,0 +1,59 @@
+///
+
+context('Viewport', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/viewport')
+ })
+
+ it('cy.viewport() - set the viewport size and dimension', () => {
+ // https://on.cypress.io/viewport
+
+ cy.get('#navbar').should('be.visible')
+ cy.viewport(320, 480)
+
+ // the navbar should have collapse since our screen is smaller
+ cy.get('#navbar').should('not.be.visible')
+ cy.get('.navbar-toggle').should('be.visible').click()
+ cy.get('.nav').find('a').should('be.visible')
+
+ // lets see what our app looks like on a super large screen
+ cy.viewport(2999, 2999)
+
+ // cy.viewport() accepts a set of preset sizes
+ // to easily set the screen to a device's width and height
+
+ // We added a cy.wait() between each viewport change so you can see
+ // the change otherwise it is a little too fast to see :)
+
+ cy.viewport('macbook-15')
+ cy.wait(200)
+ cy.viewport('macbook-13')
+ cy.wait(200)
+ cy.viewport('macbook-11')
+ cy.wait(200)
+ cy.viewport('ipad-2')
+ cy.wait(200)
+ cy.viewport('ipad-mini')
+ cy.wait(200)
+ cy.viewport('iphone-6+')
+ cy.wait(200)
+ cy.viewport('iphone-6')
+ cy.wait(200)
+ cy.viewport('iphone-5')
+ cy.wait(200)
+ cy.viewport('iphone-4')
+ cy.wait(200)
+ cy.viewport('iphone-3')
+ cy.wait(200)
+
+ // cy.viewport() accepts an orientation for all presets
+ // the default orientation is 'portrait'
+ cy.viewport('ipad-2', 'portrait')
+ cy.wait(200)
+ cy.viewport('iphone-4', 'landscape')
+ cy.wait(200)
+
+ // The viewport will be reset back to the default dimensions
+ // in between tests (the default can be set in cypress.json)
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/waiting.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/waiting.spec.js
new file mode 100644
index 0000000000..c8f0d7c672
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/waiting.spec.js
@@ -0,0 +1,31 @@
+///
+
+context('Waiting', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/waiting')
+ })
+ // BE CAREFUL of adding unnecessary wait times.
+ // https://on.cypress.io/best-practices#Unnecessary-Waiting
+
+ // https://on.cypress.io/wait
+ it('cy.wait() - wait for a specific amount of time', () => {
+ cy.get('.wait-input1').type('Wait 1000ms after typing')
+ cy.wait(1000)
+ cy.get('.wait-input2').type('Wait 1000ms after typing')
+ cy.wait(1000)
+ cy.get('.wait-input3').type('Wait 1000ms after typing')
+ cy.wait(1000)
+ })
+
+ it('cy.wait() - wait for a specific route', () => {
+ // Listen to GET to comments/1
+ cy.intercept('GET', '**/comments/*').as('getComment')
+
+ // we have code that gets a comment when
+ // the button is clicked in scripts.js
+ cy.get('.network-btn').click()
+
+ // wait for GET comments/1
+ cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
+ })
+})
diff --git a/frontend/cypress/integration-examples/2-advanced-examples/window.spec.js b/frontend/cypress/integration-examples/2-advanced-examples/window.spec.js
new file mode 100644
index 0000000000..f94b64971d
--- /dev/null
+++ b/frontend/cypress/integration-examples/2-advanced-examples/window.spec.js
@@ -0,0 +1,22 @@
+///
+
+context('Window', () => {
+ beforeEach(() => {
+ cy.visit('https://example.cypress.io/commands/window')
+ })
+
+ it('cy.window() - get the global window object', () => {
+ // https://on.cypress.io/window
+ cy.window().should('have.property', 'top')
+ })
+
+ it('cy.document() - get the document object', () => {
+ // https://on.cypress.io/document
+ cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
+ })
+
+ it('cy.title() - get the title', () => {
+ // https://on.cypress.io/title
+ cy.title().should('include', 'Kitchen Sink')
+ })
+})
diff --git a/frontend/cypress/integration/01-auth/login.spec.js b/frontend/cypress/integration/01-auth/login.spec.js
new file mode 100644
index 0000000000..f3b84e2aff
--- /dev/null
+++ b/frontend/cypress/integration/01-auth/login.spec.js
@@ -0,0 +1,21 @@
+/**
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * Copyright (c) UXBOX Labs SL
+ */
+
+"use strict";
+
+describe("login", () => {
+ beforeEach(() => {
+ cy.visit("http://localhost:3449/#/auth/login");
+ });
+
+ it("displays the login form", () => {
+ cy.get("#email").should("exist");
+ cy.get("#password").should("exist");
+ });
+});
+
diff --git a/frontend/cypress/plugins/index.js b/frontend/cypress/plugins/index.js
new file mode 100644
index 0000000000..59b2bab6e4
--- /dev/null
+++ b/frontend/cypress/plugins/index.js
@@ -0,0 +1,22 @@
+///
+// ***********************************************************
+// This example plugins/index.js can be used to load plugins
+//
+// You can change the location of this file or turn off loading
+// the plugins file with the 'pluginsFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/plugins-guide
+// ***********************************************************
+
+// This function is called when a project is opened or re-opened (e.g. due to
+// the project's config changing)
+
+/**
+ * @type {Cypress.PluginConfig}
+ */
+// eslint-disable-next-line no-unused-vars
+module.exports = (on, config) => {
+ // `on` is used to hook into various events Cypress emits
+ // `config` is the resolved Cypress config
+}
diff --git a/frontend/cypress/support/commands.js b/frontend/cypress/support/commands.js
new file mode 100644
index 0000000000..119ab03f7c
--- /dev/null
+++ b/frontend/cypress/support/commands.js
@@ -0,0 +1,25 @@
+// ***********************************************
+// This example commands.js shows you how to
+// create various custom commands and overwrite
+// existing commands.
+//
+// For more comprehensive examples of custom
+// commands please read more here:
+// https://on.cypress.io/custom-commands
+// ***********************************************
+//
+//
+// -- This is a parent command --
+// Cypress.Commands.add('login', (email, password) => { ... })
+//
+//
+// -- This is a child command --
+// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
+//
+//
+// -- This is a dual command --
+// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
+//
+//
+// -- This will overwrite an existing command --
+// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
diff --git a/frontend/cypress/support/index.js b/frontend/cypress/support/index.js
new file mode 100644
index 0000000000..d68db96df2
--- /dev/null
+++ b/frontend/cypress/support/index.js
@@ -0,0 +1,20 @@
+// ***********************************************************
+// This example support/index.js is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
diff --git a/frontend/deps.edn b/frontend/deps.edn
index b2f85c8b1f..56225cef59 100644
--- a/frontend/deps.edn
+++ b/frontend/deps.edn
@@ -9,7 +9,7 @@
funcool/beicon {:mvn/version "2021.07.05-1"}
funcool/okulary {:mvn/version "2020.04.14-0"}
funcool/potok {:mvn/version "2021.09.20-0"}
- funcool/rumext {:mvn/version "2021.05.12-1"}
+ funcool/rumext {:mvn/version "2022.01.20.128"}
funcool/tubax {:mvn/version "2021.05.20-0"}
instaparse/instaparse {:mvn/version "1.4.10"}
@@ -17,15 +17,21 @@
:aliases
{:outdated
- {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}
- org.slf4j/slf4j-nop {:mvn/version "RELEASE"}}
+ {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}}
:main-opts ["-m" "antq.core"]}
+ :jvm-repl
+ {:extra-deps
+ {com.bhauman/rebel-readline {:mvn/version "RELEASE"}}
+ :main-opts ["-m" "rebel-readline.main"]}
+
+
:dev
{:extra-paths ["dev"]
:extra-deps
- {thheller/shadow-cljs {:mvn/version "2.15.12"}
- cider/cider-nrepl {:mvn/version "0.26.0"}}}
+ {thheller/shadow-cljs {:mvn/version "2.16.12"}
+ org.clojure/tools.namespace {:mvn/version "RELEASE"}
+ cider/cider-nrepl {:mvn/version "0.28.0"}}}
:shadow-cljs
{:main-opts ["-m" "shadow.cljs.devtools.cli"]}
diff --git a/frontend/package.json b/frontend/package.json
index 63d5aed696..5c484d65e1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -2,7 +2,7 @@
"name": "app",
"version": "0.1.0",
"description": "The Open-Source prototyping tool",
- "author": "UXBOX LABS SL",
+ "author": "Kaleidos Ventures SL",
"license": "SEE LICENSE IN ",
"repository": {
"type": "git",
@@ -12,19 +12,21 @@
"defaults"
],
"scripts": {
- "validate-translations": "node ./scripts/validate-translations.js",
- "watch-main": "shadow-cljs watch main",
+ "compile-test": "clojure -M:dev:shadow-cljs compile test --config-merge '{:autorun false}'",
+ "lint-scss": "yarn run prettier -c resources/styles",
+ "run-test": "node target/test.js",
+ "test": "yarn run compile-test && yarn run run-test",
"watch-gulp": "gulp watch",
- "test-watch-compile": "shadow-cljs watch test",
- "test-run": "node target/tests.js",
- "test-watch-run": "nodemon --signal SIGKILL --watch target --exec npm run test-run",
- "test-watch": "npm-run-all --parallel test-watch-compile test-watch-run",
- "test": "npm run test-watch",
- "start": "npm-run-all --parallel watch-gulp watch-main"
+ "watch-main": "shadow-cljs watch main",
+ "watch-test": "clojure -M:dev:shadow-cljs watch test",
+ "validate-translations": "node ./scripts/validate-translations.js",
+ "test-e2e": "cypress run",
+ "test-e2e-gui": "cypress open"
},
"devDependencies": {
- "autoprefixer": "^10.3.7",
- "gettext-parser": "^4.0.4",
+ "autoprefixer": "^10.4.1",
+ "cypress": "^9.2.0",
+ "gettext-parser": "^4.2.0",
"gulp": "4.0.2",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.2",
@@ -35,25 +37,26 @@
"gulp-sourcemaps": "^3.0.0",
"gulp-svg-sprite": "^1.5.0",
"map-stream": "0.0.7",
- "marked": "^3.0.8",
+ "marked": "^4.0.8",
"mkdirp": "^1.0.4",
- "nodemon": "^2.0.14",
+ "nodemon": "^2.0.15",
"npm-run-all": "^4.1.5",
- "postcss": "^8.3.11",
+ "postcss": "^8.4.5",
"postcss-clean": "^1.2.2",
+ "prettier": "^2.5.1",
"rimraf": "^3.0.0",
- "sass": "^1.43.4",
- "shadow-cljs": "2.15.12"
+ "sass": "^1.45.1",
+ "shadow-cljs": "2.16.12"
},
"dependencies": {
- "@sentry/browser": "^6.13.3",
- "@sentry/tracing": "^6.13.3",
- "date-fns": "^2.25.0",
+ "@sentry/browser": "^6.16.1",
+ "@sentry/tracing": "^6.16.1",
+ "date-fns": "^2.28.0",
"draft-js": "^0.11.7",
"highlight.js": "^11.3.1",
"js-beautify": "^1.14.0",
"jszip": "^3.6.0",
- "luxon": "^2.0.2",
+ "luxon": "^2.2.0",
"mousetrap": "^1.6.5",
"opentype.js": "^1.3.4",
"randomcolor": "^0.6.2",
@@ -62,7 +65,7 @@
"react-virtualized": "^9.22.3",
"rxjs": "~7.4.0",
"sax": "^1.2.4",
- "source-map-support": "^0.5.16",
+ "source-map-support": "^0.5.21",
"tdigest": "^0.1.1",
"ua-parser-js": "^1.0.2",
"xregexp": "^5.0.1"
diff --git a/frontend/resources/images/features/1.11-animations.gif b/frontend/resources/images/features/1.11-animations.gif
new file mode 100644
index 0000000000..3b56b1bf2d
Binary files /dev/null and b/frontend/resources/images/features/1.11-animations.gif differ
diff --git a/frontend/resources/images/features/1.11-bg-export.gif b/frontend/resources/images/features/1.11-bg-export.gif
new file mode 100644
index 0000000000..72c1cbbbe6
Binary files /dev/null and b/frontend/resources/images/features/1.11-bg-export.gif differ
diff --git a/frontend/resources/images/features/1.11-zoom-widget.gif b/frontend/resources/images/features/1.11-zoom-widget.gif
new file mode 100644
index 0000000000..dd96e2d946
Binary files /dev/null and b/frontend/resources/images/features/1.11-zoom-widget.gif differ
diff --git a/frontend/resources/images/icons/animate-down.svg b/frontend/resources/images/icons/animate-down.svg
new file mode 100644
index 0000000000..6eb7a3501a
--- /dev/null
+++ b/frontend/resources/images/icons/animate-down.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/animate-left.svg b/frontend/resources/images/icons/animate-left.svg
new file mode 100644
index 0000000000..39318e4cc4
--- /dev/null
+++ b/frontend/resources/images/icons/animate-left.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/animate-right.svg b/frontend/resources/images/icons/animate-right.svg
new file mode 100644
index 0000000000..aadf5a05b9
--- /dev/null
+++ b/frontend/resources/images/icons/animate-right.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/animate-up.svg b/frontend/resources/images/icons/animate-up.svg
new file mode 100644
index 0000000000..31e8774238
--- /dev/null
+++ b/frontend/resources/images/icons/animate-up.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/easing-ease-in-out.svg b/frontend/resources/images/icons/easing-ease-in-out.svg
new file mode 100644
index 0000000000..3ada98e194
--- /dev/null
+++ b/frontend/resources/images/icons/easing-ease-in-out.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/easing-ease-in.svg b/frontend/resources/images/icons/easing-ease-in.svg
new file mode 100644
index 0000000000..40d2bbd249
--- /dev/null
+++ b/frontend/resources/images/icons/easing-ease-in.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/easing-ease-out.svg b/frontend/resources/images/icons/easing-ease-out.svg
new file mode 100644
index 0000000000..9bf6cd73f2
--- /dev/null
+++ b/frontend/resources/images/icons/easing-ease-out.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/easing-ease.svg b/frontend/resources/images/icons/easing-ease.svg
new file mode 100644
index 0000000000..7760f329df
--- /dev/null
+++ b/frontend/resources/images/icons/easing-ease.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/images/icons/easing-linear.svg b/frontend/resources/images/icons/easing-linear.svg
new file mode 100644
index 0000000000..f3ab49784e
--- /dev/null
+++ b/frontend/resources/images/icons/easing-linear.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss
index 54032cad67..34ff9c2320 100644
--- a/frontend/resources/styles/common/base.scss
+++ b/frontend/resources/styles/common/base.scss
@@ -10,7 +10,7 @@ body {
color: $color-gray-20;
display: flex;
flex-direction: column;
- font-family: 'worksans', sans-serif;
+ font-family: "worksans", sans-serif;
height: 100vh;
overflow: hidden;
}
@@ -30,7 +30,6 @@ body {
object {
transition: none;
}
-
}
img {
@@ -39,11 +38,9 @@ img {
}
svg {
-
* {
transition: none;
}
-
}
*:focus {
@@ -51,7 +48,6 @@ svg {
box-shadow: 0;
}
-
a {
cursor: pointer;
color: $color-primary-dark;
@@ -59,7 +55,6 @@ a {
&:hover {
color: $color-primary;
}
-
}
p {
@@ -71,16 +66,14 @@ p {
font-size: $fs16;
line-height: $base-lh;
}
-
}
li {
line-height: $base-lh-sm;
@include bp(baby-bear) {
- line-height: $base-lh
+ line-height: $base-lh;
}
-
}
ul {
@@ -114,9 +107,7 @@ h1 {
font-size: $fs44;
line-height: $title-lh;
}
-
}
-
}
h2 {
font-size: $fs24;
@@ -127,13 +118,12 @@ h2 {
font-size: $fs32;
line-height: $title-lh;
}
-
}
h3 {
font-size: $fs24;
font-weight: 300;
- padding: .5rem 0;
+ padding: 0.5rem 0;
}
h4 {
@@ -142,33 +132,65 @@ h4 {
}
@-webkit-keyframes rotation {
- from {-webkit-transform: rotate(0deg);}
- to {-webkit-transform: rotate(359deg);}
+ from {
+ -webkit-transform: rotate(0deg);
+ }
+ to {
+ -webkit-transform: rotate(359deg);
+ }
}
@-webkit-keyframes rotation-negative {
- from {-webkit-transform: rotate(0deg);}
- to {-webkit-transform: rotate(-359deg);}
+ from {
+ -webkit-transform: rotate(0deg);
+ }
+ to {
+ -webkit-transform: rotate(-359deg);
+ }
}
-
@keyframes tooltipAppear {
- 0% {opacity: 0; display: none;}
- 1% {display: block; opacity: 0; left: 3rem}
- 100% {opacity: 1; left: 2rem}
+ 0% {
+ opacity: 0;
+ display: none;
+ }
+ 1% {
+ display: block;
+ opacity: 0;
+ left: 3rem;
+ }
+ 100% {
+ opacity: 1;
+ left: 2rem;
+ }
}
-
@keyframes show {
- 0% {opacity: 0; display: none;}
- 1% {display: block; opacity: 0;}
- 100% {opacity: 1;}
+ 0% {
+ opacity: 0;
+ display: none;
+ }
+ 1% {
+ display: block;
+ opacity: 0;
+ }
+ 100% {
+ opacity: 1;
+ }
}
@keyframes hide {
- 0% {opacity: 1; display: block;}
- 99% {opacity: 0; display: block}
- 100% {display: none;}
+ 0% {
+ opacity: 1;
+ display: block;
+ }
+ 99% {
+ opacity: 0;
+ display: block;
+ }
+ 100% {
+ display: none;
+ }
}
.hide {
@@ -182,7 +204,7 @@ h4 {
}
.show {
- animation: show .4s linear ;
+ animation: show 0.4s linear;
display: block !important;
}
@@ -191,7 +213,7 @@ h4 {
text-align: center;
}
-.hidden-input {
+.hidden-input {
display: none;
}
@@ -215,17 +237,17 @@ hr {
margin: 1rem 0;
}
-input[type=number]::-webkit-inner-spin-button,
- input[type=number]::-webkit-outer-spin-button {
- -webkit-appearance: none;
- margin: 0;
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
}
-input[type=number] {
+input[type="number"] {
-moz-appearance: textfield;
}
[contenteditable] {
- -webkit-user-select: text;
- user-select: text;
+ -webkit-user-select: text;
+ user-select: text;
}
diff --git a/frontend/resources/styles/common/dependencies/animations.scss b/frontend/resources/styles/common/dependencies/animations.scss
index 6184af1ba1..00b09bf215 100644
--- a/frontend/resources/styles/common/dependencies/animations.scss
+++ b/frontend/resources/styles/common/dependencies/animations.scss
@@ -6,21 +6,21 @@
* Copyright (c) 2016 Daniel Eden
*/
-@mixin animation ($delay, $duration, $animation) {
- -webkit-animation-delay: $delay;
- -webkit-animation-duration: $duration;
- -webkit-animation-name: $animation;
- -webkit-animation-fill-mode: both;
+@mixin animation($delay, $duration, $animation) {
+ -webkit-animation-delay: $delay;
+ -webkit-animation-duration: $duration;
+ -webkit-animation-name: $animation;
+ -webkit-animation-fill-mode: both;
- -moz-animation-delay: $delay;
- -moz-animation-duration: $duration;
- -moz-animation-name: $animation;
- -moz-animation-fill-mode: both;
+ -moz-animation-delay: $delay;
+ -moz-animation-duration: $duration;
+ -moz-animation-name: $animation;
+ -moz-animation-fill-mode: both;
- animation-delay: $delay;
- animation-duration: $duration;
- animation-name: $animation;
- animation-fill-mode: both;
+ animation-delay: $delay;
+ animation-duration: $duration;
+ animation-name: $animation;
+ animation-fill-mode: both;
}
.animated {
@@ -42,69 +42,79 @@
.animated.bounceIn,
.animated.bounceOut {
- -webkit-animation-duration: .75s;
- animation-duration: .75s;
+ -webkit-animation-duration: 0.75s;
+ animation-duration: 0.75s;
}
.animated.flipOutX,
.animated.flipOutY {
- -webkit-animation-duration: .75s;
- animation-duration: .75s;
+ -webkit-animation-duration: 0.75s;
+ animation-duration: 0.75s;
}
@-webkit-keyframes bounce {
- 0%, 20%, 53%, 80%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- -webkit-transform: translate3d(0,0,0);
- transform: translate3d(0,0,0);
+ 0%,
+ 20%,
+ 53%,
+ 80%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
}
- 40%, 43% {
- -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
- animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+ 40%,
+ 43% {
+ -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+ animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
- -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
- animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+ -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+ animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
- -webkit-transform: translate3d(0,-4px,0);
- transform: translate3d(0,-4px,0);
+ -webkit-transform: translate3d(0, -4px, 0);
+ transform: translate3d(0, -4px, 0);
}
}
@keyframes bounce {
- 0%, 20%, 53%, 80%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- -webkit-transform: translate3d(0,0,0);
- transform: translate3d(0,0,0);
+ 0%,
+ 20%,
+ 53%,
+ 80%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ -webkit-transform: translate3d(0, 0, 0);
+ transform: translate3d(0, 0, 0);
}
- 40%, 43% {
- -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
- animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+ 40%,
+ 43% {
+ -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+ animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -30px, 0);
transform: translate3d(0, -30px, 0);
}
70% {
- -webkit-animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
- animation-timing-function: cubic-bezier(0.755, 0.050, 0.855, 0.060);
+ -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
+ animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
-webkit-transform: translate3d(0, -15px, 0);
transform: translate3d(0, -15px, 0);
}
90% {
- -webkit-transform: translate3d(0,-4px,0);
- transform: translate3d(0,-4px,0);
+ -webkit-transform: translate3d(0, -4px, 0);
+ transform: translate3d(0, -4px, 0);
}
}
@@ -116,21 +126,27 @@
}
@-webkit-keyframes flash {
- 0%, 50%, 100% {
+ 0%,
+ 50%,
+ 100% {
opacity: 1;
}
- 25%, 75% {
+ 25%,
+ 75% {
opacity: 0;
}
}
@keyframes flash {
- 0%, 50%, 100% {
+ 0%,
+ 50%,
+ 100% {
opacity: 1;
}
- 25%, 75% {
+ 25%,
+ 75% {
opacity: 0;
}
}
@@ -203,13 +219,13 @@
}
65% {
- -webkit-transform: scale3d(.95, 1.05, 1);
- transform: scale3d(.95, 1.05, 1);
+ -webkit-transform: scale3d(0.95, 1.05, 1);
+ transform: scale3d(0.95, 1.05, 1);
}
75% {
- -webkit-transform: scale3d(1.05, .95, 1);
- transform: scale3d(1.05, .95, 1);
+ -webkit-transform: scale3d(1.05, 0.95, 1);
+ transform: scale3d(1.05, 0.95, 1);
}
100% {
@@ -240,13 +256,13 @@
}
65% {
- -webkit-transform: scale3d(.95, 1.05, 1);
- transform: scale3d(.95, 1.05, 1);
+ -webkit-transform: scale3d(0.95, 1.05, 1);
+ transform: scale3d(0.95, 1.05, 1);
}
75% {
- -webkit-transform: scale3d(1.05, .95, 1);
- transform: scale3d(1.05, .95, 1);
+ -webkit-transform: scale3d(1.05, 0.95, 1);
+ transform: scale3d(1.05, 0.95, 1);
}
100% {
@@ -261,34 +277,50 @@
}
@-webkit-keyframes shake {
- 0%, 100% {
+ 0%,
+ 100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
- 10%, 30%, 50%, 70%, 90% {
+ 10%,
+ 30%,
+ 50%,
+ 70%,
+ 90% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
- 20%, 40%, 60%, 80% {
+ 20%,
+ 40%,
+ 60%,
+ 80% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
}
@keyframes shake {
- 0%, 100% {
+ 0%,
+ 100% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
}
- 10%, 30%, 50%, 70%, 90% {
+ 10%,
+ 30%,
+ 50%,
+ 70%,
+ 90% {
-webkit-transform: translate3d(-10px, 0, 0);
transform: translate3d(-10px, 0, 0);
}
- 20%, 40%, 60%, 80% {
+ 20%,
+ 40%,
+ 60%,
+ 80% {
-webkit-transform: translate3d(10px, 0, 0);
transform: translate3d(10px, 0, 0);
}
@@ -366,17 +398,23 @@
transform: scale3d(1, 1, 1);
}
- 10%, 20% {
- -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
- transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
+ 10%,
+ 20% {
+ -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
+ transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
- 30%, 50%, 70%, 90% {
+ 30%,
+ 50%,
+ 70%,
+ 90% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
- 40%, 60%, 80% {
+ 40%,
+ 60%,
+ 80% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
@@ -393,17 +431,23 @@
transform: scale3d(1, 1, 1);
}
- 10%, 20% {
- -webkit-transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
- transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
+ 10%,
+ 20% {
+ -webkit-transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
+ transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg);
}
- 30%, 50%, 70%, 90% {
+ 30%,
+ 50%,
+ 70%,
+ 90% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
}
- 40%, 60%, 80% {
+ 40%,
+ 60%,
+ 80% {
-webkit-transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
}
@@ -503,104 +547,106 @@
@-webkit-keyframes jello {
11.1% {
-webkit-transform: none;
- transform: none
+ transform: none;
}
22.2% {
-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
- transform: skewX(-12.5deg) skewY(-12.5deg)
+ transform: skewX(-12.5deg) skewY(-12.5deg);
}
33.3% {
-webkit-transform: skewX(6.25deg) skewY(6.25deg);
- transform: skewX(6.25deg) skewY(6.25deg)
+ transform: skewX(6.25deg) skewY(6.25deg);
}
44.4% {
-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
- transform: skewX(-3.125deg) skewY(-3.125deg)
+ transform: skewX(-3.125deg) skewY(-3.125deg);
}
55.5% {
-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
- transform: skewX(1.5625deg) skewY(1.5625deg)
+ transform: skewX(1.5625deg) skewY(1.5625deg);
}
66.6% {
-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
- transform: skewX(-0.78125deg) skewY(-0.78125deg)
+ transform: skewX(-0.78125deg) skewY(-0.78125deg);
}
77.7% {
-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
- transform: skewX(0.390625deg) skewY(0.390625deg)
+ transform: skewX(0.390625deg) skewY(0.390625deg);
}
88.8% {
-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
- transform: skewX(-0.1953125deg) skewY(-0.1953125deg)
+ transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
}
100% {
-webkit-transform: none;
- transform: none
+ transform: none;
}
}
@keyframes jello {
11.1% {
-webkit-transform: none;
- transform: none
+ transform: none;
}
22.2% {
-
-webkit-transform: skewX(-12.5deg) skewY(-12.5deg);
- transform: skewX(-12.5deg) skewY(-12.5deg)
+ transform: skewX(-12.5deg) skewY(-12.5deg);
}
33.3% {
-webkit-transform: skewX(6.25deg) skewY(6.25deg);
- transform: skewX(6.25deg) skewY(6.25deg)
+ transform: skewX(6.25deg) skewY(6.25deg);
}
44.4% {
-webkit-transform: skewX(-3.125deg) skewY(-3.125deg);
- transform: skewX(-3.125deg) skewY(-3.125deg)
+ transform: skewX(-3.125deg) skewY(-3.125deg);
}
55.5% {
-webkit-transform: skewX(1.5625deg) skewY(1.5625deg);
- transform: skewX(1.5625deg) skewY(1.5625deg)
+ transform: skewX(1.5625deg) skewY(1.5625deg);
}
66.6% {
-webkit-transform: skewX(-0.78125deg) skewY(-0.78125deg);
- transform: skewX(-0.78125deg) skewY(-0.78125deg)
+ transform: skewX(-0.78125deg) skewY(-0.78125deg);
}
77.7% {
-webkit-transform: skewX(0.390625deg) skewY(0.390625deg);
- transform: skewX(0.390625deg) skewY(0.390625deg)
+ transform: skewX(0.390625deg) skewY(0.390625deg);
}
88.8% {
-webkit-transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
- transform: skewX(-0.1953125deg) skewY(-0.1953125deg)
+ transform: skewX(-0.1953125deg) skewY(-0.1953125deg);
}
100% {
-webkit-transform: none;
- transform: none
+ transform: none;
}
}
+.jello {
+ -webkit-animation-name: jello;
+ animation-name: jello;
+ -webkit-transform-origin: center;
-
-.jello{
- -webkit-animation-name:jello;
- animation-name:jello;
- -webkit-transform-origin: center;
-
- transform-origin: center
+ transform-origin: center;
}
@-webkit-keyframes bounceIn {
- 0%, 20%, 40%, 60%, 80%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 20%,
+ 40%,
+ 60%,
+ 80%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
20% {
@@ -609,8 +655,8 @@
}
40% {
- -webkit-transform: scale3d(.9, .9, .9);
- transform: scale3d(.9, .9, .9);
+ -webkit-transform: scale3d(0.9, 0.9, 0.9);
+ transform: scale3d(0.9, 0.9, 0.9);
}
60% {
@@ -620,8 +666,8 @@
}
80% {
- -webkit-transform: scale3d(.97, .97, .97);
- transform: scale3d(.97, .97, .97);
+ -webkit-transform: scale3d(0.97, 0.97, 0.97);
+ transform: scale3d(0.97, 0.97, 0.97);
}
100% {
@@ -632,15 +678,20 @@
}
@keyframes bounceIn {
- 0%, 20%, 40%, 60%, 80%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 20%,
+ 40%,
+ 60%,
+ 80%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
20% {
@@ -649,8 +700,8 @@
}
40% {
- -webkit-transform: scale3d(.9, .9, .9);
- transform: scale3d(.9, .9, .9);
+ -webkit-transform: scale3d(0.9, 0.9, 0.9);
+ transform: scale3d(0.9, 0.9, 0.9);
}
60% {
@@ -660,8 +711,8 @@
}
80% {
- -webkit-transform: scale3d(.97, .97, .97);
- transform: scale3d(.97, .97, .97);
+ -webkit-transform: scale3d(0.97, 0.97, 0.97);
+ transform: scale3d(0.97, 0.97, 0.97);
}
100% {
@@ -677,9 +728,13 @@
}
@-webkit-keyframes bounceInDown {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -711,9 +766,13 @@
}
@keyframes bounceInDown {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -750,9 +809,13 @@
}
@-webkit-keyframes bounceInLeft {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -784,9 +847,13 @@
}
@keyframes bounceInLeft {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -823,9 +890,13 @@
}
@-webkit-keyframes bounceInRight {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -857,9 +928,13 @@
}
@keyframes bounceInRight {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -896,9 +971,13 @@
}
@-webkit-keyframes bounceInUp {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -930,9 +1009,13 @@
}
@keyframes bounceInUp {
- 0%, 60%, 75%, 90%, 100% {
- -webkit-animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
- animation-timing-function: cubic-bezier(0.215, 0.610, 0.355, 1.000);
+ 0%,
+ 60%,
+ 75%,
+ 90%,
+ 100% {
+ -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+ animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
@@ -970,11 +1053,12 @@
@-webkit-keyframes bounceOut {
20% {
- -webkit-transform: scale3d(.9, .9, .9);
- transform: scale3d(.9, .9, .9);
+ -webkit-transform: scale3d(0.9, 0.9, 0.9);
+ transform: scale3d(0.9, 0.9, 0.9);
}
- 50%, 55% {
+ 50%,
+ 55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
@@ -982,18 +1066,19 @@
100% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
}
@keyframes bounceOut {
20% {
- -webkit-transform: scale3d(.9, .9, .9);
- transform: scale3d(.9, .9, .9);
+ -webkit-transform: scale3d(0.9, 0.9, 0.9);
+ transform: scale3d(0.9, 0.9, 0.9);
}
- 50%, 55% {
+ 50%,
+ 55% {
opacity: 1;
-webkit-transform: scale3d(1.1, 1.1, 1.1);
transform: scale3d(1.1, 1.1, 1.1);
@@ -1001,8 +1086,8 @@
100% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
}
@@ -1017,7 +1102,8 @@
transform: translate3d(0, 10px, 0);
}
- 40%, 45% {
+ 40%,
+ 45% {
opacity: 1;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
@@ -1036,7 +1122,8 @@
transform: translate3d(0, 10px, 0);
}
- 40%, 45% {
+ 40%,
+ 45% {
opacity: 1;
-webkit-transform: translate3d(0, -20px, 0);
transform: translate3d(0, -20px, 0);
@@ -1126,7 +1213,8 @@
transform: translate3d(0, -10px, 0);
}
- 40%, 45% {
+ 40%,
+ 45% {
opacity: 1;
-webkit-transform: translate3d(0, 20px, 0);
transform: translate3d(0, 20px, 0);
@@ -1145,7 +1233,8 @@
transform: translate3d(0, -10px, 0);
}
- 40%, 45% {
+ 40%,
+ 45% {
opacity: 1;
-webkit-transform: translate3d(0, 20px, 0);
transform: translate3d(0, 20px, 0);
@@ -1718,22 +1807,26 @@
}
40% {
- -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
- transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+ -webkit-transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -190deg);
+ transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -190deg);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
50% {
- -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
- transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+ -webkit-transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -170deg);
+ transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -170deg);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
80% {
- -webkit-transform: perspective(400px) scale3d(.95, .95, .95);
- transform: perspective(400px) scale3d(.95, .95, .95);
+ -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95);
+ transform: perspective(400px) scale3d(0.95, 0.95, 0.95);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
@@ -1755,22 +1848,26 @@
}
40% {
- -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
- transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -190deg);
+ -webkit-transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -190deg);
+ transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -190deg);
-webkit-animation-timing-function: ease-out;
animation-timing-function: ease-out;
}
50% {
- -webkit-transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
- transform: perspective(400px) translate3d(0, 0, 150px) rotate3d(0, 1, 0, -170deg);
+ -webkit-transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -170deg);
+ transform: perspective(400px) translate3d(0, 0, 150px)
+ rotate3d(0, 1, 0, -170deg);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
80% {
- -webkit-transform: perspective(400px) scale3d(.95, .95, .95);
- transform: perspective(400px) scale3d(.95, .95, .95);
+ -webkit-transform: perspective(400px) scale3d(0.95, 0.95, 0.95);
+ transform: perspective(400px) scale3d(0.95, 0.95, 0.95);
-webkit-animation-timing-function: ease-in;
animation-timing-function: ease-in;
}
@@ -2514,7 +2611,8 @@
animation-timing-function: ease-in-out;
}
- 20%, 60% {
+ 20%,
+ 60% {
-webkit-transform: rotate3d(0, 0, 1, 80deg);
transform: rotate3d(0, 0, 1, 80deg);
-webkit-transform-origin: top left;
@@ -2523,7 +2621,8 @@
animation-timing-function: ease-in-out;
}
- 40%, 80% {
+ 40%,
+ 80% {
-webkit-transform: rotate3d(0, 0, 1, 60deg);
transform: rotate3d(0, 0, 1, 60deg);
-webkit-transform-origin: top left;
@@ -2548,7 +2647,8 @@
animation-timing-function: ease-in-out;
}
- 20%, 60% {
+ 20%,
+ 60% {
-webkit-transform: rotate3d(0, 0, 1, 80deg);
transform: rotate3d(0, 0, 1, 80deg);
-webkit-transform-origin: top left;
@@ -2557,7 +2657,8 @@
animation-timing-function: ease-in-out;
}
- 40%, 80% {
+ 40%,
+ 80% {
-webkit-transform: rotate3d(0, 0, 1, 60deg);
transform: rotate3d(0, 0, 1, 60deg);
-webkit-transform-origin: top left;
@@ -2648,8 +2749,8 @@
@-webkit-keyframes zoomIn {
0% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
50% {
@@ -2660,8 +2761,8 @@
@keyframes zoomIn {
0% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
50% {
@@ -2677,36 +2778,36 @@
@-webkit-keyframes zoomInDown {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInDown {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, -1000px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -1000px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -2718,36 +2819,36 @@
@-webkit-keyframes zoomInLeft {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
- transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInLeft {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
- transform: scale3d(.1, .1, .1) translate3d(-1000px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(-1000px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(10px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(10px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -2759,36 +2860,36 @@
@-webkit-keyframes zoomInRight {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
- transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInRight {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
- transform: scale3d(.1, .1, .1) translate3d(1000px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(1000px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(-10px, 0, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(-10px, 0, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -2800,36 +2901,36 @@
@-webkit-keyframes zoomInUp {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomInUp {
0% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, 1000px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 1000px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
60% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -2845,8 +2946,8 @@
50% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
100% {
@@ -2861,8 +2962,8 @@
50% {
opacity: 0;
- -webkit-transform: scale3d(.3, .3, .3);
- transform: scale3d(.3, .3, .3);
+ -webkit-transform: scale3d(0.3, 0.3, 0.3);
+ transform: scale3d(0.3, 0.3, 0.3);
}
100% {
@@ -2878,40 +2979,40 @@
@-webkit-keyframes zoomOutDown {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
100% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomOutDown {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, -60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, -60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
100% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, 2000px, 0);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, 2000px, 0);
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -2923,14 +3024,14 @@
@-webkit-keyframes zoomOutLeft {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
}
100% {
opacity: 0;
- -webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
- transform: scale(.1) translate3d(-2000px, 0, 0);
+ -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0);
+ transform: scale(0.1) translate3d(-2000px, 0, 0);
-webkit-transform-origin: left center;
transform-origin: left center;
}
@@ -2939,14 +3040,14 @@
@keyframes zoomOutLeft {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(42px, 0, 0);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(42px, 0, 0);
}
100% {
opacity: 0;
- -webkit-transform: scale(.1) translate3d(-2000px, 0, 0);
- transform: scale(.1) translate3d(-2000px, 0, 0);
+ -webkit-transform: scale(0.1) translate3d(-2000px, 0, 0);
+ transform: scale(0.1) translate3d(-2000px, 0, 0);
-webkit-transform-origin: left center;
transform-origin: left center;
}
@@ -2960,14 +3061,14 @@
@-webkit-keyframes zoomOutRight {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
}
100% {
opacity: 0;
- -webkit-transform: scale(.1) translate3d(2000px, 0, 0);
- transform: scale(.1) translate3d(2000px, 0, 0);
+ -webkit-transform: scale(0.1) translate3d(2000px, 0, 0);
+ transform: scale(0.1) translate3d(2000px, 0, 0);
-webkit-transform-origin: right center;
transform-origin: right center;
}
@@ -2976,14 +3077,14 @@
@keyframes zoomOutRight {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
- transform: scale3d(.475, .475, .475) translate3d(-42px, 0, 0);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(-42px, 0, 0);
}
100% {
opacity: 0;
- -webkit-transform: scale(.1) translate3d(2000px, 0, 0);
- transform: scale(.1) translate3d(2000px, 0, 0);
+ -webkit-transform: scale(0.1) translate3d(2000px, 0, 0);
+ transform: scale(0.1) translate3d(2000px, 0, 0);
-webkit-transform-origin: right center;
transform-origin: right center;
}
@@ -2997,40 +3098,40 @@
@-webkit-keyframes zoomOutUp {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
100% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@keyframes zoomOutUp {
40% {
opacity: 1;
- -webkit-transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- transform: scale3d(.475, .475, .475) translate3d(0, 60px, 0);
- -webkit-animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
- animation-timing-function: cubic-bezier(0.550, 0.055, 0.675, 0.190);
+ -webkit-transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ transform: scale3d(0.475, 0.475, 0.475) translate3d(0, 60px, 0);
+ -webkit-animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+ animation-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
}
100% {
opacity: 0;
- -webkit-transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
- transform: scale3d(.1, .1, .1) translate3d(0, -2000px, 0);
+ -webkit-transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
+ transform: scale3d(0.1, 0.1, 0.1) translate3d(0, -2000px, 0);
-webkit-transform-origin: center bottom;
transform-origin: center bottom;
- -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
- animation-timing-function: cubic-bezier(0.175, 0.885, 0.320, 1);
+ -webkit-animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
+ animation-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1);
}
}
@@ -3326,16 +3427,16 @@
@keyframes loaderColor {
0% {
- fill: #513B56;
+ fill: #513b56;
}
33% {
- fill: #348AA7;
+ fill: #348aa7;
}
66% {
- fill: #5DD39E;
+ fill: #5dd39e;
}
100% {
- fill: #513B56;
+ fill: #513b56;
}
}
diff --git a/frontend/resources/styles/common/dependencies/colors.scss b/frontend/resources/styles/common/dependencies/colors.scss
index 5a9c19c4d7..57b538061d 100644
--- a/frontend/resources/styles/common/dependencies/colors.scss
+++ b/frontend/resources/styles/common/dependencies/colors.scss
@@ -8,30 +8,35 @@
// Colors
$color-white: #ffffff;
$color-black: #000000;
-$color-canvas: #E8E9EA;
-$color-dashboard: #F6F6F6;
+$color-canvas: #e8e9ea;
+$color-dashboard: #f6f6f6;
// Main color
-$color-primary: #31EFB8;
+$color-primary: #31efb8;
// Secondary colors
-$color-success: #58C35C;
-$color-complete : #a599c6;
-$color-warning: #FC8802;
-$color-danger: #E65244;
+$color-success: #58c35c;
+$color-complete: #a599c6;
+$color-warning: #fc8802;
+$color-danger: #e65244;
$color-info: #59b9e2;
$color-ocean: #4285f4;
-$color-component: #76B0B8;
-$color-component-highlight: #00E0FF;
+$color-component: #76b0b8;
+$color-component-highlight: #1890ff;
$color-pink: #feecfc;
// Gray scale
-$color-gray-10: #E3E3E3;
+$color-gray-10: #e3e3e3;
$color-gray-20: #b1b2b5;
-$color-gray-30: #7B7D85;
-$color-gray-40: #64666A;
-$color-gray-50: #303236;
-$color-gray-60: #1F1F1F;
+$color-gray-30: #7b7d85;
+$color-gray-40: #64666a;
+$color-gray-50: #303236;
+$color-gray-60: #1f1f1f;
+
+// UI colors
+$color-select: #1fdea7;
+$color-distance: #db00ff;
+$color-snap: #d383da;
// Mixing Color variable for creating both light and dark colors
$mix-percentage-dark: 81%;
@@ -41,43 +46,217 @@ $mix-percentage-lighter: 20%;
$mix-percentage-lightest: 10%;
// Lighter colors
-$color-success-light: mix($color-success, $color-white, $mix-percentage-light);
-$color-success-lighter: mix($color-success, $color-white, $mix-percentage-lighter);
+$color-success-light: mix(
+ $color-success,
+ $color-white,
+ $mix-percentage-light
+); //#79cf7d
+$color-success-lighter: mix(
+ $color-success,
+ $color-white,
+ $mix-percentage-lighter
+); //#def3de
-$color-complete-light: mix($color-complete, $color-white, $mix-percentage-light);
-$color-complete-lighter: mix($color-complete, $color-white, $mix-percentage-lighter);
+$color-complete-light: mix(
+ $color-complete,
+ $color-white,
+ $mix-percentage-light
+); //#b7add1
+$color-complete-lighter: mix(
+ $color-complete,
+ $color-white,
+ $mix-percentage-lighter
+); //#edebf4
-$color-primary-light: mix($color-primary, $color-white, $mix-percentage-light);
-$color-primary-lighter: mix($color-primary, $color-white, $mix-percentage-lighter);
+$color-primary-light: mix(
+ $color-primary,
+ $color-white,
+ $mix-percentage-light
+); //#5af2c6
+$color-primary-lighter: mix(
+ $color-primary,
+ $color-white,
+ $mix-percentage-lighter
+); //#d6fcf1
-$color-warning-light: mix($color-warning, $color-white, $mix-percentage-light);
-$color-warning-lighter: mix($color-warning, $color-white, $mix-percentage-lighter);
+$color-warning-light: mix(
+ $color-warning,
+ $color-white,
+ $mix-percentage-light
+); //#fda035
+$color-warning-lighter: mix(
+ $color-warning,
+ $color-white,
+ $mix-percentage-lighter
+); //#fee7cc;
-$color-danger-light: mix($color-danger, $color-white, $mix-percentage-light);
-$color-danger-lighter: mix($color-danger, $color-white, $mix-percentage-lighter);
+$color-danger-light: mix(
+ $color-danger,
+ $color-white,
+ $mix-percentage-light
+); //#eb7569
+$color-danger-lighter: mix(
+ $color-danger,
+ $color-white,
+ $mix-percentage-lighter
+); //#fadcda
-$color-info-light: mix($color-info, $color-white, $mix-percentage-light);
-$color-info-lighter: mix($color-info, $color-white, $mix-percentage-lighter);
+$color-info-light: mix(
+ $color-info,
+ $color-white,
+ $mix-percentage-light
+); //#7ac7e8
+$color-info-lighter: mix(
+ $color-info,
+ $color-white,
+ $mix-percentage-lighter
+); //#def1f9;
// Darker colors
-$color-success-dark: mix($color-success, $color-black, $mix-percentage-dark);
-$color-success-darker: mix($color-success, $color-black, $mix-percentage-darker);
+$color-success-dark: mix(
+ $color-success,
+ $color-black,
+ $mix-percentage-dark
+); //#479e4b;
+$color-success-darker: mix(
+ $color-success,
+ $color-black,
+ $mix-percentage-darker
+); // #357537;
-$color-complete-dark: mix($color-complete, $color-black, $mix-percentage-dark);
-$color-complete-darker: mix($color-complete, $color-black, $mix-percentage-darker);
+$color-complete-dark: mix(
+ $color-complete,
+ $color-black,
+ $mix-percentage-dark
+); //#867ca0
+$color-complete-darker: mix(
+ $color-complete,
+ $color-black,
+ $mix-percentage-darker
+); //#635c77
-$color-primary-dark: mix($color-primary, $color-black, $mix-percentage-dark);
-$color-primary-darker: mix($color-primary, $color-black, $mix-percentage-darker);
+$color-primary-dark: mix(
+ $color-primary,
+ $color-black,
+ $mix-percentage-dark
+); //#28c295;
+$color-primary-darker: mix(
+ $color-primary,
+ $color-black,
+ $mix-percentage-darker
+); // #1d8f6e
-$color-warning-dark: mix($color-warning, $color-black, $mix-percentage-dark);
-$color-warning-darker: mix($color-warning, $color-black, $mix-percentage-darker);
+$color-warning-dark: mix(
+ $color-warning,
+ $color-black,
+ $mix-percentage-dark
+); // #cc6e02;
+$color-warning-darker: mix(
+ $color-warning,
+ $color-black,
+ $mix-percentage-darker
+); //#975201
-$color-danger-dark: mix($color-danger, $color-black, $mix-percentage-dark);
-$color-danger-darker: mix($color-danger, $color-black, $mix-percentage-darker);
+$color-danger-dark: mix(
+ $color-danger,
+ $color-black,
+ $mix-percentage-dark
+); //#ba4237
+$color-danger-darker: mix(
+ $color-danger,
+ $color-black,
+ $mix-percentage-darker
+); // #8a3129;
-$color-info-dark: mix($color-info, $color-black, $mix-percentage-dark);
-$color-info-darker: mix($color-info, $color-black, $mix-percentage-darker);
+$color-info-dark: mix(
+ $color-info,
+ $color-black,
+ $mix-percentage-dark
+); // #4896b7
+$color-info-darker: mix(
+ $color-info,
+ $color-black,
+ $mix-percentage-darker
+); // #356f88;
// bg transparent
-$color-dark-bg: rgba(0,0,0,.4);
-$color-light-bg: rgba(255,255,255,.6);
+$color-dark-bg: rgba(0, 0, 0, 0.4);
+$color-light-bg: rgba(255, 255, 255, 0.6);
+
+// Transform scss variables into css variables to use them onto cljs files
+:root {
+ // Colors
+ --color-white: #{$color-white};
+ --color-black: #{$color-black};
+ --color-canvas: #{$color-canvas};
+ --color-dashboard: #{$color-dashboard};
+
+ // Main color;
+ --color-primary: #{$color-primary};
+
+ // Secondary colors;
+ --color-success: #{$color-success};
+ --color-complete: #{$color-complete};
+ --color-warning: #{$color-warning};
+ --color-danger: #{$color-danger};
+ --color-info: #{$color-info};
+ --color-ocean: #{$color-ocean};
+ --color-component: #{$color-component};
+ --color-component-highlight: #{$color-component-highlight};
+ --color-pink: #{$color-pink};
+
+ // Gray scale;
+ --color-gray-10: #{$color-gray-10};
+ --color-gray-20: #{$color-gray-20};
+ --color-gray-30: #{$color-gray-30};
+ --color-gray-40: #{$color-gray-40};
+ --color-gray-50: #{$color-gray-50};
+ --color-gray-60: #{$color-gray-60};
+
+ // UI colors
+ --color-distance: #{$color-distance};
+ --color-select: #{$color-select};
+ --color-snap: #{$color-snap};
+
+ // Lighter colors
+ --color-success-light: #{$color-success-light};
+ --color-success-lighter: #{$color-success-lighter};
+
+ --color-complete-light: #{$color-complete-light};
+ --color-complete-lighter: #{$color-complete-lighter};
+
+ --color-primary-light: #{$color-primary-light};
+ --color-primary-lighter: #{$color-primary-lighter};
+
+ --color-warning-light: #{$color-warning-light};
+ --color-warning-lighter: #{$color-warning-lighter};
+
+ --color-danger-light: #{$color-danger-light};
+ --color-danger-lighter: #{$color-danger-lighter};
+
+ --color-info-light: #{$color-info-light};
+ --color-info-lighter: #{$color-info-lighter};
+
+ // Darker colors
+ --color-success-dark: #{$color-success-dark};
+ --color-success-darker: #{$color-success-darker};
+
+ --color-complete-dark: #{$color-complete-dark};
+ --color-complete-darker: #{$color-complete-darker};
+
+ --color-primary-dark: #{$color-primary-dark};
+ --color-primary-darker: #{$color-primary-darker};
+
+ --color-warning-dark: #{$color-warning-dark};
+ --color-warning-darker: #{$color-warning-darker};
+
+ --color-danger-dark: #{$color-danger-dark};
+ --color-danger-darker: #{$color-danger-darker};
+
+ --color-info-dark: #{$color-info-dark};
+ --color-info-darker: #{$color-info-darker};
+
+ // bg transparent
+ --color-dark-bg: #{$color-dark-bg};
+ --color-light-bg: #{$color-light-bg};
+}
diff --git a/frontend/resources/styles/common/dependencies/fonts.scss b/frontend/resources/styles/common/dependencies/fonts.scss
index 75eb65c1f1..316c531fe4 100644
--- a/frontend/resources/styles/common/dependencies/fonts.scss
+++ b/frontend/resources/styles/common/dependencies/fonts.scss
@@ -13,7 +13,7 @@ $fs12: 0.75rem;
$fs13: 0.8125rem;
$fs14: 0.875rem;
$fs15: 0.9375rem;
-$fs16: 1rem ;
+$fs16: 1rem;
$fs18: 1.125rem;
$fs19: 1.1875rem;
$fs20: 1.25rem;
@@ -40,33 +40,43 @@ $title-lh: 1.25;
$title-lh-sm: 1.15;
// Work Sans
-@include font-face("worksans","WorkSans-Thin", "100");
-@include font-face("worksans","WorkSans-ThinItalic", "100", italic);
-@include font-face("worksans","WorkSans-ExtraLight", "200");
-@include font-face("worksans","WorkSans-ExtraLightitalic", "200", italic);
-@include font-face("worksans","WorkSans-Light", "300");
-@include font-face("worksans","WorkSans-LightItalic", "300", italic);
-@include font-face("worksans","WorkSans-Regular", normal);
-@include font-face("worksans","WorkSans-Italic", normal, italic);
-@include font-face("worksans","WorkSans-Medium", "500");
-@include font-face("worksans","WorkSans-MediumItalic", "500", italic);
-@include font-face("worksans","WorkSans-SemiBold", "600");
-@include font-face("worksans","WorkSans-SemiBoldItalic", "600", italic);
-@include font-face("worksans","WorkSans-Bold", bold);
-@include font-face("worksans","WorkSans-BoldItalic", bold, italic);
-@include font-face("worksans","WorkSans-Black", "900");
-@include font-face("worksans","WorkSans-BlackItalic","900", italic);
+@include font-face("worksans", "WorkSans-Thin", "100");
+@include font-face("worksans", "WorkSans-ThinItalic", "100", italic);
+@include font-face("worksans", "WorkSans-ExtraLight", "200");
+@include font-face("worksans", "WorkSans-ExtraLightitalic", "200", italic);
+@include font-face("worksans", "WorkSans-Light", "300");
+@include font-face("worksans", "WorkSans-LightItalic", "300", italic);
+@include font-face("worksans", "WorkSans-Regular", normal);
+@include font-face("worksans", "WorkSans-Italic", normal, italic);
+@include font-face("worksans", "WorkSans-Medium", "500");
+@include font-face("worksans", "WorkSans-MediumItalic", "500", italic);
+@include font-face("worksans", "WorkSans-SemiBold", "600");
+@include font-face("worksans", "WorkSans-SemiBoldItalic", "600", italic);
+@include font-face("worksans", "WorkSans-Bold", bold);
+@include font-face("worksans", "WorkSans-BoldItalic", bold, italic);
+@include font-face("worksans", "WorkSans-Black", "900");
+@include font-face("worksans", "WorkSans-BlackItalic", "900", italic);
// Source Sans Pro
-@include font-face("sourcesanspro","sourcesanspro-extralight", "200");
-@include font-face("sourcesanspro","sourcesanspro-extralightitalic", "200", italic);
-@include font-face("sourcesanspro","sourcesanspro-light", "300");
-@include font-face("sourcesanspro","sourcesanspro-lightitalic", "300", italic);
-@include font-face("sourcesanspro","sourcesanspro-regular", normal);
-@include font-face("sourcesanspro","sourcesanspro-italic", normal, italic);
-@include font-face("sourcesanspro","sourcesanspro-semibold", "600");
-@include font-face("sourcesanspro","sourcesanspro-semibolditalic", "600", italic);
-@include font-face("sourcesanspro","sourcesanspro-bold", bold);
-@include font-face("sourcesanspro","sourcesanspro-bolditalic", bold, italic);
-@include font-face("sourcesanspro","sourcesanspro-black", "900");
-@include font-face("sourcesanspro","sourcesanspro-blackitalic", "900", italic);
+@include font-face("sourcesanspro", "sourcesanspro-extralight", "200");
+@include font-face(
+ "sourcesanspro",
+ "sourcesanspro-extralightitalic",
+ "200",
+ italic
+);
+@include font-face("sourcesanspro", "sourcesanspro-light", "300");
+@include font-face("sourcesanspro", "sourcesanspro-lightitalic", "300", italic);
+@include font-face("sourcesanspro", "sourcesanspro-regular", normal);
+@include font-face("sourcesanspro", "sourcesanspro-italic", normal, italic);
+@include font-face("sourcesanspro", "sourcesanspro-semibold", "600");
+@include font-face(
+ "sourcesanspro",
+ "sourcesanspro-semibolditalic",
+ "600",
+ italic
+);
+@include font-face("sourcesanspro", "sourcesanspro-bold", bold);
+@include font-face("sourcesanspro", "sourcesanspro-bolditalic", bold, italic);
+@include font-face("sourcesanspro", "sourcesanspro-black", "900");
+@include font-face("sourcesanspro", "sourcesanspro-blackitalic", "900", italic);
diff --git a/frontend/resources/styles/common/dependencies/helpers.scss b/frontend/resources/styles/common/dependencies/helpers.scss
index 3562e93707..0027118f84 100644
--- a/frontend/resources/styles/common/dependencies/helpers.scss
+++ b/frontend/resources/styles/common/dependencies/helpers.scss
@@ -21,11 +21,11 @@ $br-huge: 12px;
// Alignments
.text-left {
- text-align: left;
+ text-align: left;
}
.text-right {
- text-align: right;
+ text-align: right;
}
.row-flex {
@@ -43,12 +43,12 @@ $br-huge: 12px;
}
.row-grid-2 {
- display: grid;
- grid-template-columns: repeat(2, 1fr);
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
}
.flex-grow {
- flex-grow: 1;
+ flex-grow: 1;
}
.column-half {
@@ -57,13 +57,13 @@ $br-huge: 12px;
// Display
.hidden {
- display: none;
+ display: none;
}
.hide {
- opacity: 0;
+ opacity: 0;
}
.display {
- opacity: 1 !important;
+ opacity: 1 !important;
}
diff --git a/frontend/resources/styles/common/dependencies/mixin.scss b/frontend/resources/styles/common/dependencies/mixin.scss
index 0b553dcbc2..c4e19f9164 100644
--- a/frontend/resources/styles/common/dependencies/mixin.scss
+++ b/frontend/resources/styles/common/dependencies/mixin.scss
@@ -9,31 +9,34 @@
/// @group Mixins
/// @parameter $point - This parameter decide which one of Breaking Point you want to use: "bp-desktop" (Desktop), "bp-tablet" (Tablet) and "bp-mobile" (Mobile).
@mixin bp($point) {
-
$bp-mobile: "(min-width: 720px)";
$bp-tablet: "(min-width: 1020px)";
$bp-desktop: "(min-width: 1366px)";
@if $point == mobile {
- @media #{$bp-desktop} { @content; }
+ @media #{$bp-desktop} {
+ @content;
+ }
+ } @else if $point == tablet {
+ @media #{$bp-tablet} {
+ @content;
+ }
+ } @else if $point == desktop {
+ @media #{$bp-mobile} {
+ @content;
+ }
}
- @else if $point == tablet {
- @media #{$bp-tablet} { @content; }
- }
- @else if $point == desktop {
- @media #{$bp-mobile} { @content; }
- }
-
}
-
// Advanced positioning
// ----------------
-@mixin position($type,
- $top: $position-default,
- $right: $position-default,
- $bottom: $position-default,
- $left: $position-default) {
+@mixin position(
+ $type,
+ $top: $position-default,
+ $right: $position-default,
+ $bottom: $position-default,
+ $left: $position-default
+) {
position: $type;
$allowed_types: absolute relative fixed;
@if not index($allowed_types, $type) {
@@ -43,45 +46,59 @@
#{nth($data, 1)}: nth($data, 2);
}
}
-@mixin absolute($top: $position-default, $right: $position-default, $bottom: $position-default, $left: $position-default) {
+@mixin absolute(
+ $top: $position-default,
+ $right: $position-default,
+ $bottom: $position-default,
+ $left: $position-default
+) {
@include position(absolute, $top, $right, $bottom, $left);
}
-@mixin relative($top: $position-default, $right: $position-default, $bottom: $position-default, $left: $position-default) {
+@mixin relative(
+ $top: $position-default,
+ $right: $position-default,
+ $bottom: $position-default,
+ $left: $position-default
+) {
@include position(relative, $top, $right, $bottom, $left);
}
-@mixin fixed($top: $position-default, $right: $position-default, $bottom: $position-default, $left: $position-default) {
+@mixin fixed(
+ $top: $position-default,
+ $right: $position-default,
+ $bottom: $position-default,
+ $left: $position-default
+) {
@include position(fixed, $top, $right, $bottom, $left);
}
-
/// Center an element vertically and horizontally with an absolute position.
/// @group Mixins
@mixin centerer {
- @include absolute(50%,null,null,50%);
- transform: translate(-50%,-50%);
+ @include absolute(50%, null, null, 50%);
+ transform: translate(-50%, -50%);
}
-
-
-/// This mixing allow you to add placeholder colors in all availables browsers
+/// This mixing allow you to add placeholder colors in all available browsers
/// @group Mixins
@mixin placeholder {
&::-webkit-input-placeholder {
- @content;
+ @content;
}
- &:-moz-placeholder { /* Firefox 18- */
- @content;
+ &:-moz-placeholder {
+ /* Firefox 18- */
+ @content;
}
- &::-moz-placeholder { /* Firefox 19+ */
- @content;
+ &::-moz-placeholder {
+ /* Firefox 19+ */
+ @content;
}
&:-ms-input-placeholder {
- @content;
+ @content;
}
}
@@ -89,9 +106,9 @@
/// @group Mixins
@mixin hide-text {
- font: 0/0 a;
- color: transparent;
- text-shadow: none;
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
}
/// Shortcut mixin to add new font-face compatible with all browser. To work you need to add the follow formats of font:".eot", ".woff", ".ttf" and "svg".
@@ -101,15 +118,23 @@
/// @parameter $weight [normal] - With this variable you can add how much weight you want to add to this specific font. EX: Bold
/// @parameter $style [normal] - With this variable you can add a font style to this specific font. EX: Italic
-@mixin font-face($style-name, $file, $weight: unquote("normal"), $style: unquote("normal") ) {
- $filepath: "/fonts/" + $file;
- @font-face {
- font-family: "#{$style-name}";
- src: url($filepath + ".eot");
- src: url($filepath + ".eot?#iefix") format('embedded-opentype'), url($filepath + ".woff") format('woff'), url($filepath + ".ttf") format('truetype'), url($filepath + ".svg#" + $style-name + "") format('svg');
- font-weight: unquote($weight);
- font-style: unquote($style);
- }
+@mixin font-face(
+ $style-name,
+ $file,
+ $weight: unquote("normal"),
+ $style: unquote("normal")
+) {
+ $filepath: "/fonts/" + $file;
+ @font-face {
+ font-family: "#{$style-name}";
+ src: url($filepath + ".eot");
+ src: url($filepath + ".eot?#iefix") format("embedded-opentype"),
+ url($filepath + ".woff") format("woff"),
+ url($filepath + ".ttf") format("truetype"),
+ url($filepath + ".svg#" + $style-name + "") format("svg");
+ font-weight: unquote($weight);
+ font-style: unquote($style);
+ }
}
@mixin tooltipShow {
@@ -117,14 +142,14 @@
.icon-tooltip {
display: block;
left: 2rem;
- animation: tooltipAppear .2s linear ;
+ animation: tooltipAppear 0.2s linear;
}
}
&.active {
.icon-tooltip {
display: block;
left: 2rem;
- animation: tooltipAppear .2s linear ;
+ animation: tooltipAppear 0.2s linear;
}
}
}
@@ -148,5 +173,4 @@
position: absolute;
right: 2px;
}
-
}
diff --git a/frontend/resources/styles/common/dependencies/reset.scss b/frontend/resources/styles/common/dependencies/reset.scss
index cb6bb8429a..1fc96a1502 100644
--- a/frontend/resources/styles/common/dependencies/reset.scss
+++ b/frontend/resources/styles/common/dependencies/reset.scss
@@ -11,7 +11,6 @@ img {
display: block;
}
-
// #Reset & Basics (Inspired by E. Meyers)
//==================================================
a,
@@ -95,14 +94,14 @@ u,
ul,
var,
video {
- border: 0;
- font: inherit;
- font-size: 100%;
- line-height: $base-lh;
- margin: 0;
- padding: 0;
- text-decoration: none;
- vertical-align: baseline;
+ border: 0;
+ font: inherit;
+ font-size: 100%;
+ line-height: $base-lh;
+ margin: 0;
+ padding: 0;
+ text-decoration: none;
+ vertical-align: baseline;
}
article,
@@ -116,34 +115,34 @@ hgroup,
menu,
nav,
section {
- display: block;
+ display: block;
}
body {
- line-height: 1;
+ line-height: 1;
}
ol,
ul {
- list-style: none;
+ list-style: none;
}
blockquote,
q {
- quotes: none;
+ quotes: none;
}
blockquote:before,
blockquote:after,
q:before,
q:after {
- content: '';
+ content: "";
}
table {
- border-collapse: collapse;
- border-spacing: 0;
+ border-collapse: collapse;
+ border-spacing: 0;
}
select {
- -webkit-appearance:none
+ -webkit-appearance: none;
}
diff --git a/frontend/resources/styles/common/dependencies/z-index.scss b/frontend/resources/styles/common/dependencies/z-index.scss
index 68bf198ca4..6324aa1124 100644
--- a/frontend/resources/styles/common/dependencies/z-index.scss
+++ b/frontend/resources/styles/common/dependencies/z-index.scss
@@ -5,7 +5,7 @@
// Copyright (c) 2015-2016 Andrey Antukh
// Copyright (c) 2015-2016 Juan de la Cruz
-$autocomplete:30000;
+$autocomplete: 30000;
$index-lightbox-shadow: 60000;
$index-lightbox: 60001;
$index-lightbox-close-x: 200;
diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss
index adaf7f1383..37e5c275e9 100644
--- a/frontend/resources/styles/common/framework.scss
+++ b/frontend/resources/styles/common/framework.scss
@@ -19,7 +19,7 @@
justify-content: center;
min-width: 25px;
padding: 0 1rem;
- transition: all .4s;
+ transition: all 0.4s;
text-decoration: none !important;
svg {
height: 16px;
@@ -76,7 +76,7 @@
color: $color-primary-dark;
flex-shrink: 0;
&:hover {
- background: rgba(49,239,184,.12);
+ background: rgba(49, 239, 184, 0.12);
}
}
@@ -148,7 +148,6 @@
}
}
-
.btn-gray {
@extend %btn;
background: $color-gray-30;
@@ -171,7 +170,7 @@
.btn-option {
display: flex;
a {
- margin-right: .5rem;
+ margin-right: 0.5rem;
&:last-child {
margin-right: 0;
}
@@ -179,7 +178,7 @@
&.column {
flex-direction: column;
a {
- margin-bottom: .5rem;
+ margin-bottom: 0.5rem;
&:last-child {
margin-bottom: 0;
}
@@ -193,11 +192,11 @@
max-width: 400px;
}
&.mb {
- margin-bottom: .5rem;
+ margin-bottom: 0.5rem;
}
}
-input[type=button][disabled],
+input[type="button"][disabled],
.btn-disabled {
background-color: $color-gray-10;
color: $color-gray-40;
@@ -254,11 +253,9 @@ ul.slider-dots {
&:hover {
background-color: $color-gray-10;
}
-
}
&.dots-purple {
-
li {
border-color: $color-complete;
@@ -266,17 +263,13 @@ ul.slider-dots {
&:hover {
background-color: $color-complete;
}
-
}
-
}
-
}
// Doted list
.doted-list {
-
li {
align-items: center;
display: flex;
@@ -295,9 +288,7 @@ ul.slider-dots {
&.not-included {
text-decoration: line-through;
}
-
}
-
}
// Tags
@@ -310,7 +301,7 @@ ul.slider-dots {
margin-right: 0;
}
- .tag {
+ .tag {
background-color: $color-gray-20;
border-radius: 3px;
color: $color-white;
@@ -332,7 +323,6 @@ ul.slider-dots {
&:hover {
background-color: $color-primary-dark;
}
-
}
&.tag-green {
@@ -342,7 +332,6 @@ ul.slider-dots {
&:hover {
background-color: $color-success-dark;
}
-
}
&.tag-purple {
@@ -352,7 +341,6 @@ ul.slider-dots {
&:hover {
background-color: $color-complete-dark;
}
-
}
&.tag-orange {
@@ -362,7 +350,6 @@ ul.slider-dots {
&:hover {
background-color: $color-warning-dark;
}
-
}
&.tag-red {
@@ -372,11 +359,8 @@ ul.slider-dots {
&:hover {
background-color: $color-danger-dark;
}
-
}
-
}
-
}
// Input elements
@@ -404,8 +388,8 @@ ul.slider-dots {
}
.after {
- width: auto;
- right: 6px;
+ width: auto;
+ right: 6px;
}
&.mini {
@@ -425,49 +409,42 @@ ul.slider-dots {
}
&.percentail {
-
&::after {
content: "%";
}
}
- &.miliseconds {
-
+ &.milliseconds {
&::after {
content: "ms";
}
}
&.degrees {
-
&::after {
content: "dg";
}
}
&.height {
-
&::after {
content: "H";
}
}
&.width {
-
&::after {
content: "W";
}
}
&.Xaxis {
-
&::after {
content: "X";
}
}
&.Yaxis {
-
&::after {
content: "Y";
}
@@ -524,7 +501,7 @@ input[type="checkbox"]:focus {
position: relative;
@include placeholder {
- transition: all .3s ease;
+ transition: all 0.3s ease;
}
&:focus {
@@ -534,9 +511,8 @@ input[type="checkbox"]:focus {
@include placeholder {
opacity: 0;
transform: translateY(-20px);
- transition: all .3s ease;
+ transition: all 0.3s ease;
}
-
}
&.success {
@@ -550,7 +526,6 @@ input[type="checkbox"]:focus {
border-color: $color-danger;
color: $color-danger-dark;
}
-
}
// Element-name
@@ -579,10 +554,8 @@ input.element-name {
&.small {
padding: $size-1 $size-5 $size-1 $size-1;
}
-
}
-
// Input radio
.input-radio,
@@ -594,14 +567,15 @@ input.element-name {
margin-top: 10px;
padding-left: 0px;
- label{
+ label {
cursor: pointer;
display: flex;
+ align-items: center;
margin-right: 15px;
font-size: $fs12;
- &:before{
- content:"";
+ &:before {
+ content: "";
width: 20px;
height: 20px;
margin-right: 10px;
@@ -617,11 +591,9 @@ input.element-name {
align-items: flex-start;
flex-direction: column;
}
-
}
.input-radio {
-
label {
margin-bottom: 6px;
@@ -629,76 +601,61 @@ input.element-name {
border-radius: 99px;
transition: box-shadow 0.2s linear 0s, color 0.2s linear 0s;
}
-
}
- input[type=radio]:checked {
-
+ input[type="radio"]:checked {
& + label {
-
&:before {
- box-shadow: inset 0 0 0 5px $color-gray-20 ;
+ box-shadow: inset 0 0 0 5px $color-gray-20;
}
-
}
-
}
- input[type=radio] {
+ input[type="radio"] {
display: none;
}
- input[type=radio][disabled] {
-
+ input[type="radio"][disabled] {
& + label {
opacity: 0.65;
}
-
}
-
}
-input[type=radio]:checked + label:before{
-
- .input-radio.radio-success &{
+input[type="radio"]:checked + label:before {
+ .input-radio.radio-success & {
box-shadow: inset 0 0 0 5px $color-success;
}
- .input-radio.radio-primary &{
+ .input-radio.radio-primary & {
box-shadow: inset 0 0 0 5px $color-primary;
}
- .input-radio.radio-info &{
+ .input-radio.radio-info & {
box-shadow: inset 0 0 0 5px $color-info;
}
- .input-radio.radio-warning &{
+ .input-radio.radio-warning & {
box-shadow: inset 0 0 0 5px $color-warning;
}
- .input-radio.radio-danger &{
+ .input-radio.radio-danger & {
box-shadow: inset 0 0 0 5px $color-danger;
}
- .input-radio.radio-complete &{
+ .input-radio.radio-complete & {
box-shadow: inset 0 0 0 5px $color-complete;
}
-
}
// Input checkbox
.input-checkbox {
-
- input[type=radio][disabled] {
-
+ input[type="radio"][disabled] {
& + label {
-
&:after {
background-color: $color-gray-10;
}
-
}
-
}
label {
@@ -725,17 +682,14 @@ input[type=radio]:checked + label:before{
&:after {
border-radius: 3px;
}
-
}
- input[type=checkbox] {
+ input[type="checkbox"] {
display: none;
}
&.checkbox-circle {
-
label {
-
&:after {
border-radius: 99px;
}
@@ -743,98 +697,90 @@ input[type=radio]:checked + label:before{
&:before {
border-radius: 99px;
}
-
}
-
}
- input[type=checkbox]:checked {
-
+ input[type="checkbox"]:checked {
& + label {
-
&:before {
border-width: 10px;
}
&::after {
- content:"✓";
+ content: "✓";
color: #ffffff;
font-size: $fs16;
}
-
}
-
}
- input[type=checkbox][disabled] {
-
+ input[type="checkbox"][disabled] {
& + label {
opacity: 0.65;
&:before {
background-color: #eceff3;
}
-
}
+ }
+ input[type="checkbox"][indeterminate] {
+ & + label {
+ &::after {
+ content: "?";
+ left: 4px;
+ }
+ }
}
&.right {
-
label {
margin-right: 35px;
- padding-left:0 !important;
+ padding-left: 0 !important;
&:before {
- right:-35px;
- left:auto;
+ right: -35px;
+ left: auto;
}
-
}
- input[type=checkbox]:checked {
-
+ input[type="checkbox"]:checked {
& + label {
position: relative;
&::after {
- content:"✓";
+ content: "✓";
position: absolute;
right: -27px;
left: auto;
}
-
}
-
}
-
}
-
}
-input[type=checkbox]:checked + label{
-
- .input-checkbox.check-success &:before{
+input[type="checkbox"]:checked + label {
+ .input-checkbox.check-success &:before {
border-color: $color-success;
}
- .input-checkbox.check-primary &:before{
+ .input-checkbox.check-primary &:before {
border-color: $color-primary;
}
- .input-checkbox.check-complete &:before{
+ .input-checkbox.check-complete &:before {
border-color: $color-complete;
}
- .input-checkbox.check-warning &:before{
+ .input-checkbox.check-warning &:before {
border-color: $color-warning;
}
- .input-checkbox.check-danger &:before{
+ .input-checkbox.check-danger &:before {
border-color: $color-danger;
}
- .input-checkbox.check-info &:before{
+ .input-checkbox.check-info &:before {
border-color: $color-info;
}
@@ -846,22 +792,21 @@ input[type=checkbox]:checked + label{
.input-checkbox.check-info &::after {
color: $color-white;
}
-
}
// Input slidebar
-input[type=range] {
+input[type="range"] {
background-color: transparent;
-webkit-appearance: none;
margin: 10px 0 10px 3px;
max-width: 70px;
width: 100%;
}
-input[type=range]:focus {
+input[type="range"]:focus {
outline: none;
}
-input[type=range]::-webkit-slider-runnable-track {
+input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 6px;
cursor: pointer;
@@ -871,7 +816,7 @@ input[type=range]::-webkit-slider-runnable-track {
border-radius: 25px;
border: 0px solid #000101;
}
-input[type=range]::-webkit-slider-thumb {
+input[type="range"]::-webkit-slider-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 18px;
@@ -882,10 +827,10 @@ input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
margin-top: -6px;
}
-input[type=range]:focus::-webkit-slider-runnable-track {
+input[type="range"]:focus::-webkit-slider-runnable-track {
background: $color-gray-60;
}
-input[type=range]::-moz-range-track {
+input[type="range"]::-moz-range-track {
width: 100%;
height: 8px;
cursor: pointer;
@@ -895,7 +840,7 @@ input[type=range]::-moz-range-track {
border-radius: 25px;
border: 0px solid #000101;
}
-input[type=range]::-moz-range-thumb {
+input[type="range"]::-moz-range-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 24px;
@@ -904,7 +849,7 @@ input[type=range]::-moz-range-thumb {
background: $color-gray-20;
cursor: pointer;
}
-input[type=range]::-ms-track {
+input[type="range"]::-ms-track {
width: 100%;
height: 8px;
cursor: pointer;
@@ -914,19 +859,19 @@ input[type=range]::-ms-track {
border-width: 39px 0;
color: transparent;
}
-input[type=range]::-ms-fill-lower {
+input[type="range"]::-ms-fill-lower {
background: $color-gray-60;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
-input[type=range]::-ms-fill-upper {
+input[type="range"]::-ms-fill-upper {
background: $color-gray-60;
border: 0px solid #000101;
border-radius: 50px;
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
}
-input[type=range]::-ms-thumb {
+input[type="range"]::-ms-thumb {
box-shadow: 0px 0px 0px #000000, 0px 0px 0px #0d0d0d;
border: 0px solid #000000;
height: 24px;
@@ -935,32 +880,31 @@ input[type=range]::-ms-thumb {
background: $color-gray-20;
cursor: pointer;
}
-input[type=range]:focus::-ms-fill-lower {
+input[type="range"]:focus::-ms-fill-lower {
background: $color-gray-60;
}
-input[type=range]:focus::-ms-fill-upper {
+input[type="range"]:focus::-ms-fill-upper {
background: $color-gray-60;
}
// Scroll bar (chrome)
::-webkit-scrollbar {
- background-color: transparent;
- cursor: pointer;
- height: 8px;
- width: 8px;
+ background-color: transparent;
+ cursor: pointer;
+ height: 8px;
+ width: 8px;
}
::-webkit-scrollbar-track {
- background-color: transparent;
+ background-color: transparent;
}
::-webkit-scrollbar-thumb {
- background-color: $color-gray-20;
-
- &:hover {
- background-color: darken($color-gray-20, 14%);
- outline: 2px solid $color-primary;
- }
+ background-color: $color-gray-20;
+ &:hover {
+ background-color: darken($color-gray-20, 14%);
+ outline: 2px solid $color-primary;
+ }
}
// Tooltip
@@ -983,7 +927,7 @@ input[type=range]:focus::-ms-fill-upper {
top: 0;
white-space: nowrap;
z-index: 20;
- @include animation(.3s,.6s,fadeIn);
+ @include animation(0.3s, 0.6s, fadeIn);
}
}
@@ -1024,7 +968,7 @@ input[type=range]:focus::-ms-fill-upper {
}
}
- &.tooltip-right {
+ &.tooltip-right {
&:hover {
&::after {
top: 15%;
@@ -1033,7 +977,7 @@ input[type=range]:focus::-ms-fill-upper {
}
}
- &.tooltip-left {
+ &.tooltip-left {
&:hover {
&::after {
left: unset;
@@ -1085,7 +1029,7 @@ input[type=range]:focus::-ms-fill-upper {
}
&.hide {
- @include animation(0, .6s, fadeOutUp);
+ @include animation(0, 0.6s, fadeOutUp);
}
.icon {
@@ -1136,7 +1080,7 @@ input[type=range]:focus::-ms-fill-upper {
justify-content: center;
align-items: center;
cursor: pointer;
- opacity: .35;
+ opacity: 0.35;
svg {
fill: $color-black;
@@ -1146,13 +1090,13 @@ input[type=range]:focus::-ms-fill-upper {
}
&:hover {
- opacity: .8;
+ opacity: 0.8;
}
}
&.fixed {
border-radius: $br-small;
- box-shadow: 0px 4px 4px rgba(0,0,0,0.2);
+ box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.2);
height: 48px;
max-width: 1000px;
min-width: 500px;
@@ -1185,7 +1129,8 @@ input[type=range]:focus::-ms-fill-upper {
}
}
- &.floating, &.inline {
+ &.floating,
+ &.inline {
min-height: 40px;
.wrapper {
@@ -1211,25 +1156,25 @@ input[type=range]:focus::-ms-fill-upper {
&.error {
.content {
- background-color: lighten($color-danger,30%);
+ background-color: lighten($color-danger, 30%);
}
}
&.success {
.content {
- background-color: lighten($color-success,30%);
+ background-color: lighten($color-success, 30%);
}
}
&.warning {
.content {
- background-color: lighten($color-warning,30%);
+ background-color: lighten($color-warning, 30%);
}
}
&.info {
.content {
- background-color: lighten($color-info,30%);
+ background-color: lighten($color-info, 30%);
}
}
}
@@ -1281,7 +1226,7 @@ input[type=range]:focus::-ms-fill-upper {
background-color: $color-info;
color: $color-info-darker;
margin-bottom: 1.2rem;
- padding: .8rem;
+ padding: 0.8rem;
text-align: center;
p {
margin: 0;
diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss
index 53d0baedf1..ca1c5c61b7 100644
--- a/frontend/resources/styles/main-default.scss
+++ b/frontend/resources/styles/main-default.scss
@@ -4,7 +4,6 @@
//
// Copyright (c) UXBOX Labs SL
-
//#################################################
// Import libraries
//#################################################
diff --git a/frontend/resources/styles/main/layouts/handoff.scss b/frontend/resources/styles/main/layouts/handoff.scss
index 917410eca3..087c0c6f47 100644
--- a/frontend/resources/styles/main/layouts/handoff.scss
+++ b/frontend/resources/styles/main/layouts/handoff.scss
@@ -1,8 +1,9 @@
$width-settings-bar: 16rem;
.handoff-layout {
+ height: 100vh;
display: grid;
- grid-template-rows: 40px auto;
+ grid-template-rows: 48px auto;
grid-template-columns: 1fr;
user-select: none;
@@ -17,13 +18,14 @@ $width-settings-bar: 16rem;
}
}
-.fullscreen .handoff-layout:not(.force-visible) {
+.fullscreen.handoff-layout:not(.force-visible) {
.viewer-header {
width: 100%;
position: fixed;
top: -48px;
left: 0;
transition: top 400ms ease 300ms;
+ z-index: 25;
&::after {
content: " ";
@@ -48,7 +50,14 @@ $width-settings-bar: 16rem;
.handoff-layout {
.viewer-section {
flex-wrap: nowrap;
+ &.fullscreen {
+ .settings-bar,
+ .settings-bar {
+ padding-top: 48px;
+ }
+ }
}
+
.settings-bar {
transition: width 0.2s;
&.expanded {
@@ -77,7 +86,7 @@ $width-settings-bar: 16rem;
.handoff-svg-container {
display: grid;
width: 100%;
- height: calc(100% - 35px);
+ height: 100%;
overflow: auto;
align-items: center;
justify-content: safe center;
diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss
index 627753cf01..fa79a39958 100644
--- a/frontend/resources/styles/main/layouts/login.scss
+++ b/frontend/resources/styles/main/layouts/login.scss
@@ -22,7 +22,7 @@
flex-direction: column;
align-items: center;
justify-content: flex-start;
- background-color:#FEECFD;
+ background-color: #feecfd;
background-image: url("/images/login-pink.svg");
background-position: center;
background-size: 96%;
@@ -33,12 +33,12 @@
width: 280px;
font-size: $fs18;
margin-top: 2vh;
- color: #2C233E;
+ color: #2c233e;
}
.logo {
svg {
- fill: #2C233E;
+ fill: #2c233e;
max-width: 11vw;
height: 80px;
}
@@ -122,7 +122,6 @@
margin-top: $size-4;
margin-bottom: $size-4;
-
&.demo {
justify-content: center;
margin-top: $size-5;
diff --git a/frontend/resources/styles/main/layouts/not-found.scss b/frontend/resources/styles/main/layouts/not-found.scss
index fbecd59102..99060839d7 100644
--- a/frontend/resources/styles/main/layouts/not-found.scss
+++ b/frontend/resources/styles/main/layouts/not-found.scss
@@ -17,7 +17,6 @@
height: 55px;
width: 170px;
}
-
}
.not-found-content {
@@ -29,7 +28,6 @@
justify-content: center;
align-items: center;
-
.container {
max-width: 600px;
}
@@ -80,4 +78,3 @@
}
}
}
-
diff --git a/frontend/resources/styles/main/layouts/viewer.scss b/frontend/resources/styles/main/layouts/viewer.scss
index 296322332b..28a817ad7e 100644
--- a/frontend/resources/styles/main/layouts/viewer.scss
+++ b/frontend/resources/styles/main/layouts/viewer.scss
@@ -15,14 +15,14 @@
}
}
-.fullscreen .viewer-layout:not(.force-visible) {
+.fullscreen.viewer-layout:not(.force-visible) {
& .viewer-header {
width: 100%;
position: fixed;
top: -48px;
left: 0;
transition: top 400ms ease 300ms;
-
+ z-index: 1;
&::after {
content: " ";
position: absolute;
@@ -31,7 +31,6 @@
left: 0;
top: 48px;
}
-
}
& .viewer-header:hover {
@@ -57,5 +56,3 @@
background-color: rgb(0, 0, 0, 0.2);
}
}
-
-
diff --git a/frontend/resources/styles/main/partials/activity-bar.scss b/frontend/resources/styles/main/partials/activity-bar.scss
index 7e116fae18..75ca3ec58e 100644
--- a/frontend/resources/styles/main/partials/activity-bar.scss
+++ b/frontend/resources/styles/main/partials/activity-bar.scss
@@ -65,7 +65,6 @@
font-weight: bold;
margin: 0 3px;
}
-
}
.activity-time {
@@ -75,5 +74,4 @@
}
}
}
-
}
diff --git a/frontend/resources/styles/main/partials/af-signup-questions.scss b/frontend/resources/styles/main/partials/af-signup-questions.scss
index 38c431a2ea..bb5017818b 100644
--- a/frontend/resources/styles/main/partials/af-signup-questions.scss
+++ b/frontend/resources/styles/main/partials/af-signup-questions.scss
@@ -12,9 +12,10 @@
padding: 3rem;
width: 100% !important;
- h1, h3 {
- font-family: 'worksans', sans-serif !important;
- margin-bottom: .8rem;
+ h1,
+ h3 {
+ font-family: "worksans", sans-serif !important;
+ margin-bottom: 0.8rem;
font-weight: 500 !important;
}
@@ -26,8 +27,9 @@
font-weight: 500;
}
- p, label {
- font-family: 'worksans', sans-serif !important;
+ p,
+ label {
+ font-family: "worksans", sans-serif !important;
font-size: $fs14;
}
@@ -37,7 +39,7 @@
}
button {
- font-family: 'worksans', sans-serif !important;
+ font-family: "worksans", sans-serif !important;
}
.af-choice,
@@ -51,7 +53,7 @@
width: 100%;
label {
- font-family: 'worksans', sans-serif !important;
+ font-family: "worksans", sans-serif !important;
font-size: $fs14;
padding-left: 0;
}
@@ -66,7 +68,6 @@
.af-divider-block {
/* margin-bottom: 2rem; */
-
p {
&::after,
&::before {
@@ -77,7 +78,7 @@
.af-dropdown-text,
.text {
- font-family: 'worksans', sans-serif !important;
+ font-family: "worksans", sans-serif !important;
}
.af-step-next {
@@ -110,13 +111,13 @@
border-color: #c5c6c9 !important;
}
- .af-choice-option input:checked+label:before,
- .af-legal input:checked+label:before {
+ .af-choice-option input:checked + label:before,
+ .af-legal input:checked + label:before {
background-color: $color-primary;
}
- .af-field-use_of_penpot .af-choice-option input:checked+label,
- .af-field-previous_design_tool .af-choice-option input:checked+label {
+ .af-field-use_of_penpot .af-choice-option input:checked + label,
+ .af-field-previous_design_tool .af-choice-option input:checked + label {
&::before {
background-color: transparent;
border: 2px solid $color-primary !important;
@@ -157,7 +158,7 @@
&:hover {
background-color: transparent;
- box-shadow: 0px 10px 20px rgba(0,0,0,.2);
+ box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.2);
}
&::before {
@@ -178,7 +179,6 @@
.af-field-previous_design_tool .af-choice-option:nth-child(1) label {
background-image: url("../images/form/figma.png");
-
}
.af-field-previous_design_tool .af-choice-option:nth-child(2) label {
@@ -243,9 +243,11 @@
}
}
- .af-field-previous_design_tool .af-choice-option:nth-child(7) input:checked+label,
- .af-field-use_of_penpot .af-choice-option:nth-child(5) input:checked+label {
-
+ .af-field-previous_design_tool
+ .af-choice-option:nth-child(7)
+ input:checked
+ + label,
+ .af-field-use_of_penpot .af-choice-option:nth-child(5) input:checked + label {
&::before {
background-color: $color-primary;
}
diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss
index 9522617764..c9c1acfa64 100644
--- a/frontend/resources/styles/main/partials/color-bullet.scss
+++ b/frontend/resources/styles/main/partials/color-bullet.scss
@@ -6,7 +6,6 @@
.color-cell {
.color-bullet {
- background-color: transparent;
// Creates strange artifacts
border: 2px solid $color-gray-60;
// box-shadow: 0 0 0 2px $color-gray-60;
@@ -31,7 +30,6 @@
width: 50px;
height: 50px;
}
-
}
.color-cell.current {
@@ -56,7 +54,7 @@ ul.palette-menu .color-bullet {
display: flex;
justify-content: center;
margin-bottom: 1rem;
- padding: .6rem;
+ padding: 0.6rem;
svg {
fill: $color-gray-10;
@@ -93,9 +91,8 @@ ul.palette-menu .color-bullet {
.color-bullet {
display: flex;
flex-direction: row;
- overflow: hidden;
-
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+ left center;
background-color: $color-white;
& > * {
@@ -104,20 +101,37 @@ ul.palette-menu .color-bullet {
}
}
-.color-data .color-bullet.multiple {
- background: transparent;
+.color-bullet.is-library-color .color-bullet-left,
+.selected-colors .color-bullet .color-bullet-left {
+ border-radius: 10px 0 0 10px;
+}
- &::before {
- content: "?"
- }
+.color-bullet.is-library-color .color-bullet-right,
+.selected-colors .color-bullet .color-bullet-right {
+ border-radius: 0 10px 10px 0;
+}
+
+.color-palette .color-bullet .color-bullet-left {
+ border-radius: 25px 0 0 25px;
+}
+
+.color-palette .color-bullet .color-bullet-right {
+ border-radius: 0 25px 25px 0;
}
.color-data .color-bullet.is-library-color {
border-radius: 50%;
}
+.color-data .color-bullet.multiple {
+ background: transparent;
+
+ &::before {
+ content: "?";
+ }
+}
+
.color-data .color-bullet {
- background-color: $color-gray-30;
border: 1px solid $color-gray-30;
border-radius: $br-small;
cursor: pointer;
@@ -151,7 +165,6 @@ ul.palette-menu .color-bullet {
svg {
fill: $color-primary;
}
-
}
}
}
@@ -163,6 +176,7 @@ ul.palette-menu .color-bullet {
&:hover {
border-color: $color-primary;
+ z-index: 10;
}
&.button {
diff --git a/frontend/resources/styles/main/partials/color-palette.scss b/frontend/resources/styles/main/partials/color-palette.scss
index 5cb587d059..fb42b2418b 100644
--- a/frontend/resources/styles/main/partials/color-palette.scss
+++ b/frontend/resources/styles/main/partials/color-palette.scss
@@ -5,7 +5,7 @@
// Copyright (c) UXBOX Labs SL
.color-palette {
- @include animation(0,.5s,fadeInUp);
+ @include animation(0, 0.5s, fadeInUp);
align-items: center;
background-color: $color-gray-50;
border-top: 1px solid $color-gray-60;
@@ -18,12 +18,12 @@
& .right-arrow,
& .left-arrow {
- cursor: pointer;
+ cursor: pointer;
svg {
fill: $color-gray-20;
height: 1rem;
- margin: 0 .5rem;
+ margin: 0 0.5rem;
width: 1rem;
}
@@ -37,53 +37,52 @@
}
}
-
.left-arrow {
- transform: rotate(180deg);
- padding-top: 10px;
+ transform: rotate(180deg);
+ padding-top: 10px;
}
&.fade-out-down {
- @include animation(0,.5s,fadeOutDown);
+ @include animation(0, 0.5s, fadeOutDown);
}
&.left-sidebar-open {
- left: 303px;
- width: calc(100% - 303px);
+ left: 303px;
+ width: calc(100% - 303px);
}
& .context-menu-items {
- bottom: 1.5rem;
- top: initial;
- min-width: 10rem;
+ bottom: 1.5rem;
+ top: initial;
+ min-width: 10rem;
}
}
.color-palette-actions {
- align-self: stretch;
- border: 1px solid #1F1F1F;
- cursor: pointer;
- display: flex;
- flex-direction: column;
- flex-shrink: 0;
- justify-content: center;
- margin-right: .5rem;
- padding: 0.5rem;
+ align-self: stretch;
+ border: 1px solid #1f1f1f;
+ cursor: pointer;
+ display: flex;
+ flex-direction: column;
+ flex-shrink: 0;
+ justify-content: center;
+ margin-right: 0.5rem;
+ padding: 0.5rem;
- .color-palette-buttons {
- align-items: center;
- display: flex;
- justify-content: space-around;
- }
+ .color-palette-buttons {
+ align-items: center;
+ display: flex;
+ justify-content: space-around;
+ }
}
.color-palette-actions-button {
- cursor: pointer;
- & svg {
- width: 1rem;
- height: 1rem;
- fill: #AFB2BF;
- }
+ cursor: pointer;
+ & svg {
+ width: 1rem;
+ height: 1rem;
+ fill: #afb2bf;
+ }
}
.btn-palette {
@@ -94,7 +93,7 @@
display: flex;
flex-shrink: 0;
justify-content: center;
- padding: .6rem;
+ padding: 0.6rem;
svg {
fill: $color-gray-10;
height: 20px;
@@ -124,7 +123,7 @@
padding: 0.25rem;
&.size-small {
- height: 3.5rem;
+ height: 3.5rem;
}
}
@@ -132,7 +131,7 @@
position: relative;
align-items: center;
display: flex;
- transition: all .6s ease;
+ transition: all 0.6s ease;
width: 100%;
scroll-behavior: smooth;
}
@@ -146,11 +145,11 @@
position: relative;
&.cell-big {
- flex-basis: 66px;
+ flex-basis: 66px;
}
&.cell-small {
- flex-basis: 52px;
+ flex-basis: 52px;
}
.color-text {
@@ -232,7 +231,7 @@
position: relative;
.input-text {
font-size: $fs12;
- margin: 0 .5rem;
+ margin: 0 0.5rem;
max-width: 100px;
}
&::after {
@@ -251,43 +250,41 @@
}
ul.palette-menu {
- left: 8px;
- top: auto;
- bottom: 4.5rem;
- color: $color-black;
+ left: 8px;
+ top: auto;
+ bottom: 4.5rem;
+ color: $color-black;
- li {
- position: relative;
- padding: 5px 1.5rem;
- }
+ li {
+ position: relative;
+ padding: 5px 1.5rem;
+ }
- hr {
- margin: 0.5rem 0;
- }
+ hr {
+ margin: 0.5rem 0;
+ }
- svg {
- width: 9px;
- height: 9px;
- position: absolute;
- left: 0.5rem;
- top: 10px;
- }
+ svg {
+ width: 9px;
+ height: 9px;
+ position: absolute;
+ left: 0.5rem;
+ top: 10px;
+ }
- hr {
- border-color: $color-gray-20;
- }
+ hr {
+ border-color: $color-gray-20;
+ }
- .palette-library {
- display: flex;
- flex-direction: column;
- align-items: flex-start;
- }
-
- .color-sample {
- display: flex;
- flex-direction: row;
- margin-top: 0.5rem;
- }
+ .palette-library {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ }
+ .color-sample {
+ display: flex;
+ flex-direction: row;
+ margin-top: 0.5rem;
+ }
}
-
diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss
index f5e8afb3ff..dbd3fff3dd 100644
--- a/frontend/resources/styles/main/partials/colorpicker.scss
+++ b/frontend/resources/styles/main/partials/colorpicker.scss
@@ -76,7 +76,8 @@
height: 100%;
width: 100%;
border: 1px solid $color-gray-10;
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+ left center;
}
.gradient-background {
@@ -102,7 +103,8 @@
margin-left: -7px;
box-shadow: 0 2px 2px rgb(0 0 0 / 15%);
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") left center;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+ left center;
background-color: $color-white;
&.active {
@@ -142,7 +144,11 @@
border: 1px solid $color-gray-10;
- background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
+ background: linear-gradient(
+ var(--gradient-direction),
+ rgba(var(--color), 0) 0%,
+ rgba(var(--color), 1) 100%
+ );
align-self: center;
position: relative;
cursor: pointer;
@@ -157,9 +163,15 @@
&.hue {
background: linear-gradient(
- var(--gradient-direction),
- #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%,
- #00f 67%, #f0f 83%, #f00 100%);
+ var(--gradient-direction),
+ #f00 0%,
+ #ff0 17%,
+ #0f0 33%,
+ #0ff 50%,
+ #00f 67%,
+ #f0f 83%,
+ #f00 100%
+ );
}
&.saturation {
@@ -167,29 +179,36 @@
var(--gradient-direction),
var(--saturation-grad-from) 0%,
var(--saturation-grad-to) 100%
- )
+ );
}
&.opacity {
- background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=") var(--background-repeat) center;
+ background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+ var(--background-repeat) center;
&::after {
content: "";
position: absolute;
width: 100%;
height: 100%;
- background: linear-gradient(var(--gradient-direction), rgba(var(--color), 0) 0%, rgba(var(--color), 1.0) 100%);
+ background: linear-gradient(
+ var(--gradient-direction),
+ rgba(var(--color), 0) 0%,
+ rgba(var(--color), 1) 100%
+ );
}
-
}
&.value {
- background: linear-gradient(var(--gradient-direction), #FFF 0%, #000 100%);
+ background: linear-gradient(
+ var(--gradient-direction),
+ #fff 0%,
+ #000 100%
+ );
}
-
.handler {
- background-color: $color-white;;
+ background-color: $color-white;
box-shadow: rgba(0, 0, 0, 0.37) 0px 1px 4px 0px;
transform: translate(-6px, -2px);
left: 50%;
@@ -218,7 +237,8 @@
border-radius: 6px;
z-index: 1;
border: 1px solid $color-white;
- box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
+ box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset,
+ rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
transform: translate(-6px, -6px);
left: 50%;
top: 50%;
@@ -229,7 +249,7 @@
position: absolute;
width: 100%;
height: 100%;
- background: linear-gradient(to right, #fff, rgba(255,255,255,0));
+ background: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));
}
&::after {
@@ -237,7 +257,7 @@
position: absolute;
width: 100%;
height: 100%;
- background: linear-gradient(to top, #000, rgba(0,0,0,0));
+ background: linear-gradient(to top, #000, rgba(0, 0, 0, 0));
}
}
@@ -245,8 +265,9 @@
display: grid;
justify-items: center;
align-items: center;
- grid-template-areas: "color hue"
- "color opacity";
+ grid-template-areas:
+ "color hue"
+ "color opacity";
grid-template-columns: 2.5rem 1fr;
height: 3.5rem;
grid-row-gap: 0.5rem;
@@ -296,7 +317,7 @@
padding-top: 0.5rem;
margin-top: 0.25rem;
width: 200px;
-
+
select {
background-image: url(/images/icons/arrow-down.svg);
background-repeat: no-repeat;
@@ -322,12 +343,9 @@
grid-template-columns: repeat(8, 1fr);
justify-content: space-between;
margin-right: -8px;
- overflow-x: hidden;
- overflow-y: auto;
max-height: 5.5rem;
}
-
.selected-colors::after {
content: "";
flex: auto;
@@ -367,7 +385,8 @@
border-radius: 6px;
z-index: 1;
border: 1px solid $color-white;
- box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset, rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
+ box-shadow: rgb(255, 255, 255) 0px 0px 0px 1px inset,
+ rgb(0 0 0 / 0.25) 0px 4px 4px inset, rgb(0 0 0 / 0.25) 0px 4px 4px;
transform: translate(-6px, -6px);
left: 50%;
top: 50%;
@@ -431,7 +450,6 @@
}
.inputs-area {
-
.input-text {
color: $color-gray-60;
font-size: $fs12;
@@ -439,7 +457,6 @@
padding: 5px;
width: 100%;
}
-
}
.colorpicker-tabs {
@@ -473,7 +490,6 @@
:hover svg {
fill: $color-primary;
}
-
}
}
@@ -517,7 +533,7 @@
}
}
- ::placeholder{
+ ::placeholder {
color: $color-gray-10;
}
@@ -535,4 +551,3 @@
height: 10px;
}
}
-
diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss
index 04fcc45839..e5e865d13d 100644
--- a/frontend/resources/styles/main/partials/comments.scss
+++ b/frontend/resources/styles/main/partials/comments.scss
@@ -8,7 +8,7 @@
pointer-events: auto;
background-color: $color-gray-10;
color: $color-gray-60;
- border: 1px solid #B1B2B5;
+ border: 1px solid #b1b2b5;
box-sizing: border-box;
box-shadow: 0px 4px 4px rgba($color-black, 0.25);
@@ -124,7 +124,6 @@
@include text-ellipsis;
width: 174px;
-
}
.timeago {
margin-top: -2px;
@@ -178,7 +177,6 @@
fill: $color-black;
}
}
-
}
}
@@ -194,13 +192,12 @@
}
}
-
.comment-options-dropdown {
top: 7px;
right: 7px;
width: 150px;
- border: 1px solid #B1B2B5;
+ border: 1px solid #b1b2b5;
}
}
@@ -241,13 +238,10 @@
}
}
-
-
.comment-threads-section {
pointer-events: auto;
.thread-groups {
-
hr {
border: 0;
height: 1px;
@@ -332,7 +326,6 @@
}
}
-
.viewer-comments-container {
width: 100%;
height: 100%;
@@ -345,8 +338,8 @@
.workspace-comments-container {
width: 100%;
height: 100%;
- grid-column: 1/span 2;
- grid-row: 1/span 2;
+ grid-column: 1 / span 2;
+ grid-row: 1 / span 2;
z-index: 1000;
pointer-events: none;
overflow: hidden;
@@ -389,7 +382,9 @@
&.open {
background-color: $color-black;
- svg { fill: $color-primary; }
+ svg {
+ fill: $color-primary;
+ }
}
}
@@ -419,7 +414,6 @@
align-items: center;
}
-
svg {
width: 15px;
height: 15px;
@@ -439,7 +433,7 @@
.thread-group .section-title {
color: $color-black;
}
-
+
.comment {
.author .name .fullname {
color: $color-gray-40;
@@ -465,4 +459,3 @@
width: 24px;
}
}
-
diff --git a/frontend/resources/styles/main/partials/context-menu.scss b/frontend/resources/styles/main/partials/context-menu.scss
index ce19531bc8..37adaa41e5 100644
--- a/frontend/resources/styles/main/partials/context-menu.scss
+++ b/frontend/resources/styles/main/partials/context-menu.scss
@@ -5,99 +5,98 @@
// Copyright (c) UXBOX Labs SL
.context-menu {
- position: relative;
- visibility: hidden;
- opacity: 0;
- z-index: 100;
+ position: relative;
+ visibility: hidden;
+ opacity: 0;
+ z-index: 100;
}
.context-menu.is-open {
- position: relative;
- display: block;
- opacity: 1;
- visibility: visible;
+ position: relative;
+ display: block;
+ opacity: 1;
+ visibility: visible;
}
.context-menu.fixed {
- position: fixed;
+ position: fixed;
}
.context-menu-items {
- background: $color-white;
- border-radius: $br-small;
- box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
- left: -$size-4;
- max-height: 30rem;
- min-width: 7rem;
- overflow: auto;
- position: absolute;
- top: $size-3;
+ background: $color-white;
+ border-radius: $br-small;
+ box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.25);
+ left: -$size-4;
+ max-height: 30rem;
+ min-width: 7rem;
+ overflow: auto;
+ position: absolute;
+ top: $size-3;
- & .separator {
- border-top: 1px solid $color-gray-10;
- padding: 0px;
- margin: 2px;
- }
+ & .separator {
+ border-top: 1px solid $color-gray-10;
+ padding: 0px;
+ margin: 2px;
+ }
- &.min-width {
- min-width: 13rem;
- }
+ &.min-width {
+ min-width: 13rem;
+ }
}
.context-menu-action {
+ color: $color-black;
+ display: block;
+ font-size: $fs14;
+ padding: $size-2 $size-4;
+ text-align: left;
+ white-space: nowrap;
+
+ &:hover {
color: $color-black;
- display: block;
- font-size: $fs14;
- padding: $size-2 $size-4;
- text-align: left;
- white-space: nowrap;
+ background-color: $color-primary-lighter;
+ }
- &:hover {
- color: $color-black;
- background-color: $color-primary-lighter;
+ &.submenu {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+
+ & span {
+ margin-left: 0.5rem;
}
- &.submenu {
- display: flex;
- align-items: center;
- justify-content: space-between;
-
- & span {
- margin-left: 0.5rem;
- }
-
- & svg {
- height: 10px;
- width: 10px;
- }
+ & svg {
+ height: 10px;
+ width: 10px;
}
+ }
- &.submenu-back {
- color: $color-black;
- display: flex;
- font-weight: bold;
- align-items: center;
+ &.submenu-back {
+ color: $color-black;
+ display: flex;
+ font-weight: bold;
+ align-items: center;
- & svg {
- height: 10px;
- width: 10px;
- transform: rotate(180deg);
- margin-right: $size-2;
- }
+ & svg {
+ height: 10px;
+ width: 10px;
+ transform: rotate(180deg);
+ margin-right: $size-2;
}
+ }
}
.context-menu.is-selectable {
- & .context-menu-action {
- padding-left: 1.5rem;
- }
+ & .context-menu-action {
+ padding-left: 1.5rem;
+ }
- & .context-menu-item.is-selected .context-menu-action {
- background-image: url(/images/icons/tick.svg);
- background-repeat: no-repeat;
- background-position: 5% 48%;
- background-size: 10px;
- font-weight: bold;
- }
+ & .context-menu-item.is-selected .context-menu-action {
+ background-image: url(/images/icons/tick.svg);
+ background-repeat: no-repeat;
+ background-position: 5% 48%;
+ background-size: 10px;
+ font-weight: bold;
+ }
}
-
diff --git a/frontend/resources/styles/main/partials/dashboard-fonts.scss b/frontend/resources/styles/main/partials/dashboard-fonts.scss
index e1ea817ebf..9b186fdc58 100644
--- a/frontend/resources/styles/main/partials/dashboard-fonts.scss
+++ b/frontend/resources/styles/main/partials/dashboard-fonts.scss
@@ -56,7 +56,6 @@
}
.font-item {
- margin-top: $size-5;
color: $color-gray-40;
font-size: $fs14;
background-color: $color-white;
@@ -164,6 +163,10 @@
.upload-button {
width: 100px;
}
+
+ .btn-secondary {
+ margin-left: 10px;
+ }
}
.dashboard-fonts-hero {
diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss
index bbf80ec676..a39028c70b 100644
--- a/frontend/resources/styles/main/partials/dashboard-grid.scss
+++ b/frontend/resources/styles/main/partials/dashboard-grid.scss
@@ -63,7 +63,6 @@
.placeholder-label {
font-size: $fs14;
}
-
}
&.overlay {
@@ -90,8 +89,8 @@
}
.grid-item-icon {
- width:90px;
- height:90px;
+ width: 90px;
+ height: 90px;
}
.item-info {
@@ -133,7 +132,6 @@
font-weight: 400;
}
}
-
}
.item-badge {
@@ -170,7 +168,6 @@
background-color: $color-white;
border: 2px solid $color-primary;
}
-
}
// PROJECTS, ELEMENTS & ICONS GRID
@@ -225,13 +222,9 @@
> svg {
fill: $color-primary-dark;
}
-
}
-
}
-
}
-
}
.project-th-actions.force-display {
@@ -247,11 +240,9 @@
&:hover {
border-color: $color-primary;
}
-
}
.grid-item-image {
-
svg {
max-height: 100px;
max-width: 100px;
@@ -259,7 +250,6 @@
min-width: 40px;
width: 8vw;
}
-
}
.color-swatch {
@@ -291,7 +281,6 @@
justify-content: center;
align-items: center;
}
-
}
// STYLES FOR LIBRARIES
@@ -311,6 +300,9 @@
width: 100%;
background-color: $color-canvas;
+ display: flex;
+ justify-content: center;
+ flex-direction: row;
.img-th {
height: auto;
@@ -321,6 +313,10 @@
height: 100%;
width: 100%;
}
+
+ svg#loader-pencil {
+ fill: $color-gray-20;
+ }
}
}
@@ -348,7 +344,6 @@
}
}
-
svg {
width: 36px;
height: 36px;
@@ -366,4 +361,3 @@
margin-right: calc(100% - 148px);
}
}
-
diff --git a/frontend/resources/styles/main/partials/dashboard-header.scss b/frontend/resources/styles/main/partials/dashboard-header.scss
index b9e7a44f72..251cbe1c95 100644
--- a/frontend/resources/styles/main/partials/dashboard-header.scss
+++ b/frontend/resources/styles/main/partials/dashboard-header.scss
@@ -58,7 +58,6 @@
&:hover {
color: $color-black;
}
-
}
&.active {
@@ -115,7 +114,9 @@
}
&.active {
- svg { fill: $color-gray-50; }
+ svg {
+ fill: $color-gray-50;
+ }
}
}
}
diff --git a/frontend/resources/styles/main/partials/dashboard-settings.scss b/frontend/resources/styles/main/partials/dashboard-settings.scss
index 8515c28cc9..c5c19597b9 100644
--- a/frontend/resources/styles/main/partials/dashboard-settings.scss
+++ b/frontend/resources/styles/main/partials/dashboard-settings.scss
@@ -5,7 +5,6 @@
//
// Copyright (c) UXBOX Labs SL
-
.dashboard-sidebar {
&.settings {
.back-to-dashboard {
@@ -34,7 +33,6 @@
}
}
-
.dashboard-settings {
display: flex;
width: 100%;
@@ -88,7 +86,7 @@
z-index: 14;
}
- input[type=file] {
+ input[type="file"] {
width: 120px;
height: 120px;
position: absolute;
@@ -99,7 +97,9 @@
}
&:hover {
- .update-overlay {opacity: 0.8};
+ .update-overlay {
+ opacity: 0.8;
+ }
}
}
}
@@ -118,5 +118,4 @@
margin-bottom: 20px;
}
}
-
}
diff --git a/frontend/resources/styles/main/partials/dashboard-sidebar.scss b/frontend/resources/styles/main/partials/dashboard-sidebar.scss
index 3a68e1ac43..0003951f45 100644
--- a/frontend/resources/styles/main/partials/dashboard-sidebar.scss
+++ b/frontend/resources/styles/main/partials/dashboard-sidebar.scss
@@ -278,7 +278,8 @@
border-color: $color-black;
}
- .search, .clear-search {
+ .search,
+ .clear-search {
align-items: center;
cursor: pointer;
display: flex;
@@ -354,12 +355,11 @@
justify-content: center;
}
- input[type=submit] {
+ input[type="submit"] {
margin-bottom: 0px;
}
}
-
.profile-section {
align-items: center;
cursor: pointer;
@@ -400,7 +400,7 @@
bottom: 45px;
min-width: 189px;
- @include animation(0,.2s,fadeInUp);
+ @include animation(0, 0.2s, fadeInUp);
li {
height: 40px;
diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss
index 6efc6f1786..94684950d3 100644
--- a/frontend/resources/styles/main/partials/dashboard-team.scss
+++ b/frontend/resources/styles/main/partials/dashboard-team.scss
@@ -1,4 +1,3 @@
-
.dashboard-invite-modal {
top: 65px;
right: 13px;
@@ -31,7 +30,7 @@
.action-buttons {
display: flex;
justify-content: center;
- input[type=submit] {
+ input[type="submit"] {
margin-bottom: 0px;
}
}
@@ -98,11 +97,9 @@
}
}
}
-
}
.dashboard-team-settings {
-
.team-settings {
display: flex;
justify-content: center;
@@ -173,7 +170,9 @@
background: $color-primary-dark;
z-index: 14;
- svg { fill: $color-white; }
+ svg {
+ fill: $color-white;
+ }
}
&:hover {
@@ -194,7 +193,7 @@
svg {
width: 12px;
height: 12px;
- fill: $color-primary-dark;
+ fill: $color-black;
}
.owner {
@@ -209,7 +208,7 @@
.summary {
margin-top: 5px;
- color: $color-primary-dark;
+ color: $color-black;
.icon {
padding: 0px 10px;
margin-right: 12px;
diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss
index 3a8c0deb51..6c786b8286 100644
--- a/frontend/resources/styles/main/partials/dashboard.scss
+++ b/frontend/resources/styles/main/partials/dashboard.scss
@@ -71,7 +71,9 @@
}
&.active {
- svg { fill: $color-gray-50; }
+ svg {
+ fill: $color-gray-50;
+ }
}
}
}
@@ -133,7 +135,6 @@
}
}
-
.edit-wrapper {
border: 1px solid $color-gray-10;
border-radius: $br-small;
diff --git a/frontend/resources/styles/main/partials/debug-icons-preview.scss b/frontend/resources/styles/main/partials/debug-icons-preview.scss
index 7a4940eec1..8ddba1688c 100644
--- a/frontend/resources/styles/main/partials/debug-icons-preview.scss
+++ b/frontend/resources/styles/main/partials/debug-icons-preview.scss
@@ -1,13 +1,14 @@
.debug-preview {
- display: flex;
- flex-direction: column;
- overflow: scroll;
+ display: flex;
+ flex-direction: column;
+ overflow: scroll;
}
.debug-icons-preview {
display: flex;
flex-wrap: wrap;
- .icon-item, .cursor-item {
+ .icon-item,
+ .cursor-item {
padding: 10px;
display: flex;
flex-direction: column;
@@ -22,6 +23,6 @@
}
}
.cursor-item {
- height: auto;
+ height: auto;
}
}
diff --git a/frontend/resources/styles/main/partials/dropdown.scss b/frontend/resources/styles/main/partials/dropdown.scss
index 27500670a5..3e480de707 100644
--- a/frontend/resources/styles/main/partials/dropdown.scss
+++ b/frontend/resources/styles/main/partials/dropdown.scss
@@ -36,14 +36,15 @@
}
}
-
&.with-check {
> li {
padding: 5px 10px;
}
> li:not(.selected) {
- svg { display: none; }
+ svg {
+ display: none;
+ }
}
svg {
diff --git a/frontend/resources/styles/main/partials/editable-label.scss b/frontend/resources/styles/main/partials/editable-label.scss
index 66e50ac07b..db2d42a897 100644
--- a/frontend/resources/styles/main/partials/editable-label.scss
+++ b/frontend/resources/styles/main/partials/editable-label.scss
@@ -1,30 +1,30 @@
.editable-label {
- display: flex;
+ display: flex;
- &.is-hidden {
- display: none;
- }
+ &.is-hidden {
+ display: none;
+ }
}
.editable-label-input {
- border: 0;
- height: 30px;
- padding: 5px;
- margin: 0;
- width: 100%;
- background-color: $color-white;
+ border: 0;
+ height: 30px;
+ padding: 5px;
+ margin: 0;
+ width: 100%;
+ background-color: $color-white;
}
.editable-label-close {
- background-color: $color-white;
- cursor: pointer;
- padding: 3px 5px;
+ background-color: $color-white;
+ cursor: pointer;
+ padding: 3px 5px;
- & svg {
- fill: $color-gray-30;
- height: 15px;
- transform: rotate(45deg) translateY(7px);
- width: 15px;
- margin: 0;
- }
+ & svg {
+ fill: $color-gray-30;
+ height: 15px;
+ transform: rotate(45deg) translateY(7px);
+ width: 15px;
+ margin: 0;
+ }
}
diff --git a/frontend/resources/styles/main/partials/exception-page.scss b/frontend/resources/styles/main/partials/exception-page.scss
index f0815818b1..59610ad657 100644
--- a/frontend/resources/styles/main/partials/exception-page.scss
+++ b/frontend/resources/styles/main/partials/exception-page.scss
@@ -20,7 +20,6 @@
height: 55px;
width: 170px;
}
-
}
.exception-content {
@@ -32,7 +31,6 @@
justify-content: center;
align-items: center;
-
.container {
max-width: 600px;
}
@@ -83,4 +81,3 @@
}
}
}
-
diff --git a/frontend/resources/styles/main/partials/forms.scss b/frontend/resources/styles/main/partials/forms.scss
index 2c780c91c3..d3f52b61ec 100644
--- a/frontend/resources/styles/main/partials/forms.scss
+++ b/frontend/resources/styles/main/partials/forms.scss
@@ -51,13 +51,13 @@ textarea {
h1 {
font-size: $fs36;
- color: #2C233E;
+ color: #2c233e;
margin-bottom: 20px;
}
.subtitle {
font-size: $fs24;
- color: #2C233E;
+ color: #2c233e;
margin-bottom: 20px;
}
@@ -261,7 +261,6 @@ textarea {
padding-top: 6px;
padding-bottom: 6px;
padding-left: 15px;
-
}
.input-container {
@@ -319,4 +318,3 @@ textarea {
}
}
}
-
diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss
index fab38c7af4..c666f03cd5 100644
--- a/frontend/resources/styles/main/partials/handoff.scss
+++ b/frontend/resources/styles/main/partials/handoff.scss
@@ -59,7 +59,7 @@
border-bottom: 1px solid $color-gray-60;
}
- & :last-child{
+ & :last-child {
border-bottom: none;
}
@@ -272,7 +272,6 @@
.typography-sample {
font-size: $fs16;
}
-
}
.download-button {
diff --git a/frontend/resources/styles/main/partials/left-toolbar.scss b/frontend/resources/styles/main/partials/left-toolbar.scss
index 1385bfc929..9b5cf562e8 100644
--- a/frontend/resources/styles/main/partials/left-toolbar.scss
+++ b/frontend/resources/styles/main/partials/left-toolbar.scss
@@ -57,7 +57,6 @@ $width-left-toolbar: 48px;
svg {
fill: $color-gray-50;
}
-
}
&.selected {
@@ -66,15 +65,10 @@ $width-left-toolbar: 48px;
svg {
fill: $color-primary;
}
-
}
-
}
&.panels {
margin-top: auto;
}
-
}
-
-
diff --git a/frontend/resources/styles/main/partials/loader.scss b/frontend/resources/styles/main/partials/loader.scss
index 210d0c8c37..f42675aaff 100644
--- a/frontend/resources/styles/main/partials/loader.scss
+++ b/frontend/resources/styles/main/partials/loader.scss
@@ -1,7 +1,7 @@
// full width BG
.loader-content {
align-items: center;
- background-color: rgba(255,255,255, .85);
+ background-color: rgba(255, 255, 255, 0.85);
display: flex;
height: 100vh;
justify-content: center;
@@ -31,12 +31,12 @@ svg#loader-icon {
animation: pen3 2s infinite ease;
}
-// btn prncil loader
+// btn pencil loader
svg#loader-pencil {
fill: $color-primary-darker;
width: 60px;
}
#loader-line {
- animation: linePencil .8s infinite linear;
+ animation: linePencil 0.8s infinite linear;
}
diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss
index ec14003a8d..d852fbd0ec 100644
--- a/frontend/resources/styles/main/partials/modal.scss
+++ b/frontend/resources/styles/main/partials/modal.scss
@@ -1,4 +1,5 @@
-.modal-wrapper {}
+.modal-wrapper {
+}
.modal-overlay {
align-items: center;
@@ -13,7 +14,7 @@
z-index: 1000;
&.transparent {
- background-color: rgba($color-white, 0)
+ background-color: rgba($color-white, 0);
}
}
@@ -134,7 +135,6 @@
margin-bottom: 0px;
}
}
-
}
.btn-disabled {
@@ -144,7 +144,7 @@
.change-email-modal {
h2 {
- font-size: $fs18
+ font-size: $fs18;
}
h3 {
@@ -230,7 +230,8 @@
font-size: $fs12;
}
- .detail, .explain {
+ .detail,
+ .explain {
padding: 0 1rem;
}
@@ -256,7 +257,7 @@
padding: 0.3rem 1.25rem;
&[disabled] {
- border: 1px solid #E3E3E3;
+ border: 1px solid #e3e3e3;
}
&:hover {
@@ -322,6 +323,10 @@
fill: $color-success;
}
+ .icon-msg-warning {
+ fill: $color-warning;
+ }
+
.icon-close {
transform: rotate(45deg);
fill: $color-danger;
@@ -334,19 +339,21 @@
color: $color-black;
.file-name-label {
- flex: 1;
- white-space: nowrap;
- display: flex;
align-items: center;
+ flex: 1;
height: 2rem;
margin-left: -0.25rem;
+ overflow: hidden;
padding-left: 0.25rem;
+ padding-top: 0.25rem;
+ text-overflow: ellipsis;
+ white-space: nowrap;
.icon-library {
width: 14px;
fill: $color-gray-20;
margin-left: 0.5rem;
- padding-top: 1px
+ padding-top: 1px;
}
}
@@ -390,6 +397,13 @@
fill: $color-white;
}
}
+
+ &.warning {
+ background: $color-warning-lighter;
+ .icon {
+ background: $color-warning;
+ }
+ }
}
.error-message {
@@ -411,7 +425,8 @@
flex-wrap: wrap;
margin-left: 2rem;
- .icon-chain, .icon-unchain {
+ .icon-chain,
+ .icon-unchain {
width: 10px;
height: 10px;
margin-right: 2px;
@@ -468,7 +483,8 @@
font-weight: 700;
}
- h3, p {
+ h3,
+ p {
font-size: $fs12;
line-height: 1.5;
margin: 0;
@@ -703,7 +719,7 @@
//- ONBOARDING
.onboarding {
background-color: $color-white;
- box-shadow: 0 10px 10px rgba(0,0,0,.2);
+ box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2);
display: flex;
min-height: 420px;
flex-direction: row;
@@ -837,19 +853,18 @@
align-items: center;
h1 {
- font-family: 'worksans', sans-serif;
+ font-family: "worksans", sans-serif;
font-weight: 700;
font-size: 27px;
margin-bottom: $size-3;
text-align: center;
}
p {
- font-family: 'worksans', sans-serif;
+ font-family: "worksans", sans-serif;
font-weight: 500;
font-size: $fs18;
text-align: center;
}
-
}
.modal-columns {
@@ -882,7 +897,7 @@
.modal-left {
background-repeat: no-repeat;
border-radius: $br-medium;
- transition: all ease .3s;
+ transition: all ease 0.3s;
&:hover {
background-color: $color-primary;
}
@@ -978,7 +993,8 @@
background-color: $color-white;
flex-grow: 1;
- p, h3 {
+ p,
+ h3 {
color: $color-gray-60;
text-align: center;
}
@@ -992,7 +1008,6 @@
font-size: $fs16;
}
-
.templates {
display: flex;
flex-direction: column;
@@ -1044,12 +1059,10 @@
justify-content: flex-end;
margin-top: $size-2;
}
-
}
}
}
-
.onboarding-team {
display: flex;
min-width: 620px;
@@ -1091,8 +1104,10 @@
margin-right: 13px;
}
- input { margin-bottom: unset; }
- input[type=submit] {
+ input {
+ margin-bottom: unset;
+ }
+ input[type="submit"] {
}
.btn-primary {
@@ -1134,19 +1149,17 @@
cursor: pointer;
}
}
-
}
}
-
-
.questions-form {
.modal-overlay {
z-index: 2001;
}
.modal-container {
- background-image: url("../images/deco-left.png"), url("../images/deco-right.png");
+ background-image: url("../images/deco-left.png"),
+ url("../images/deco-right.png");
background-repeat: no-repeat;
background-position: 10% 50px, 90% 50px;
background-size: 65px;
@@ -1157,13 +1170,13 @@
width: 100vw;
.af-form {
- --primary-color: #00C38B;
+ --primary-color: #00c38b;
--input-background-color: #ffffff;
--label-font-size: $fs16;
- --field-error-font-color: #E65244;
- --message-success-font-color: #49D793;
- --message-fail-font-color: #E65244;
- --invalid-field-border-color: #E65244;
+ --field-error-font-color: #e65244;
+ --message-success-font-color: #49d793;
+ --message-fail-font-color: #e65244;
+ --invalid-field-border-color: #e65244;
--dropdown-background-color: #ffffff;
--primary-font-color: #000;
--input-border-color: rgb(224, 230, 240);
@@ -1171,15 +1184,25 @@
--button-border-radius: 3px;
--message-border-radius: 3px;
--checkbox-border-radius: 3px;
- --dropdown-option-background-color: rgba(0,195,139,1);
- --dropdown-option-active-background-color: rgba(0,138,98,1);
- --invalid-field-background-color: rgba(238.51780000000002,205.7178,204.11780000000002,1);
- --message-fail-background-color: rgba(238.51780000000002,205.7178,204.11780000000002,1);
- --message-success-background-color: rgba(171,232,197,1);
+ --dropdown-option-background-color: rgba(0, 195, 139, 1);
+ --dropdown-option-active-background-color: rgba(0, 138, 98, 1);
+ --invalid-field-background-color: rgba(
+ 238.51780000000002,
+ 205.7178,
+ 204.11780000000002,
+ 1
+ );
+ --message-fail-background-color: rgba(
+ 238.51780000000002,
+ 205.7178,
+ 204.11780000000002,
+ 1
+ );
+ --message-success-background-color: rgba(171, 232, 197, 1);
}
}
.modal-overlay {
- background-color: rgba(0,0,0,0.9);
+ background-color: rgba(0, 0, 0, 0.9);
}
}
diff --git a/frontend/resources/styles/main/partials/project-bar.scss b/frontend/resources/styles/main/partials/project-bar.scss
index 908dc03f96..e59d43c16b 100644
--- a/frontend/resources/styles/main/partials/project-bar.scss
+++ b/frontend/resources/styles/main/partials/project-bar.scss
@@ -16,7 +16,7 @@
z-index: 9;
&.toggle {
- left:-201px;
+ left: -201px;
}
.project-bar-inside {
@@ -37,13 +37,11 @@
.btn-primary,
.btn-warning {
font-size: $fs12;
- margin-bottom: .5rem;
+ margin-bottom: 0.5rem;
padding: 8px $size-2;
width: 90%;
}
-
}
-
}
.tree-view {
@@ -69,11 +67,9 @@
&:hover,
&.current {
-
span {
color: $color-primary;
}
-
}
.options {
@@ -92,11 +88,7 @@
&:hover {
fill: $color-gray-40;
}
-
}
-
}
-
}
-
}
diff --git a/frontend/resources/styles/main/partials/share-link.scss b/frontend/resources/styles/main/partials/share-link.scss
index bd59474e95..1dd9a5415a 100644
--- a/frontend/resources/styles/main/partials/share-link.scss
+++ b/frontend/resources/styles/main/partials/share-link.scss
@@ -29,7 +29,6 @@
font-size: $fs14;
margin-bottom: 16px;
-
}
.actions {
display: flex;
@@ -38,7 +37,6 @@
}
}
-
.modal-content {
padding: 26px;
@@ -60,7 +58,6 @@
}
}
-
.share-link-section {
margin-top: 12px;
label {
@@ -93,7 +90,8 @@
padding-left: 20px;
display: flex;
- > .input-checkbox, > .input-radio {
+ > .input-checkbox,
+ > .input-radio {
display: flex;
user-select: none;
diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss
index 7fdacb37ba..eed7e64b3e 100644
--- a/frontend/resources/styles/main/partials/sidebar-assets.scss
+++ b/frontend/resources/styles/main/partials/sidebar-assets.scss
@@ -217,11 +217,11 @@
& svg {
width: 0.7rem;
height: 0.7rem;
- fill: #F0F0F0;
+ fill: #f0f0f0;
}
&:hover svg {
- fill: $color-primary;
+ fill: $color-primary;
}
}
@@ -359,8 +359,7 @@
}
.enum-item:hover,
- .enum-item.selected,
- {
+ .enum-item.selected {
color: $color-primary;
}
}
@@ -405,7 +404,9 @@
border-color: $color-black;
background-color: $color-gray-60;
- .input-text, .input-select, .adv-typography-name {
+ .input-text,
+ .input-select,
+ .adv-typography-name {
background-color: $color-gray-60;
}
}
@@ -413,58 +414,57 @@
}
.modal-create-color {
- position: relative;
- background-color: $color-white;
- padding: 4rem;
- display: flex;
- flex-direction: column;
- align-items: center;
+ position: relative;
+ background-color: $color-white;
+ padding: 4rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
- & .sketch-picker, .chrome-picker {
- box-shadow: none !important;
- border: 1px solid $color-gray-10 !important;
- border-radius: 0 !important;
+ & .sketch-picker,
+ .chrome-picker {
+ box-shadow: none !important;
+ border: 1px solid $color-gray-10 !important;
+ border-radius: 0 !important;
- & input {
- background-color: $color-white;
- }
+ & input {
+ background-color: $color-white;
}
+ }
- & .close {
- position: absolute;
- right: 1rem;
- transform: rotate(45deg);
- top: 1rem;
+ & .close {
+ position: absolute;
+ right: 1rem;
+ transform: rotate(45deg);
+ top: 1rem;
- svg {
- fill: $color-black;
- height: 20px;
- width: 20px;
+ svg {
+ fill: $color-black;
+ height: 20px;
+ width: 20px;
- &:hover {
- fill: $color-danger;
- }
-
- }
+ &:hover {
+ fill: $color-danger;
+ }
}
+ }
- & .btn-primary {
- width: 10rem;
- padding: 0.5rem;
- margin-top: 1rem;
- }
+ & .btn-primary {
+ width: 10rem;
+ padding: 0.5rem;
+ margin-top: 1rem;
+ }
}
.modal-create-color-title {
- color: $color-black;
- font-size: 24px;
- font-weight: normal;
+ color: $color-black;
+ font-size: 24px;
+ font-weight: normal;
}
-
.libraries-wrapper {
- overflow: auto;
- display: flex;
- flex-direction: column;
- flex: 1;
+ overflow: auto;
+ display: flex;
+ flex-direction: column;
+ flex: 1;
}
diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss
index 27d8b07fbe..95dd6ef0c0 100644
--- a/frontend/resources/styles/main/partials/sidebar-element-options.scss
+++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss
@@ -8,6 +8,7 @@
display: flex;
flex-direction: column;
width: 100%;
+ height: 100%;
.element-icons {
background-color: $color-gray-60;
@@ -33,11 +34,9 @@
}
&:hover {
-
svg {
fill: $color-primary;
}
-
}
&.selected {
@@ -46,15 +45,12 @@
svg {
fill: $color-white;
}
-
}
&:last-child {
border: none;
}
-
}
-
}
.element-set {
@@ -84,14 +80,12 @@
width: 100%;
.list-icon {
-
svg {
fill: $color-gray-30;
height: 15px;
margin-right: $size-1;
width: 15px;
}
-
}
span {
@@ -110,7 +104,6 @@
margin-left: auto;
a {
-
svg {
fill: $color-gray-60;
height: 15px;
@@ -120,59 +113,43 @@
&:hover {
fill: $color-gray-20;
}
-
}
-
}
-
}
&:hover {
-
.list-icon {
-
svg {
fill: $color-primary;
}
-
}
span {
color: $color-primary;
}
-
}
&.selected {
-
.list-icon {
-
svg {
fill: $color-primary;
}
-
}
span {
color: $color-primary;
font-weight: bold;
}
-
}
-
}
&:hover {
-
.list-actions {
display: flex;
- @include animation(0s,.3s,fadeIn);
+ @include animation(0s, 0.3s, fadeIn);
}
-
}
-
}
-
}
.element-set-content {
@@ -271,7 +248,6 @@
&:hover {
fill: $color-primary;
}
-
}
&.selected {
@@ -324,7 +300,6 @@
&:hover {
border: 1px solid $color-gray-20;
}
-
}
.custom-select-dropdown {
@@ -425,8 +400,8 @@
}
input.input-text {
- border: none;
- background: none;
+ border: none;
+ background: none;
}
.input-select {
@@ -457,12 +432,11 @@
height: 100%;
display: flex;
align-items: center;
-
}
&.input-option {
height: 26px;
- border-bottom: 1px solid #64666A;
+ border-bottom: 1px solid #64666a;
width: 100%;
margin-left: 0.25rem;
@@ -487,7 +461,7 @@
}
}
-.element-set-content .grid-option-main {
+.element-set-content .grid-option-main {
.editable-select {
height: 2rem;
}
@@ -597,20 +571,15 @@
&:hover,
&.current {
-
svg {
fill: $color-primary;
}
-
}
&:last-child {
margin-right: 0;
}
-
}
-
-
}
.element-color-picker {
@@ -639,9 +608,7 @@
transition: none;
top: 30%;
}
-
}
-
}
.radius-options {
@@ -750,7 +717,8 @@
fill: $color-gray-20;
}
- &:hover svg, &.is-active svg {
+ &:hover svg,
+ &.is-active svg {
fill: $color-primary;
}
}
@@ -796,7 +764,6 @@
&:hover {
background: $color-gray-60;
-
.custom-select,
.editable-select,
input {
@@ -808,7 +775,7 @@
min-width: 4.75rem;
height: 2rem;
border-color: transparent;
- border-bottom: 1px solid #65666A;
+ border-bottom: 1px solid #65666a;
max-height: 30px;
&:hover {
@@ -897,7 +864,6 @@
}
}
-
.element-set-options-group {
display: flex;
padding: 3px;
@@ -905,7 +871,7 @@
border-radius: 4px;
&:hover {
- background: #1F1F1F;
+ background: #1f1f1f;
}
&.selected {
@@ -977,34 +943,34 @@
}
.shadow-options .color-row-wrap {
- margin-left: 6px;
- margin-top: 0.5rem;
+ margin-left: 6px;
+ margin-top: 0.5rem;
}
.element-set-actions-button {
- display: flex;
- min-width: 30px;
- min-height: 30px;
- justify-content: center;
- align-items: center;
- cursor: pointer;
- svg {
- width: 12px;
- height: 12px;
- fill: $color-gray-20;
- stroke: $color-gray-20;
- }
+ display: flex;
+ min-width: 30px;
+ min-height: 30px;
+ justify-content: center;
+ align-items: center;
+ cursor: pointer;
+ svg {
+ width: 12px;
+ height: 12px;
+ fill: $color-gray-20;
+ stroke: $color-gray-20;
+ }
- &:hover svg,
- &.active svg {
- fill: $color-primary;
- stroke: $color-primary;
- }
+ &:hover svg,
+ &.active svg {
+ fill: $color-primary;
+ stroke: $color-primary;
+ }
- &.actions-inside {
- position: absolute;
- right: 0;
- }
+ &.actions-inside {
+ position: absolute;
+ right: 0;
+ }
}
.element-set-label {
@@ -1014,8 +980,8 @@
}
.element-set-actions {
- display: flex;
- visibility: hidden;
+ display: flex;
+ visibility: hidden;
}
.row-flex-removable:hover .element-set-actions,
@@ -1028,44 +994,44 @@
}
.typography-entry {
- margin: 0.5rem 0.3rem;
+ margin: 0.5rem 0.3rem;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+
+ .typography-selection-wrapper {
display: flex;
flex-direction: row;
align-items: center;
+ flex: 1;
+ height: 100%;
- .typography-selection-wrapper {
- display: flex;
- flex-direction: row;
- align-items: center;
- flex: 1;
- height: 100%;
-
- &.is-selectable {
- cursor: pointer;
- }
+ &.is-selectable {
+ cursor: pointer;
}
+ }
- .typography-sample {
- font-size: 17px;
- color: $color-white;
- margin: 0 0.5rem;
+ .typography-sample {
+ font-size: 17px;
+ color: $color-white;
+ margin: 0 0.5rem;
- font-family: sourcesanspro;
- font-style: normal;
- font-weight: normal;
- }
+ font-family: sourcesanspro;
+ font-style: normal;
+ font-weight: normal;
+ }
- .typography-name {
- flex-grow: 1;
- font-size: 11px;
- margin-top: 4px;
- white-space: nowrap;
- }
+ .typography-name {
+ flex-grow: 1;
+ font-size: 11px;
+ margin-top: 4px;
+ white-space: nowrap;
+ }
- .element-set-actions-button svg {
- width: 10px;
- height: 10px;
- }
+ .element-set-actions-button svg {
+ width: 10px;
+ height: 10px;
+ }
}
.spacing-options {
@@ -1074,75 +1040,75 @@
}
.asset-section {
- .typography-entry {
- margin: 0.25rem 0;
- }
+ .typography-entry {
+ margin: 0.25rem 0;
+ }
- .element-set-content .font-option,
- .element-set-content .size-option {
- margin: 0.5rem 0;
- }
- .element-set-content .variant-option {
- margin-left: 0.5rem;
- }
+ .element-set-content .font-option,
+ .element-set-content .size-option {
+ margin: 0.5rem 0;
+ }
+ .element-set-content .variant-option {
+ margin-left: 0.5rem;
+ }
}
.row-flex input.adv-typography-name {
- font-size: 14px;
- color: $color-gray-10;
- width: 100%;
- max-width: none;
- margin: 0;
- background-color: #303236;
- border-top: none;
- border-left: none;
- border-right: none;
+ font-size: 14px;
+ color: $color-gray-10;
+ width: 100%;
+ max-width: none;
+ margin: 0;
+ background-color: #303236;
+ border-top: none;
+ border-left: none;
+ border-right: none;
}
.size-option .custom-select-dropdown {
- cursor: pointer;
- max-height: 16rem;
- min-width: 6rem;
- left: initial;
- top: 0;
+ cursor: pointer;
+ max-height: 16rem;
+ min-width: 6rem;
+ left: initial;
+ top: 0;
}
.typography-read-only-data {
- font-size: 12px;
- color: $color-white;
+ font-size: 12px;
+ color: $color-white;
- .typography-name {
- font-size: 14px;
+ .typography-name {
+ font-size: 14px;
+ }
+
+ .row-flex {
+ padding: 0.5rem 0;
+ }
+
+ .label {
+ color: $color-gray-30;
+
+ &::after {
+ content: ":";
+ margin-right: 0.25rem;
}
+ }
- .row-flex {
- padding: 0.5rem 0;
- }
-
- .label {
- color: $color-gray-30;
-
- &::after {
- content: ':';
- margin-right: 0.25rem;
- }
- }
-
- .go-to-lib-button {
- transition: border 0.3s, color 0.3s;
- text-align: center;
- background: $color-gray-50;
- padding: 0.5rem;
- border-radius: 2px;
- cursor: pointer;
- font-size: 14px;
- margin-top: 1rem;
-
- &:hover {
- background: $color-primary;
- color: $color-black;
- }
+ .go-to-lib-button {
+ transition: border 0.3s, color 0.3s;
+ text-align: center;
+ background: $color-gray-50;
+ padding: 0.5rem;
+ border-radius: 2px;
+ cursor: pointer;
+ font-size: 14px;
+ margin-top: 1rem;
+
+ &:hover {
+ background: $color-primary;
+ color: $color-black;
}
+ }
}
.multiple-typography {
@@ -1248,7 +1214,6 @@
}
}
}
-
}
input {
@@ -1274,7 +1239,7 @@
svg {
width: 16px;
height: 16px;
- fill: $color-gray-20
+ fill: $color-gray-20;
}
&.active {
@@ -1314,7 +1279,9 @@
background-color: $color-black;
color: $color-primary;
- .icon svg {fill: $color-primary;}
+ .icon svg {
+ fill: $color-primary;
+ }
}
&:hover {
@@ -1327,7 +1294,7 @@
// justify-content: center;
align-items: center;
// border: 1px solid red;
- width: $size-5
+ width: $size-5;
}
.label {
@@ -1370,7 +1337,7 @@
align-items: center;
&::after {
- content: ' ';
+ content: " ";
background-color: $color-gray-30;
}
diff --git a/frontend/resources/styles/main/partials/sidebar-icons.scss b/frontend/resources/styles/main/partials/sidebar-icons.scss
index 8af2365f65..524064f825 100644
--- a/frontend/resources/styles/main/partials/sidebar-icons.scss
+++ b/frontend/resources/styles/main/partials/sidebar-icons.scss
@@ -14,7 +14,6 @@
width: 100%;
margin-bottom: 0;
}
-
}
.figure-btn {
@@ -47,7 +46,5 @@
svg {
fill: $color-white;
}
-
}
-
}
diff --git a/frontend/resources/styles/main/partials/sidebar-interactions.scss b/frontend/resources/styles/main/partials/sidebar-interactions.scss
index 568dd76e12..7a1dc3d93e 100644
--- a/frontend/resources/styles/main/partials/sidebar-interactions.scss
+++ b/frontend/resources/styles/main/partials/sidebar-interactions.scss
@@ -80,6 +80,63 @@
}
}
+.interactions-way-buttons {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+
+ & .input-radio {
+ margin-bottom: 0;
+
+ & label {
+ color: $color-gray-20;
+
+ &:before {
+ background-color: unset;
+ }
+ }
+
+ & input[type="radio"]:checked {
+ & + label {
+ &:before {
+ background-color: $color-primary;
+ box-shadow: inset 0 0 0 5px $color-gray-50;
+ }
+ }
+ }
+ }
+}
+
+.interactions-direction-buttons {
+ margin-top: $size-2;
+ padding-top: $size-2;
+ padding-bottom: $size-2;
+ justify-content: space-around;
+
+ .element-set-actions-button {
+ min-width: 40px;
+ min-height: 13px;
+ }
+
+ svg {
+ height: 13px;
+ width: 13px;
+ }
+}
+
+.interactions-easing-icon {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ min-width: 30px;
+ min-height: 30px;
+
+ & svg {
+ width: 12px;
+ height: 12px;
+ stroke: $color-gray-20;
+ }
+}
+
.flow-element {
display: flex;
align-items: center;
@@ -155,4 +212,3 @@
}
}
}
-
diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss
index 19442c608d..ce99e46335 100644
--- a/frontend/resources/styles/main/partials/sidebar-layers.scss
+++ b/frontend/resources/styles/main/partials/sidebar-layers.scss
@@ -42,7 +42,6 @@
.element-icon,
.element-actions {
-
svg {
fill: $color-gray-60;
}
@@ -110,7 +109,6 @@
}
.element-list li.component {
-
.element-list-body {
span.element-name {
color: $color-component;
@@ -228,9 +226,9 @@ span.element-name {
&.selected {
display: flex;
- svg {
- fill: $color-gray-20;
- }
+ svg {
+ fill: $color-gray-20;
+ }
}
}
}
@@ -254,7 +252,9 @@ span.element-name {
}
&.inverse {
- svg { transform: rotate(270deg); }
+ svg {
+ transform: rotate(270deg);
+ }
}
&:hover {
@@ -265,8 +265,8 @@ span.element-name {
}
.icon-layer {
- > svg {
- background-color: rgba(255,255,255,.6);
+ > svg {
+ background-color: rgba(255, 255, 255, 0.6);
border-radius: $br-small;
flex-shrink: 0;
fill: $color-black !important;
diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss
index 57d82086d2..3a14879f97 100644
--- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss
+++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss
@@ -9,7 +9,6 @@
flex: none !important;
.element-list {
-
li {
align-items: center;
display: flex;
@@ -17,14 +16,12 @@
width: 100%;
.page-icon {
-
svg {
fill: $color-gray-30;
height: 13px;
margin-right: $size-1;
width: 13px;
}
-
}
span {
@@ -42,23 +39,19 @@
margin-left: auto;
a {
-
svg {
fill: $color-gray-60;
height: 15px;
margin-left: $size-1;
width: 15px;
}
-
}
-
}
&:hover {
background-color: $color-primary;
.page-icon {
-
svg {
fill: $color-gray-60;
}
@@ -66,35 +59,27 @@
span {
color: $color-gray-60;
}
-
}
.page-actions {
display: flex;
}
-
}
&.selected {
-
.page-icon {
-
svg {
fill: $color-primary;
}
-
}
span {
color: $color-primary;
}
-
}
&:hover {
-
.page-icon {
-
svg {
fill: $color-gray-60;
}
@@ -104,7 +89,6 @@
color: $color-gray-60;
}
}
-
}
.element-list-body {
diff --git a/frontend/resources/styles/main/partials/sidebar-tools.scss b/frontend/resources/styles/main/partials/sidebar-tools.scss
index 2254f240b8..c2c881790c 100644
--- a/frontend/resources/styles/main/partials/sidebar-tools.scss
+++ b/frontend/resources/styles/main/partials/sidebar-tools.scss
@@ -37,7 +37,5 @@
svg {
fill: $color-white;
}
-
}
-
}
diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss
index 17a3ef6fb4..1de3a52642 100644
--- a/frontend/resources/styles/main/partials/sidebar.scss
+++ b/frontend/resources/styles/main/partials/sidebar.scss
@@ -6,7 +6,7 @@
// Copyright (c) 2015-2020 Juan de la Cruz
$width-settings-bar: 16rem;
- // This width is also used in update-viewport-size at frontend/src/app/main/data/workspace.cljs
+// This width is also used in update-viewport-size at frontend/src/app/main/data/workspace.cljs
.settings-bar {
background-color: $color-gray-50;
@@ -35,35 +35,34 @@ $width-settings-bar: 16rem;
display: grid;
grid-template-columns: 100%;
- &[data-layout*='sitemap-pages'] {
- grid-template-rows: auto;
+ &[data-layout*="sitemap-pages"] {
+ grid-template-rows: auto;
}
- &[data-layout*='layers'] {
- grid-template-rows: auto 1fr;
+ &[data-layout*="layers"] {
+ grid-template-rows: auto 1fr;
}
- &[data-layout*='libraries'] {
- grid-template-rows: auto 1fr;
+ &[data-layout*="libraries"] {
+ grid-template-rows: auto 1fr;
}
- &[data-layout*='layers'][data-layout*='sitemap-pages'] {
- grid-template-rows: 11.5rem 1fr;
+ &[data-layout*="layers"][data-layout*="sitemap-pages"] {
+ grid-template-rows: 11.5rem 1fr;
}
- &[data-layout*='libraries'][data-layout*='sitemap-pages'] {
- grid-template-rows: 11.5rem 1fr;
+ &[data-layout*="libraries"][data-layout*="sitemap-pages"] {
+ grid-template-rows: 11.5rem 1fr;
}
- &[data-layout*='layers'][data-layout*='libraries'] {
- grid-template-rows: auto 30% 1fr;
+ &[data-layout*="layers"][data-layout*="libraries"] {
+ grid-template-rows: auto 30% 1fr;
}
- &[data-layout*='layers'][data-layout*='libraries'][data-layout*='sitemap-pages'] {
- grid-template-rows: 11.5rem 25% 1fr;
+ &[data-layout*="layers"][data-layout*="libraries"][data-layout*="sitemap-pages"] {
+ grid-template-rows: 11.5rem 25% 1fr;
}
-
flex-direction: column;
padding-top: 48px;
height: 100%;
@@ -151,20 +150,17 @@ $width-settings-bar: 16rem;
transform: rotate(45deg);
&:hover {
-
svg {
fill: $color-danger;
}
-
}
-
}
}
}
.assets-bar .tool-window {
- flex: none;
- height: auto;
+ flex: none;
+ height: auto;
}
}
}
@@ -199,11 +195,8 @@ $width-settings-bar: 16rem;
padding-bottom: 1px;
&.open {
-
ul {
-
li {
-
.element-list-body {
border-style: dashed;
}
diff --git a/frontend/resources/styles/main/partials/tab-container.scss b/frontend/resources/styles/main/partials/tab-container.scss
index 1fd32a5570..76417795ae 100644
--- a/frontend/resources/styles/main/partials/tab-container.scss
+++ b/frontend/resources/styles/main/partials/tab-container.scss
@@ -1,44 +1,44 @@
-
.tab-container {
- display: flex;
- flex-direction: column;
- height: 100%;
- width: 100%;
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ width: 100%;
}
.tab-container-tabs {
- background: $color-gray-60;
- cursor: pointer;
- display: flex;
- flex-direction: row;
- font-size: 12px;
- height: 2.5rem;
- padding: 0 0.25rem;
+ background: $color-gray-60;
+ cursor: pointer;
+ display: flex;
+ flex-direction: row;
+ font-size: 12px;
+ height: 2.5rem;
+ padding: 0 0.25rem;
}
.tab-container-tab-title {
- align-items: center;
- background: $color-gray-60;
- border-radius: 2px 2px 0px 0px;
- color: $color-white;
- display: flex;
- justify-content: center;
- margin: 0.5rem 0.25rem 0 0.25rem ;
- width: 100%;
+ align-items: center;
+ background: $color-gray-60;
+ border-radius: 2px 2px 0px 0px;
+ color: $color-white;
+ display: flex;
+ justify-content: center;
+ margin: 0.5rem 0.25rem 0 0.25rem;
+ width: 100%;
- &.current{
- background: $color-gray-50;
- }
+ &.current {
+ background: $color-gray-50;
+ }
}
.tab-container-content {
- flex: 1;
- height: 100%;
- max-height: 100%;
- overflow-x: hidden;
- overflow-y: auto;
+ flex: 1;
+ height: 100%;
+ max-height: 100%;
+ overflow-x: hidden;
+ overflow-y: auto;
}
-.tab-element, .tab-element-content {
- height: 100%;
+.tab-element,
+.tab-element-content {
+ height: 100%;
}
diff --git a/frontend/resources/styles/main/partials/texts.scss b/frontend/resources/styles/main/partials/texts.scss
index a67fa3ea69..4b4a2f711d 100644
--- a/frontend/resources/styles/main/partials/texts.scss
+++ b/frontend/resources/styles/main/partials/texts.scss
@@ -1,5 +1,6 @@
foreignObject {
- .text-editor, .rich-text {
+ .text-editor,
+ .rich-text {
color: $color-black;
height: 100%;
white-space: pre-wrap;
@@ -59,4 +60,3 @@ foreignObject {
}
}
}
-
diff --git a/frontend/resources/styles/main/partials/tool-bar.scss b/frontend/resources/styles/main/partials/tool-bar.scss
index a95de86065..e502d21685 100644
--- a/frontend/resources/styles/main/partials/tool-bar.scss
+++ b/frontend/resources/styles/main/partials/tool-bar.scss
@@ -33,25 +33,17 @@
}
&:hover {
-
svg {
fill: $color-white;
}
-
}
&.current {
-
svg {
fill: $color-primary;
}
-
}
-
}
-
}
-
}
-
}
diff --git a/frontend/resources/styles/main/partials/user-settings.scss b/frontend/resources/styles/main/partials/user-settings.scss
index 1927291db5..f7cdbfb299 100644
--- a/frontend/resources/styles/main/partials/user-settings.scss
+++ b/frontend/resources/styles/main/partials/user-settings.scss
@@ -93,7 +93,6 @@
}
}
-
.settings-profile {
.forms-container {
margin-top: 80px;
@@ -126,7 +125,7 @@
z-index: 14;
}
- input[type=file] {
+ input[type="file"] {
width: 120px;
height: 120px;
position: absolute;
@@ -137,8 +136,12 @@
}
&:hover {
- img {display: none;}
- .update-overlay {opacity: 1};
+ img {
+ display: none;
+ }
+ .update-overlay {
+ opacity: 1;
+ }
}
}
}
diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss
index 3a2b6a6845..378019f3ce 100644
--- a/frontend/resources/styles/main/partials/viewer-header.scss
+++ b/frontend/resources/styles/main/partials/viewer-header.scss
@@ -1,8 +1,9 @@
.viewer-header {
align-items: center;
background-color: $color-gray-50;
- border-bottom: 1px solid $color-gray-60;
- display: flex;
+ border-bottom: 1px solid $color-gray-60;
+ display: grid;
+ grid-template-columns: 1fr auto 1fr;
height: 48px;
padding: 0 $size-4 0 55px;
position: relative;
@@ -12,6 +13,10 @@
font-size: $fs12;
}
+ .nav-zone {
+ justify-content: flex-start;
+ }
+
.main-icon {
align-items: center;
background-color: $color-gray-60;
@@ -19,8 +24,8 @@
display: flex;
height: 100%;
justify-content: center;
- left: 0;
position: absolute;
+ left: 0;
top: 0;
width: 48px;
@@ -44,7 +49,6 @@
.options-zone {
align-items: center;
display: flex;
- // width: 384px;
justify-content: flex-end;
position: relative;
@@ -115,7 +119,8 @@
}
}
- .breadcrumb, .current-frame {
+ .breadcrumb,
+ .current-frame {
display: flex;
position: relative;
diff --git a/frontend/resources/styles/main/partials/viewer-thumbnails.scss b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
index d37c05bfd5..29c7441c9a 100644
--- a/frontend/resources/styles/main/partials/viewer-thumbnails.scss
+++ b/frontend/resources/styles/main/partials/viewer-thumbnails.scss
@@ -1,6 +1,6 @@
.viewer-thumbnails {
grid-row: 1 / span 1;
- grid-column: 1 / span 1;
+ grid-column: 1 / span 1;
background-color: $color-gray-50;
overflow: hidden;
diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss
index 34e2c5779c..4f9f13fe14 100644
--- a/frontend/resources/styles/main/partials/viewer.scss
+++ b/frontend/resources/styles/main/partials/viewer.scss
@@ -8,23 +8,43 @@
.viewer-section {
height: calc(100vh - 48px);
-
+ &.fullscreen {
+ height: 100vh;
+ }
grid-row: 1 / span 2;
grid-column: 1 / span 1;
- overflow: auto;
-
display: flex;
justify-content: center;
align-items: center;
flex-flow: wrap;
- .empty-state {
- justify-content: center;
- align-items: center;
+ overflow: auto;
+
+ & .viewer-wrapper {
+ position: relative;
}
- svg {
- transform-origin: center;
+ & .viewer-clipper {
+ display: grid;
+ grid-template-rows: 1fr;
+ grid-template-columns: 1fr;
+ justify-items: center;
+ align-items: center;
+ overflow: hidden;
+
+ .empty-state {
+ justify-content: center;
+ align-items: center;
+ }
+
+ svg {
+ transform-origin: center;
+ }
}
}
+
+.viewport-container {
+ grid-column: 1 / 1;
+ grid-row: 1 / 1;
+}
diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss
index 2b3034974f..1e0c48f417 100644
--- a/frontend/resources/styles/main/partials/workspace-header.scss
+++ b/frontend/resources/styles/main/partials/workspace-header.scss
@@ -7,7 +7,7 @@
.workspace-header {
align-items: center;
background-color: $color-gray-50;
- border-bottom: 1px solid $color-gray-60;
+ border-bottom: 1px solid $color-gray-60;
display: flex;
height: 48px;
padding: $size-1 $size-4 $size-1 55px;
@@ -35,17 +35,13 @@
fill: $color-gray-30;
height: 30px;
width: 28px;
-
}
&:hover {
-
svg {
fill: $color-primary;
}
-
}
-
}
}
@@ -116,19 +112,19 @@
cursor: pointer;
&:hover {
- color: $color-primary;
+ color: $color-primary;
}
}
}
.file-name {
- margin: 0;
- padding: 0;
- border: none;
- border-bottom: 1px solid $color-gray-10;
- background: $color-gray-50;
- color: $color-gray-10;
- margin-bottom: -1px;
+ margin: 0;
+ padding: 0;
+ border: none;
+ border-bottom: 1px solid $color-gray-10;
+ background: $color-gray-50;
+ color: $color-gray-10;
+ margin-bottom: -1px;
}
}
@@ -138,7 +134,7 @@
left: 40px;
width: 270px;
z-index: 12;
- @include animation(0,.2s,fadeInDown);
+ @include animation(0, 0.2s, fadeInDown);
background-color: $color-white;
border-radius: $br-small;
@@ -208,13 +204,21 @@
display: flex;
&.error {
- .label { color: $color-danger; }
- .icon svg { fill: $color-danger; }
+ .label {
+ color: $color-danger;
+ }
+ .icon svg {
+ fill: $color-danger;
+ }
}
&.pending {
- .label { color: $color-warning; }
- .icon svg { fill: $color-warning; }
+ .label {
+ color: $color-warning;
+ }
+ .icon svg {
+ fill: $color-warning;
+ }
}
}
diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss
index f6d7816d0f..561be4c629 100644
--- a/frontend/resources/styles/main/partials/workspace.scss
+++ b/frontend/resources/styles/main/partials/workspace.scss
@@ -18,7 +18,6 @@
top: 40px;
width: 240px;
z-index: 12;
- padding: $size-1 0;
li {
align-items: center;
@@ -57,7 +56,6 @@
height: 10px;
}
}
-
}
}
@@ -156,11 +154,6 @@
overflow: hidden;
position: relative;
- svg {
- widht: 100%;
- height: 100%;
- }
-
.viewport-overlays {
position: absolute;
width: 100%;
@@ -187,10 +180,10 @@
.viewport-controls {
position: absolute;
}
-
}
- .page-canvas, .page-layout {
+ .page-canvas,
+ .page-layout {
overflow: visible;
}
@@ -230,7 +223,6 @@
stroke: $color-gray-20;
}
}
-
}
.workspace-frame-label {
@@ -239,27 +231,20 @@
}
.multiuser-cursor {
- align-items: center;
- display: flex;
- left: 0;
- position: absolute;
- top: 0;
z-index: 10000;
+ pointer-events: none;
+}
- svg {
- height: 15px;
- fill: #f3dd14;
- width: 15px;
- }
-
- span {
- background-color: #f3dd14;
- border-radius: $br-small;
- color: $color-black;
- font-size: $fs12;
- margin-left: $size-2;
- padding: $size-1;
- }
+.profile-name {
+ width: fit-content;
+ font-family: worksans;
+ padding: 2px 12px;
+ border-radius: 4px;
+ display: flex;
+ align-items: center;
+ height: 20px;
+ font-size: 12px;
+ line-height: 1.5;
}
.viewport-actions {
@@ -348,4 +333,3 @@
margin-right: 0;
}
}
-
diff --git a/frontend/resources/styles/main/partials/zoom-widget.scss b/frontend/resources/styles/main/partials/zoom-widget.scss
index 7aa2a646ae..c6ea675ba0 100644
--- a/frontend/resources/styles/main/partials/zoom-widget.scss
+++ b/frontend/resources/styles/main/partials/zoom-widget.scss
@@ -33,16 +33,62 @@
display: flex;
padding: $size-2;
+ &.separator {
+ border-top: 1px solid $color-gray-10;
+ padding: 0px;
+ margin: 2px;
+ height: 0;
+ }
+
+ &.basic-zoom-bar {
+ cursor: auto;
+ display: flex;
+ justify-content: space-between;
+
+ &:hover {
+ background-color: $color-white;
+ }
+ }
+
span {
color: $color-gray-20;
font-size: $fs14;
margin-left: auto;
+
+ &.zoom-btns {
+ display: flex;
+ margin-left: 2px;
+ color: $color-gray-60;
+
+ p {
+ margin-bottom: 0;
+ font-size: $fs14;
+ padding: 0 3px;
+ }
+ }
}
&:hover {
background-color: $color-primary-lighter;
}
+ button {
+ cursor: pointer;
+ background-color: $color-white;
+ border: none;
+ &:hover {
+ color: $color-primary;
+ }
+ }
+ .reset-btn {
+ color: $color-primary-dark;
+ }
+ .zoom-size {
+ min-width: 40px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ }
}
}
}
diff --git a/frontend/scripts/convert-to-po.js b/frontend/scripts/convert-to-po.js
deleted file mode 100644
index ce94089c86..0000000000
--- a/frontend/scripts/convert-to-po.js
+++ /dev/null
@@ -1,73 +0,0 @@
-const l = require("lodash");
-const fs = require("fs");
-const gt = require("gettext-parser");
-
-function generateLang(data, lang) {
- let output = {};
-
- for (let key of Object.keys(data)) {
- const trObj = data[key];
- const trRef = trObj["used-in"];
-
- let content = trObj.translations[lang];
- let comments = {};
-
- if (l.isNil(content)) {
- continue;
- } else {
- let result = {
- msgid: key,
- comments: {}
- }
-
- if (l.isArray(trRef)) {
- result.comments.reference = trRef.join(", ");
- }
-
- if (trObj.permanent) {
- result.comments.flag = "permanent";
- }
-
- if (l.isArray(content)) {
- result.msgid_plural = key;
- result.msgstr = content;
- } else if (l.isString(content)) {
- result.msgstr = [content];
- } else {
- throw new Error("unexpected");
- }
-
- output[key] = result;
- }
- }
-
- if (lang.includes("_")) {
- const [a, b] = lang.split("_");
- lang = `${a}_${b.toUpperCase()}`;
- }
-
- const poData = {
- charset: "utf-8",
- headers: {
- "Language": lang,
- "MIME-Version": "1.0",
- "Content-Type": "text/plain; charset=UTF-8",
- "Content-Transfer-Encoding": "8bit",
- "Plural-Forms": "nplurals=2; plural=(n != 1);"
- },
- "translations": {
- "": output
- }
- }
- const buff = gt.po.compile(poData, {sort: true});
- fs.writeFileSync(`./translations/${lang}.po`, buff);
-}
-
-const content = fs.readFileSync("./resources/locales.json");
-const data = JSON.parse(content);
-const langs = ["de"];
-
-for (let lang of langs) {
- generateLang(data, lang);
-}
-
diff --git a/frontend/scripts/jvm-repl b/frontend/scripts/jvm-repl
new file mode 100755
index 0000000000..6ca8b7d1b3
--- /dev/null
+++ b/frontend/scripts/jvm-repl
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+# A repl usefull for debug macros.
+
+export OPTIONS="\
+ -J-XX:-OmitStackTraceInFastThrow \
+ -J-Xms50m -J-Xmx512m \
+ -M:dev:jvm-repl";
+
+set -ex;
+exec clojure $OPTIONS;
diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn
index 8d3bc58e09..4bab9ad72c 100644
--- a/frontend/shadow-cljs.edn
+++ b/frontend/shadow-cljs.edn
@@ -32,6 +32,7 @@
goog.debug.LOGGING_ENABLED true}
:compiler-options
{:fn-invoke-direct true
+ :optimizations #shadow/env ["PENPOT_BUILD_OPTIMIZATIONS" :as :keyword :default :advanced]
:source-map true
:elide-asserts true
:anon-fn-naming-policy :off
@@ -61,10 +62,17 @@
:test
{:target :node-test
:output-to "target/tests.js"
+ :output-dir "target/test/"
:ns-regexp "^app.*-test$"
- ;; :autorun true
+ :autorun true
:compiler-options
{:output-feature-set :es8
:output-wrapper false
- :warnings {:fn-deprecated false}}}}}
+ :source-map true
+ :source-map-include-sources-content true
+ :source-map-detail-level :all
+ :warnings {:fn-deprecated false}}}
+
+ }}
+
diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs
index 975688c3af..12ec5aa4a7 100644
--- a/frontend/src/app/config.cljs
+++ b/frontend/src/app/config.cljs
@@ -86,7 +86,7 @@
(def browser (atom (parse-browser)))
(def platform (atom (parse-platform)))
-;; mantain for backward compatibility
+;; maintain for backward compatibility
(let [login-with-ldap (obj/get global "penpotLoginWithLDAP" false)
registration (obj/get global "penpotRegistrationEnabled" true)]
(when login-with-ldap
diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs
index 04b6a00f41..50e0f18db2 100644
--- a/frontend/src/app/main.cljs
+++ b/frontend/src/app/main.cljs
@@ -18,11 +18,14 @@
[app.main.ui.confirm]
[app.main.ui.modal :refer [modal]]
[app.main.ui.routes :as rt]
- [app.main.worker]
+ [app.main.worker :as worker]
[app.util.dom :as dom]
+ [app.util.globals :as glob]
[app.util.i18n :as i18n]
[app.util.theme :as theme]
[beicon.core :as rx]
+ [cuerdas.core :as str]
+ [debug]
[potok.core :as ptk]
[rumext.alpha :as mf]))
@@ -57,11 +60,18 @@
(rx/take 1)
(rx/map #(rt/init-routes)))))))
+(def essential-only?
+ (let [href (.-href ^js glob/location)]
+ (str/includes? href "essential=t")))
+
(defn ^:export init
[]
- (sentry/init!)
- (i18n/init! cf/translations)
- (theme/init! cf/themes)
+ (when-not essential-only?
+ (worker/init!)
+ (sentry/init!)
+ (i18n/init! cf/translations)
+ (theme/init! cf/themes))
+
(init-ui)
(st/emit! (initialize)))
diff --git a/frontend/src/app/main/constants.cljs b/frontend/src/app/main/constants.cljs
index 86c922f7f2..8247b92962 100644
--- a/frontend/src/app/main/constants.cljs
+++ b/frontend/src/app/main/constants.cljs
@@ -19,14 +19,7 @@
"Default data for page metadata."
{:grid-x-axis grid-x-axis
:grid-y-axis grid-y-axis
- :grid-color "#cccccc"
+ :grid-color "var(--color-gray-20)"
:grid-alignment true
- :background "#ffffff"})
+ :background "var(--color-white)"})
-(def zoom-levels
- [0.01 0.03 0.05 0.07 0.09 0.10 0.11 0.13 0.15 0.18
- 0.20 0.21 0.22 0.23 0.24 0.25 0.27 0.28 0.30 0.32 0.34
- 0.36 0.38 0.40 0.42 0.44 0.46 0.48 0.50 0.54 0.57 0.60
- 0.63 0.66 0.69 0.73 0.77 0.81 0.85 0.90 0.95 1.00 1.05
- 1.10 1.15 1.21 1.27 1.33 1.40 1.47 1.54 1.62 1.70 1.78
- 1.87 1.96 2.00 2.16 2.27 2.38 2.50 2.62 2.75 2.88 3.00])
diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs
index 6f728bbe2d..c986116f83 100644
--- a/frontend/src/app/main/data/dashboard.cljs
+++ b/frontend/src/app/main/data/dashboard.cljs
@@ -792,3 +792,46 @@
(watch [_ state _]
(let [team-id (:current-team-id state)]
(rx/of (rt/nav :dashboard-team-settings {:team-id team-id}))))))
+
+(defn go-to-drafts
+ []
+ (ptk/reify ::go-to-drafts
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [team-id (:current-team-id state)
+ projects (:dashboard-projects state)
+ default-project (d/seek :is-default (vals projects))]
+ (when default-project
+ (rx/of (rt/nav :dashboard-files {:team-id team-id
+ :project-id (:id default-project)})))))))
+
+(defn go-to-libs
+ []
+ (ptk/reify ::go-to-libs
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [team-id (:current-team-id state)]
+ (rx/of (rt/nav :dashboard-libraries {:team-id team-id}))))))
+
+(defn create-element
+ []
+ (ptk/reify ::create-element
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [team-id (:current-team-id state)
+ route (:route state)
+ pparams (:path-params route)
+ in-project? (contains? pparams :project-id)
+ name (if in-project?
+ (name (gensym (str (tr "dashboard.new-file-prefix") " ")))
+ (name (gensym (str (tr "dashboard.new-project-prefix") " "))))
+ params (if in-project?
+ {:project-id (:project-id pparams)
+ :name name}
+ {:name name
+ :team-id team-id})
+ action-name (if in-project? :create-file :create-project)
+ action (if in-project? file-created project-created)]
+
+ (->> (rp/mutation! action-name params)
+ (rx/map action))))))
\ No newline at end of file
diff --git a/frontend/src/app/main/data/dashboard/shortcuts.cljs b/frontend/src/app/main/data/dashboard/shortcuts.cljs
new file mode 100644
index 0000000000..983c89ae90
--- /dev/null
+++ b/frontend/src/app/main/data/dashboard/shortcuts.cljs
@@ -0,0 +1,32 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.dashboard.shortcuts
+ (:require
+ [app.main.data.dashboard :as dd]
+ [app.main.data.shortcuts :as ds]
+ [app.main.store :as st]))
+
+(def shortcuts
+ {:go-to-search {:tooltip (ds/meta "F")
+ :command (ds/c-mod "f")
+ :fn (st/emitf (dd/go-to-search))}
+
+ :go-to-drafts {:tooltip "G D"
+ :command "g d"
+ :fn (st/emitf (dd/go-to-drafts))}
+
+ :go-to-libs {:tooltip "G L"
+ :command "g l"
+ :fn (st/emitf (dd/go-to-libs))}
+
+ :create-new-project {:tooltip "+"
+ :command "+"
+ :fn (st/emitf (dd/create-element))}})
+
+(defn get-tooltip [shortcut]
+ (assert (contains? shortcuts shortcut) (str shortcut))
+ (get-in shortcuts [shortcut :tooltip]))
diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs
index 271c133a43..fcf19fc542 100644
--- a/frontend/src/app/main/data/users.cljs
+++ b/frontend/src/app/main/data/users.cljs
@@ -81,8 +81,6 @@
(when-not (contains? ids ctid)
(swap! storage dissoc ::current-team-id)))))))
-
-
(defn fetch-teams
[]
(ptk/reify ::fetch-teams
@@ -160,7 +158,6 @@
(letfn [(get-redirect-event []
(let [team-id (:default-team-id profile)]
(rt/nav' :dashboard-projects {:team-id team-id})))]
-
(ptk/reify ::logged-in
IDeref
(-deref [_] profile)
@@ -237,7 +234,7 @@
(defn login-from-register
"Event used mainly for mark current session as logged-in in after the
- user sucessfully registred using third party auth provider (in this
+ user successfully registered using third party auth provider (in this
case we dont need to verify the email)."
[]
(ptk/reify ::login-from-register
@@ -305,7 +302,11 @@
on-success identity}} (meta data)]
(->> (rp/mutation :register-profile data)
(rx/tap on-success)
- (rx/catch on-error))))))
+ (rx/catch on-error))))
+
+ ptk/EffectEvent
+ (effect [_ _ _]
+ (swap! storage dissoc ::redirect-to))))
;; --- Update Profile
@@ -383,7 +384,7 @@
(defn mark-onboarding-as-viewed
([] (mark-onboarding-as-viewed nil))
([{:keys [version]}]
- (ptk/reify ::mark-oboarding-as-viewed
+ (ptk/reify ::mark-onboarding-as-viewed
ptk/WatchEvent
(watch [_ _ _]
(let [version (or version (:main @cf/version))
diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs
index 56ee0c0dfb..6ff2212393 100644
--- a/frontend/src/app/main/data/viewer.cljs
+++ b/frontend/src/app/main/data/viewer.cljs
@@ -9,8 +9,8 @@
[app.common.data :as d]
[app.common.pages :as cp]
[app.common.spec :as us]
+ [app.common.types.interactions :as cti]
[app.common.uuid :as uuid]
- [app.main.constants :as c]
[app.main.data.comments :as dcm]
[app.main.data.fonts :as df]
[app.main.repo :as rp]
@@ -25,6 +25,7 @@
(def ^:private
default-local-state
{:zoom 1
+ :fullscreen? false
:interactions-mode :hide
:interactions-show? false
:comments-mode :all
@@ -188,18 +189,14 @@
(ptk/reify ::increase-zoom
ptk/UpdateEvent
(update [_ state]
- (let [increase #(nth c/zoom-levels
- (+ (d/index-of c/zoom-levels %) 1)
- (last c/zoom-levels))]
+ (let [increase #(min (* % 1.3) 200)]
(update-in state [:viewer-local :zoom] (fnil increase 1))))))
(def decrease-zoom
(ptk/reify ::decrease-zoom
ptk/UpdateEvent
(update [_ state]
- (let [decrease #(nth c/zoom-levels
- (- (d/index-of c/zoom-levels %) 1)
- (first c/zoom-levels))]
+ (let [decrease #(max (/ % 1.3) 0.01)]
(update-in state [:viewer-local :zoom] (fnil decrease 1))))))
(def reset-zoom
@@ -208,17 +205,57 @@
(update [_ state]
(assoc-in state [:viewer-local :zoom] 1))))
-(def zoom-to-50
- (ptk/reify ::zoom-to-50
+(def zoom-to-fit
+ (ptk/reify ::zoom-to-fit
ptk/UpdateEvent
(update [_ state]
- (assoc-in state [:viewer-local :zoom] 0.5))))
+ (let [page-id (get-in state [:route :query-params :page-id])
+ frame-idx (get-in state [:route :query-params :index])
+ srect (get (nth (get-in state [:viewer :pages page-id :frames]) frame-idx) :selrect)
+ original-size (get-in state [:viewer-local :viewport-size])
+ wdiff (/ (:width original-size) (:width srect))
+ hdiff (/ (:height original-size) (:height srect))
+ minzoom (min wdiff hdiff)]
+ (-> state
+ (assoc-in [:viewer-local :zoom] minzoom)
+ (assoc-in [:viewer-local :zoom-type] :fit))))))
-(def zoom-to-200
- (ptk/reify ::zoom-to-200
+(def zoom-to-fill
+ (ptk/reify ::zoom-to-fill
ptk/UpdateEvent
(update [_ state]
- (assoc-in state [:viewer-local :zoom] 2))))
+ (let [page-id (get-in state [:route :query-params :page-id])
+ frame-idx (get-in state [:route :query-params :index])
+ srect (get (nth (get-in state [:viewer :pages page-id :frames]) frame-idx) :selrect)
+ original-size (get-in state [:viewer-local :viewport-size])
+ wdiff (/ (:width original-size) (:width srect))
+ hdiff (/ (:height original-size) (:height srect))
+ maxzoom (max wdiff hdiff)]
+ (-> state
+ (assoc-in [:viewer-local :zoom] maxzoom)
+ (assoc-in [:viewer-local :zoom-type] :fill))))))
+
+(def toggle-zoom-style
+ (ptk/reify ::toggle-zoom-style
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [zoom-type (get-in state [:viewer-local :zoom-type])]
+ (if (= zoom-type :fit)
+ (rx/of zoom-to-fill)
+ (rx/of zoom-to-fit))))))
+
+(def toggle-fullscreen
+ (ptk/reify ::toggle-fullscreen
+ ptk/UpdateEvent
+ (update [_ state]
+ (update-in state [:viewer-local :fullscreen?] not))))
+
+(defn set-viewport-size
+ [{:keys [size]}]
+ (ptk/reify ::set-viewport-size
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:viewer-local :viewport-size] size))))
;; --- Local State Management
@@ -251,7 +288,6 @@
(ptk/reify ::select-next-frame
ptk/WatchEvent
(watch [_ state _]
- (prn "select-next-frame")
(let [route (:route state)
pparams (:path-params route)
qparams (:query-params route)
@@ -316,6 +352,12 @@
(update [_ state]
(d/dissoc-in state [:viewer-local :nav-scroll]))))
+(defn complete-animation
+ []
+ (ptk/reify ::complete-animation
+ ptk/UpdateEvent
+ (update [_ state]
+ (d/dissoc-in state [:viewer-local :current-animation]))))
;; --- Navigation inside page
@@ -335,23 +377,38 @@
(rx/of (rt/nav screen pparams (assoc qparams :index index)))))))
(defn go-to-frame
- [frame-id]
- (us/verify ::us/uuid frame-id)
- (ptk/reify ::go-to-frame
- ptk/UpdateEvent
- (update [_ state]
- (assoc-in state [:viewer-local :overlays] []))
+ ([frame-id] (go-to-frame frame-id nil))
+ ([frame-id animation]
+ (us/verify ::us/uuid frame-id)
+ (us/verify (s/nilable ::cti/animation) animation)
+ (ptk/reify ::go-to-frame
+ ptk/UpdateEvent
+ (update [_ state]
+ (let [route (:route state)
+ qparams (:query-params route)
+ page-id (:page-id qparams)
+ index (:index qparams)
+ frames (get-in state [:viewer :pages page-id :frames])
+ frame (get frames index)]
+ (cond-> state
+ :always
+ (assoc-in [:viewer-local :overlays] [])
- ptk/WatchEvent
- (watch [_ state _]
- (let [route (:route state)
- qparams (:query-params route)
- page-id (:page-id qparams)
+ (some? animation)
+ (assoc-in [:viewer-local :current-animation]
+ {:kind :go-to-frame
+ :orig-frame-id (:id frame)
+ :animation animation}))))
- frames (get-in state [:viewer :pages page-id :frames])
- index (d/index-of-pred frames #(= (:id %) frame-id))]
- (when index
- (rx/of (go-to-frame-by-index index)))))))
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [route (:route state)
+ qparams (:query-params route)
+ page-id (:page-id qparams)
+ frames (get-in state [:viewer :pages page-id :frames])
+ index (d/index-of-pred frames #(= (:id %) frame-id))]
+ (when index
+ (rx/of (go-to-frame-by-index index))))))))
(defn go-to-frame-auto
[]
@@ -383,12 +440,39 @@
;; --- Overlays
+(defn- do-open-overlay
+ [state frame position close-click-outside background-overlay animation]
+ (cond-> state
+ :always
+ (update-in [:viewer-local :overlays] conj
+ {:frame frame
+ :position position
+ :close-click-outside close-click-outside
+ :background-overlay background-overlay})
+ (some? animation)
+ (assoc-in [:viewer-local :current-animation]
+ {:kind :open-overlay
+ :overlay-id (:id frame)
+ :animation animation})))
+
+(defn- do-close-overlay
+ [state frame-id animation]
+ (if (nil? animation)
+ (update-in state [:viewer-local :overlays]
+ (fn [overlays]
+ (d/removev #(= (:id (:frame %)) frame-id) overlays)))
+ (assoc-in state [:viewer-local :current-animation]
+ {:kind :close-overlay
+ :overlay-id frame-id
+ :animation animation})))
+
(defn open-overlay
- [frame-id position close-click-outside background-overlay]
+ [frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id)
(us/verify ::us/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay)
+ (us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::open-overlay
ptk/UpdateEvent
(update [_ state]
@@ -399,19 +483,21 @@
frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])]
(if-not (some #(= (:frame %) frame) overlays)
- (update-in state [:viewer-local :overlays] conj
- {:frame frame
- :position position
- :close-click-outside close-click-outside
- :background-overlay background-overlay})
+ (do-open-overlay state
+ frame
+ position
+ close-click-outside
+ background-overlay
+ animation)
state)))))
(defn toggle-overlay
- [frame-id position close-click-outside background-overlay]
+ [frame-id position close-click-outside background-overlay animation]
(us/verify ::us/uuid frame-id)
(us/verify ::us/point position)
(us/verify (s/nilable ::us/boolean) close-click-outside)
(us/verify (s/nilable ::us/boolean) background-overlay)
+ (us/verify (s/nilable ::cti/animation) animation)
(ptk/reify ::toggle-overlay
ptk/UpdateEvent
(update [_ state]
@@ -422,23 +508,27 @@
frame (d/seek #(= (:id %) frame-id) frames)
overlays (get-in state [:viewer-local :overlays])]
(if-not (some #(= (:frame %) frame) overlays)
- (update-in state [:viewer-local :overlays] conj
- {:frame frame
- :position position
- :close-click-outside close-click-outside
- :background-overlay background-overlay})
- (update-in state [:viewer-local :overlays]
- (fn [overlays]
- (d/removev #(= (:id (:frame %)) frame-id) overlays))))))))
+ (do-open-overlay state
+ frame
+ position
+ close-click-outside
+ background-overlay
+ animation)
+ (do-close-overlay state
+ (:id frame)
+ (cti/invert-direction animation)))))))
(defn close-overlay
- [frame-id]
- (ptk/reify ::close-overlay
- ptk/UpdateEvent
- (update [_ state]
- (update-in state [:viewer-local :overlays]
- (fn [overlays]
- (d/removev #(= (:id (:frame %)) frame-id) overlays))))))
+ ([frame-id] (close-overlay frame-id nil))
+ ([frame-id animation]
+ (us/verify ::us/uuid frame-id)
+ (us/verify (s/nilable ::cti/animation) animation)
+ (ptk/reify ::close-overlay
+ ptk/UpdateEvent
+ (update [_ state]
+ (do-close-overlay state
+ frame-id
+ animation)))))
;; --- Objects selection
@@ -534,28 +624,29 @@
(assoc-in state [:viewer-local :overlays] []))
ptk/WatchEvent
- (watch [_ state _]
- (let [route (:route state)
- pparams (:path-params route)
- qparams (-> (:query-params route)
- (assoc :index 0)
- (assoc :page-id page-id))
- rname (get-in route [:data :name])]
- (rx/of (rt/nav rname pparams qparams))))))
+ (watch [_ state _]
+ (let [route (:route state)
+ pparams (:path-params route)
+ qparams (-> (:query-params route)
+ (assoc :index 0)
+ (assoc :page-id page-id))
+ rname (get-in route [:data :name])]
+ (rx/of (rt/nav rname pparams qparams))))))
(defn go-to-workspace
- [page-id]
- (ptk/reify ::go-to-workspace
- ptk/WatchEvent
- (watch [_ state _]
- (let [project-id (get-in state [:viewer :project :id])
- file-id (get-in state [:viewer :file :id])
- pparams {:project-id project-id :file-id file-id}
- qparams {:page-id page-id}]
+ ([] (go-to-workspace nil))
+ ([page-id]
+ (ptk/reify ::go-to-workspace
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [route (:route state)
+ project-id (get-in state [:viewer :project :id])
+ file-id (get-in state [:viewer :file :id])
+ saved-page-id (get-in route [:query-params :page-id])
+ pparams {:project-id project-id :file-id file-id}
+ qparams {:page-id (or page-id saved-page-id)}]
(rx/of (rt/nav-new-window*
{:rname :workspace
:path-params pparams
:query-params qparams
- :name (str "workspace-" file-id)}))))))
-
-
+ :name (str "workspace-" file-id)})))))))
diff --git a/frontend/src/app/main/data/viewer/shortcuts.cljs b/frontend/src/app/main/data/viewer/shortcuts.cljs
index 86f1ad8800..1a4dab457e 100644
--- a/frontend/src/app/main/data/viewer/shortcuts.cljs
+++ b/frontend/src/app/main/data/viewer/shortcuts.cljs
@@ -11,37 +11,53 @@
[app.main.store :as st]))
(def shortcuts
- {:increase-zoom {:tooltip "+"
- :command "+"
- :fn (st/emitf dv/increase-zoom)}
+ {:increase-zoom {:tooltip "+"
+ :command "+"
+ :fn (st/emitf dv/increase-zoom)}
- :decrease-zoom {:tooltip "-"
- :command "-"
- :fn (st/emitf dv/decrease-zoom)}
+ :decrease-zoom {:tooltip "-"
+ :command "-"
+ :fn (st/emitf dv/decrease-zoom)}
- :select-all {:tooltip (ds/meta "A")
- :command (ds/c-mod "a")
- :fn (st/emitf (dv/select-all))}
+ :select-all {:tooltip (ds/meta "A")
+ :command (ds/c-mod "a")
+ :fn (st/emitf (dv/select-all))}
- :zoom-50 {:tooltip (ds/shift "0")
- :command "shift+0"
- :fn (st/emitf dv/zoom-to-50)}
+ :reset-zoom {:tooltip (ds/shift "0")
+ :command "shift+0"
+ :fn (st/emitf dv/reset-zoom)}
- :reset-zoom {:tooltip (ds/shift "1")
- :command "shift+1"
- :fn (st/emitf dv/reset-zoom)}
+ :toggle-zoom-style {:tooltip "F"
+ :command "f"
+ :fn (st/emitf dv/toggle-zoom-style)}
- :zoom-200 {:tooltip (ds/shift "2")
- :command "shift+2"
- :fn (st/emitf dv/zoom-to-200)}
+ :toogle-fullscreen {:tooltip (ds/shift "F")
+ :command "shift+f"
+ :fn (st/emitf dv/toggle-fullscreen)}
- :next-frame {:tooltip ds/left-arrow
- :command "left"
- :fn (st/emitf dv/select-prev-frame)}
+ :next-frame {:tooltip ds/left-arrow
+ :command "left"
+ :fn (st/emitf dv/select-prev-frame)}
- :prev-frame {:tooltip ds/right-arrow
- :command "right"
- :fn (st/emitf dv/select-next-frame)}})
+ :prev-frame {:tooltip ds/right-arrow
+ :command "right"
+ :fn (st/emitf dv/select-next-frame)}
+
+ :open-handoff {:tooltip "G H"
+ :command "g h"
+ :fn #(st/emit! (dv/go-to-section :handoff))}
+
+ :open-comments {:tooltip "G C"
+ :command "g c"
+ :fn #(st/emit! (dv/go-to-section :comments))}
+
+ :open-interactions {:tooltip "G V"
+ :command "g v"
+ :fn #(st/emit! (dv/go-to-section :interactions))}
+
+ :open-workspace {:tooltip "G W"
+ :command "g w"
+ :fn #(st/emit! (dv/go-to-workspace))}})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index ba952977ec..2cb718f2ad 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -6,6 +6,7 @@
(ns app.main.data.workspace
(:require
+ [app.common.attrs :as attrs]
[app.common.data :as d]
[app.common.geom.align :as gal]
[app.common.geom.matrix :as gmt]
@@ -14,6 +15,7 @@
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.pages :as cp]
+ [app.common.pages.changes-builder :as pcb]
[app.common.pages.helpers :as cph]
[app.common.pages.spec :as spec]
[app.common.spec :as us]
@@ -22,12 +24,14 @@
[app.config :as cfg]
[app.main.data.events :as ev]
[app.main.data.messages :as dm]
- [app.main.data.workspace.booleans :as dwb]
+ [app.main.data.workspace.bool :as dwb]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
+ [app.main.data.workspace.fix-bool-contents :as fbc]
[app.main.data.workspace.groups :as dwg]
[app.main.data.workspace.interactions :as dwi]
+ [app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.notifications :as dwn]
[app.main.data.workspace.path :as dwdp]
@@ -41,10 +45,12 @@
[app.main.repo :as rp]
[app.main.streams :as ms]
[app.main.worker :as uw]
+ [app.util.dom :as dom]
[app.util.globals :as ug]
[app.util.http :as http]
[app.util.i18n :as i18n]
[app.util.router :as rt]
+ [app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.core :as rx]
[cljs.spec.alpha :as s]
@@ -76,7 +82,8 @@
:display-grid
:snap-grid
:scale-text
- :dynamic-alignment})
+ :dynamic-alignment
+ :display-artboard-names})
(s/def ::layout-flags (s/coll-of ::layout-flag))
@@ -87,11 +94,12 @@
:rules
:display-grid
:snap-grid
- :dynamic-alignment})
+ :dynamic-alignment
+ :display-artboard-names})
(def layout-presets
{:assets
- {:del #{:sitemap :layers :document-history }
+ {:del #{:sitemap :layers :document-history}
:add #{:assets}}
:document-history
@@ -108,6 +116,10 @@
{:zoom 1
:flags #{}
:selected (d/ordered-set)
+ :selected-assets {:components #{}
+ :graphics #{}
+ :colors #{}
+ :typographies #{}}
:expanded {}
:tooltip nil
:options-mode :design
@@ -211,8 +223,11 @@
(or (not ignore-until)
(> (:modified-at %) ignore-until)))
libraries)]
- (when needs-update?
- (rx/of (dwl/notify-sync-file file-id)))))))
+ (rx/merge
+ (rx/of (fbc/fix-bool-contents))
+ (if needs-update?
+ (rx/of (dwl/notify-sync-file file-id))
+ (rx/empty)))))))
(defn finalize-file
[_project-id file-id]
@@ -240,17 +255,24 @@
(->> (rx/of ::dwp/finalize)
(rx/observe-on :async))))))
+(declare go-to-page)
(defn initialize-page
[page-id]
(us/assert ::us/uuid page-id)
(ptk/reify ::initialize-page
+ ptk/WatchEvent
+ (watch [_ state _]
+ (when-not (contains? (get-in state [:workspace-data :pages-index]) page-id)
+ (let [default-page-id (get-in state [:workspace-data :pages 0])]
+ (rx/of (go-to-page default-page-id)))))
+
ptk/UpdateEvent
(update [_ state]
(let [;; we maintain a cache of page state for user convenience
;; with the exception of the selection; when user abandon
;; the current page, the selection is lost
-
page (get-in state [:workspace-data :pages-index page-id])
+ page-id (:id page)
local (-> state
(get-in [:workspace-cache page-id] workspace-local-default)
(assoc :selected (d/ordered-set)))]
@@ -269,10 +291,13 @@
(let [local (-> (:workspace-local state)
(dissoc :edition
:edit-path
- :selected))]
- (-> state
- (assoc-in [:workspace-cache page-id] local)
- (dissoc :current-page-id :workspace-local :trimmed-page :workspace-drawing))))))
+ :selected))
+ exit-workspace? (not= :workspace (get-in state [:route :data :name]))]
+ (cond-> (assoc-in state [:workspace-cache page-id] local)
+ :always
+ (dissoc :current-page-id :workspace-local :trimmed-page)
+ exit-workspace?
+ (dissoc :workspace-drawing))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Workspace Page CRUD
@@ -305,7 +330,7 @@
[page-id]
(ptk/reify ::duplicate-page
ptk/WatchEvent
- (watch [this state _]
+ (watch [it state _]
(let [id (uuid/next)
pages (get-in state [:workspace-data :pages-index])
unames (dwc/retrieve-used-names pages)
@@ -320,7 +345,7 @@
:id id}]
(rx/of (dch/commit-changes {:redo-changes [rchange]
:undo-changes [uchange]
- :origin this}))))))
+ :origin it}))))))
(s/def ::rename-page
(s/keys :req-un [::id ::name]))
@@ -353,10 +378,9 @@
ptk/WatchEvent
(watch [it state _]
(let [page (get-in state [:workspace-data :pages-index id])
- rchg {:type :del-page
- :id id}
- uchg {:type :add-page
- :page page}]
+ rchg {:type :del-page :id id}
+ uchg {:type :add-page :page page}]
+
(rx/of (dch/commit-changes {:redo-changes [rchg]
:undo-changes [uchg]
:origin it})
@@ -389,6 +413,43 @@
;; Workspace State Manipulation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; --- Toggle layout flag
+
+(defn toggle-layout-flags
+ [& flags]
+ (ptk/reify ::toggle-layout-flags
+ ptk/UpdateEvent
+ (update [_ state]
+ (update state :workspace-layout
+ (fn [stored]
+ (reduce (fn [flags flag]
+ (if (contains? flags flag)
+ (disj flags flag)
+ (conj flags flag)))
+ stored
+ (d/concat-set flags)))))))
+
+;; --- Set element options mode
+
+(defn set-options-mode
+ [mode]
+ (us/assert ::options-mode mode)
+ (ptk/reify ::set-options-mode
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :options-mode] mode))))
+
+;; --- Tooltip
+
+(defn assign-cursor-tooltip
+ [content]
+ (ptk/reify ::assign-cursor-tooltip
+ ptk/UpdateEvent
+ (update [_ state]
+ (if (string? content)
+ (assoc-in state [:workspace-local :tooltip] content)
+ (assoc-in state [:workspace-local :tooltip] nil)))))
+
;; --- Viewport Sizing
(declare increase-zoom)
@@ -404,9 +465,9 @@
(-> local
(assoc :vport size)
(update :vbox (fn [vbox]
- (-> vbox
- (update :width #(/ % wprop))
- (update :height #(/ % hprop))))))))
+ (-> vbox
+ (update :width #(/ % wprop))
+ (update :height #(/ % hprop))))))))
(initialize [state local]
(let [page-id (:current-page-id state)
@@ -433,7 +494,7 @@
:y (+ (:y srect) (/ (- (:height srect) height) 2)))))))
(setup [state local]
- (if (:vbox local)
+ (if (and (:vbox local) (:vport local))
(update* local)
(initialize state local)))]
@@ -528,44 +589,6 @@
(-> state
(update :workspace-local dissoc :zooming)))))
-
-;; --- Toggle layout flag
-
-(defn toggle-layout-flags
- [& flags]
- (ptk/reify ::toggle-layout-flags
- ptk/UpdateEvent
- (update [_ state]
- (update state :workspace-layout
- (fn [stored]
- (reduce (fn [flags flag]
- (if (contains? flags flag)
- (disj flags flag)
- (conj flags flag)))
- stored
- (d/concat-set flags)))))))
-
-;; --- Set element options mode
-
-(defn set-options-mode
- [mode]
- (us/assert ::options-mode mode)
- (ptk/reify ::set-options-mode
- ptk/UpdateEvent
- (update [_ state]
- (assoc-in state [:workspace-local :options-mode] mode))))
-
-;; --- Tooltip
-
-(defn assign-cursor-tooltip
- [content]
- (ptk/reify ::assign-cursor-tooltip
- ptk/UpdateEvent
- (update [_ state]
- (if (string? content)
- (assoc-in state [:workspace-local :tooltip] content)
- (assoc-in state [:workspace-local :tooltip] nil)))))
-
;; --- Zoom Management
(defn- impl-update-zoom
@@ -623,9 +646,7 @@
objects (wsh/lookup-page-objects state page-id)
shapes (cp/select-toplevel-shapes objects {:include-frames? true})
srect (gsh/selection-rect shapes)]
-
- (if (or (mth/nan? (:width srect))
- (mth/nan? (:height srect)))
+ (if (empty? shapes)
state
(update state :workspace-local
(fn [{:keys [vport] :as local}]
@@ -735,7 +756,7 @@
:shapes [id]}))
selected)
- uchanges (mapv (fn [id]
+ uchanges (mapv (fn [id]
(let [obj (get objects id)]
{:type :mov-objects
:parent-id (:parent-id obj)
@@ -743,7 +764,7 @@
:page-id page-id
:shapes [id]
:index (cp/position-on-parent id objects)}))
- selected)]
+ selected)]
;; TODO: maybe missing the :reg-objects event?
(rx/of (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges
@@ -902,11 +923,13 @@
:operations [{:type :set
:attr :constraints-h
:val (spec/default-constraints-h
- (assoc obj :parent-id parent-id :frame-id frame-id))}
+ (assoc obj :parent-id parent-id :frame-id frame-id))
+ :ignore-touched true}
{:type :set
:attr :constraints-v
:val (spec/default-constraints-v
- (assoc obj :parent-id parent-id :frame-id frame-id))}]}))
+ (assoc obj :parent-id parent-id :frame-id frame-id))
+ :ignore-touched true}]}))
shapes-to-unconstraint)
u-unconstraint-change
@@ -917,10 +940,12 @@
:id id
:operations [{:type :set
:attr :constraints-h
- :val (:constraints-h obj)}
+ :val (:constraints-h obj)
+ :ignore-touched true}
{:type :set
:attr :constraints-v
- :val (:constraints-v obj)}]}))
+ :val (:constraints-v obj)
+ :ignore-touched true}]}))
shapes-to-unconstraint)
r-reg-change
@@ -1112,23 +1137,24 @@
(ptk/reify ::relocate-page
ptk/WatchEvent
(watch [it state _]
- (let [cidx (-> (get-in state [:workspace-data :pages])
- (d/index-of id))
- rchg {:type :mov-page
- :id id
- :index index}
- uchg {:type :mov-page
- :id id
- :index cidx}]
- (rx/of (dch/commit-changes {:redo-changes [rchg]
- :undo-changes [uchg]
- :origin it}))))))
+ (let [prev-index (-> (get-in state [:workspace-data :pages])
+ (d/index-of id))
+ changes (-> (pcb/empty-changes it id)
+ (pcb/move-page index prev-index))]
+ (rx/of (dch/commit-changes changes))))))
;; --- Shape / Selection Alignment and Distribution
(declare align-object-to-frame)
(declare align-objects-list)
+(defn can-align? [selected objects]
+ (cond
+ (empty? selected) false
+ (> (count selected) 1) true
+ :else
+ (not= uuid/zero (:frame-id (get objects (first selected))))))
+
(defn align-objects
[axis]
(us/verify ::gal/align-axis axis)
@@ -1141,12 +1167,11 @@
moved (if (= 1 (count selected))
(align-object-to-frame objects (first selected) axis)
(align-objects-list objects selected axis))
-
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
-
- (rx/of (dch/update-shapes ids update-fn {:reg-objects? true}))))))
+ (when (can-align? selected objects)
+ (rx/of (dch/update-shapes ids update-fn {:reg-objects? true})))))))
(defn align-object-to-frame
[objects object-id axis]
@@ -1160,6 +1185,12 @@
rect (gsh/selection-rect selected-objs)]
(mapcat #(gal/align-to-rect % rect axis objects) selected-objs)))
+(defn can-distribute? [selected]
+ (cond
+ (empty? selected) false
+ (< (count selected) 2) false
+ :else true))
+
(defn distribute-objects
[axis]
(us/verify ::gal/dist-axis axis)
@@ -1175,7 +1206,8 @@
moved-objects (->> moved (group-by :id))
ids (keys moved-objects)
update-fn (fn [shape] (first (get moved-objects (:id shape))))]
- (rx/of (dch/update-shapes ids update-fn {:reg-objects? true}))))))
+ (when (can-distribute? selected)
+ (rx/of (dch/update-shapes ids update-fn {:reg-objects? true})))))))
;; --- Shape Proportions
@@ -1191,12 +1223,27 @@
(gpr/assign-proportions))))]
(rx/of (dch/update-shapes [id] assign-proportions))))))
+(defn toggle-proportion-lock
+ []
+ (ptk/reify ::toggle-propotion-lock
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ selected (wsh/lookup-selected state)
+ selected-obj (-> (map #(get objects %) selected))
+ multi (attrs/get-attrs-multi selected-obj [:proportion-lock])
+ multi? (= :multiple (:proportion-lock multi))]
+ (if multi?
+ (rx/of (dch/update-shapes selected #(assoc % :proportion-lock true)))
+ (rx/of (dch/update-shapes selected #(update % :proportion-lock not))))))))
+
;; --- Update Shape Flags
(defn update-shape-flags
- [id {:keys [blocked hidden] :as flags}]
- (s/assert ::us/uuid id)
- (s/assert ::shape-attrs flags)
+ [ids {:keys [blocked hidden] :as flags}]
+ (us/verify (s/coll-of ::us/uuid) ids)
+ (us/assert ::shape-attrs flags)
(ptk/reify ::update-shape-flags
ptk/WatchEvent
(watch [_ state _]
@@ -1205,11 +1252,25 @@
(cond-> obj
(boolean? blocked) (assoc :blocked blocked)
(boolean? hidden) (assoc :hidden hidden)))
-
objects (wsh/lookup-page-objects state)
- ids (into [id] (cp/get-children id objects))]
+ ids (into ids (->> ids (mapcat #(cp/get-children % objects))))]
(rx/of (dch/update-shapes ids update-fn))))))
+(defn toggle-visibility-selected
+ []
+ (ptk/reify ::toggle-visibility-selected
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [selected (wsh/lookup-selected state)]
+ (rx/of (dch/update-shapes selected #(update % :hidden not)))))))
+
+(defn toggle-lock-selected
+ []
+ (ptk/reify ::toggle-lock-selected
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [selected (wsh/lookup-selected state)]
+ (rx/of (dch/update-shapes selected #(update % :blocked not)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Navigation
@@ -1263,6 +1324,65 @@
qparams {:page-id page-id :layout (name layout)}]
(rx/of (rt/nav :workspace pparams qparams))))))
+(defn check-in-asset
+ [set element]
+ (if (contains? set element)
+ (disj set element)
+ (conj set element)))
+
+(defn toggle-selected-assets
+ [asset type]
+ (ptk/reify ::toggle-selected-assets
+ ptk/UpdateEvent
+ (update [_ state]
+ (update-in state [:workspace-local :selected-assets type] #(check-in-asset % asset)))))
+
+(defn select-single-asset
+ [asset type]
+ (ptk/reify ::select-single-asset
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :selected-assets type] #{asset}))))
+
+(defn select-assets
+ [assets type]
+ (ptk/reify ::select-assets
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :selected-assets type] (into #{} assets)))))
+
+(defn unselect-all-assets
+ []
+ (ptk/reify ::unselect-all-assets
+ ptk/UpdateEvent
+ (update [_ state]
+ (assoc-in state [:workspace-local :selected-assets] {:components #{}
+ :graphics #{}
+ :colors #{}
+ :typographies #{}}))))
+
+(defn go-to-component
+ [component-id]
+ (ptk/reify ::set-workspace-layout-component
+ IDeref
+ (-deref [_] {:layout :assets})
+
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [project-id (get-in state [:workspace-project :id])
+ file-id (get-in state [:workspace-file :id])
+ page-id (get state :current-page-id)
+ pparams {:file-id file-id :project-id project-id}
+ qparams {:page-id page-id :layout :assets}]
+ (rx/of (rt/nav :workspace pparams qparams)
+ (dwl/set-assets-box-open file-id :library true)
+ (dwl/set-assets-box-open file-id :components true)
+ (select-single-asset component-id :components))))
+ ptk/EffectEvent
+ (effect [_ _ _]
+ (let [wrapper-id (str "component-shape-id-" component-id)]
+ (tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))
+
(def go-to-file
(ptk/reify ::go-to-file
ptk/WatchEvent
@@ -1275,13 +1395,15 @@
(defn go-to-viewer
([] (go-to-viewer {}))
- ([{:keys [file-id page-id]}]
+ ([{:keys [file-id page-id section]}]
(ptk/reify ::go-to-viewer
ptk/WatchEvent
(watch [_ state _]
(let [{:keys [current-file-id current-page-id]} state
pparams {:file-id (or file-id current-file-id)}
- qparams {:page-id (or page-id current-page-id)}]
+ qparams (cond-> {:page-id (or page-id current-page-id)}
+ (some? section)
+ (assoc :section section))]
(rx/of ::dwp/force-persist
(rt/nav-new-window* {:rname :viewer
:path-params pparams
@@ -1300,12 +1422,12 @@
(defn go-to-dashboard-fonts
[]
- (ptk/reify ::go-to-dashboard-fonts
- ptk/WatchEvent
- (watch [_ state _]
- (let [team-id (:current-team-id state)]
- (rx/of ::dwp/force-persist
- (rt/nav :dashboard-fonts {:team-id team-id}))))))
+ (ptk/reify ::go-to-dashboard-fonts
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [team-id (:current-team-id state)]
+ (rx/of ::dwp/force-persist
+ (rt/nav :dashboard-fonts {:team-id team-id}))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Context Menu
@@ -1314,53 +1436,36 @@
(s/def ::point gpt/point?)
(defn show-context-menu
- [{:keys [position shape] :as params}]
+ [{:keys [position] :as params}]
(us/verify ::point position)
- (us/verify (s/nilable ::cp/minimal-shape) shape)
(ptk/reify ::show-context-menu
ptk/UpdateEvent
(update [_ state]
- (let [selected (wsh/lookup-selected state)
- objects (wsh/lookup-page-objects state)
-
- selected-with-children
- (into []
- (mapcat #(cp/get-object-with-children % objects))
- selected)
-
- head (get objects (first selected))
-
- first-not-group-like?
- (and (= (count selected) 1)
- (not (contains? #{:group :bool} (:type head))))
-
- has-invalid-shapes? (->> selected-with-children
- (some (comp #{:frame :text} :type)))
-
- disable-booleans? (or (empty? selected) has-invalid-shapes? first-not-group-like?)
- disable-flatten? (or (empty? selected) has-invalid-shapes?)
-
- mdata
- (-> params
- (assoc :disable-booleans? disable-booleans?)
- (assoc :disable-flatten? disable-flatten?)
- (cond-> (some? shape)
- (assoc :selected selected)))]
-
- (assoc-in state [:workspace-local :context-menu] mdata)))))
+ (assoc-in state [:workspace-local :context-menu] params))))
(defn show-shape-context-menu
- [{:keys [position shape] :as params}]
- (us/verify ::point position)
- (us/verify ::cp/minimal-shape shape)
+ [{:keys [shape] :as params}]
(ptk/reify ::show-shape-context-menu
ptk/WatchEvent
(watch [_ state _]
- (let [selected (wsh/lookup-selected state)]
+ (let [selected (wsh/lookup-selected state)
+ objects (wsh/lookup-page-objects state)
+ all-selected (into [] (mapcat #(cp/get-object-with-children % objects)) selected)
+ head (get objects (first selected))
+
+ not-group-like? (and (= (count selected) 1)
+ (not (contains? #{:group :bool} (:type head))))
+ no-bool-shapes? (->> all-selected (some (comp #{:frame :text} :type)))]
+
(rx/concat
- (when-not (selected (:id shape))
- (rx/of (dws/select-shape (:id shape))))
- (rx/of (show-context-menu params)))))))
+ (when (and (some? shape) (not (contains? selected (:id shape))))
+ (rx/of (dws/select-shape (:id shape))))
+ (rx/of (show-context-menu
+ (-> params
+ (assoc
+ :disable-booleans? (or no-bool-shapes? not-group-like?)
+ :disable-flatten? no-bool-shapes?
+ :selected (conj selected (:id shape)))))))))))
(def hide-context-menu
(ptk/reify ::hide-context-menu
@@ -1500,9 +1605,9 @@
paste-image-str)
(rx/first)
(rx/catch
- (fn [err]
- (js/console.error "Clipboard error:" err)
- (rx/empty)))))
+ (fn [err]
+ (js/console.error "Clipboard error:" err)
+ (rx/empty)))))
(catch :default e
(let [data (ex-data e)]
(if (:not-implemented data)
@@ -1600,7 +1705,36 @@
(cond
(and (selected-frame? state) (not has-frame?))
(let [frame-id (first page-selected)
- delta (get page-objects frame-id)]
+ frame-object (get page-objects frame-id)
+
+ origin-frame-id (:frame-id (first selected-objs))
+ origin-frame-object (get page-objects origin-frame-id)
+
+ ;; - The pasted object position must be limited to container boundaries. If the pasted object doesn't fit we try to:
+ ;; - Align it to the limits on the x and y axis
+ ;; - Respect the distance of the object to the right and bottom in the original frame
+ margin-x (if origin-frame-object
+ (- (:width origin-frame-object) (+ (:x wrapper) (:width wrapper)))
+ 0)
+ margin-x (min margin-x (- (:width frame-object) (:width wrapper)))
+
+ margin-y (if origin-frame-object
+ (- (:height origin-frame-object) (+ (:y wrapper) (:height wrapper)))
+ 0)
+ margin-y (min margin-y (- (:height frame-object) (:height wrapper)))
+
+ ;; Pasted objects mustn't exceed the selected frame x limit
+ paste-x (if (> (+ (:width wrapper) (:x1 wrapper)) (:width frame-object))
+ (+ (- (:x frame-object) (:x orig-pos)) (- (:width frame-object) (:width wrapper) margin-x))
+ (:x frame-object))
+
+ ;; Pasted objects mustn't exceed the selected frame y limit
+ paste-y (if (> (+ (:height wrapper) (:y1 wrapper)) (:height frame-object))
+ (+ (- (:y frame-object) (:y orig-pos)) (- (:height frame-object) (:height wrapper) margin-y))
+ (:y frame-object))
+
+ delta (gpt/point paste-x paste-y)]
+
[frame-id frame-id delta])
(empty? page-selected)
@@ -1643,7 +1777,7 @@
(not= root-file-id (:current-file-id state))
(nil? (get-in state [:workspace-libraries root-file-id])))))
- ;; Procceed with the standard shape paste procediment.
+ ;; Proceed with the standard shape paste process.
(do-paste [it state mouse-pos media]
(let [page-objects (wsh/lookup-page-objects state)
media-idx (d/index-by :prev-id media)
@@ -1659,7 +1793,7 @@
(cond->
;; if foreign instance, detach the shape
- (foreign-instance? shape paste-objects state)
+ (foreign-instance? shape paste-objects state)
(dissoc :component-id
:component-file
:component-root?
@@ -1671,7 +1805,7 @@
page-id (:current-page-id state)
unames (-> (wsh/lookup-page-objects state page-id)
- (dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplcate-changes?
+ (dwc/retrieve-used-names)) ;; TODO: move this calculation inside prepare-duplicate-changes?
rchanges (->> (dws/prepare-duplicate-changes all-objects page-id unames selected delta)
(mapv (partial process-rchange media-idx))
@@ -1719,7 +1853,7 @@
(defn paste-text
[text]
- (s/assert string? text)
+ (us/assert string? text)
(ptk/reify ::paste-text
ptk/WatchEvent
(watch [_ state _]
@@ -1748,7 +1882,7 @@
(defn- paste-svg
[text]
- (s/assert string? text)
+ (us/assert string? text)
(ptk/reify ::paste-svg
ptk/WatchEvent
(watch [_ state _]
@@ -1797,7 +1931,7 @@
(watch [it state _]
(let [page-id (get state :current-page-id)
options (wsh/lookup-page-options state page-id)
- previus-color (:background options)]
+ previous-color (:background options)]
(rx/of (dch/commit-changes
{:redo-changes [{:type :set-option
:page-id page-id
@@ -1806,9 +1940,38 @@
:undo-changes [{:type :set-option
:page-id page-id
:option :background
- :value previus-color}]
+ :value previous-color}]
:origin it}))))))
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Artboard
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(defn create-artboard-from-selection
+ []
+ (ptk/reify ::create-artboard-from-selection
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ shapes (cp/select-toplevel-shapes objects {:include-frames? true})
+ selected (wsh/lookup-selected state)
+ selected-objs (map #(get objects %) selected)
+ has-frame? (some #(= (:type %) :frame) selected-objs)]
+ (when (not (or (empty? selected) has-frame?))
+ (let [srect (gsh/selection-rect selected-objs)
+ frame-id (:frame-id (first shapes))
+ shape (-> (cp/make-minimal-shape :frame)
+ (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)})
+ (assoc :frame-id frame-id)
+ (gsh/setup-selrect))]
+ (rx/of
+ (dwu/start-undo-transaction)
+ (dwc/add-shape shape)
+ (dwc/move-shapes-into-frame (:id shape) selected)
+ (dwu/commit-undo-transaction))))))))
+
+
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -1824,6 +1987,7 @@
(d/export dwt/update-position)
(d/export dwt/flip-horizontal-selected)
(d/export dwt/flip-vertical-selected)
+(d/export dwly/set-opacity)
;; Persistence
diff --git a/frontend/src/app/main/data/workspace/booleans.cljs b/frontend/src/app/main/data/workspace/bool.cljs
similarity index 86%
rename from frontend/src/app/main/data/workspace/booleans.cljs
rename to frontend/src/app/main/data/workspace/bool.cljs
index 5c3c1af7b1..ab0ec4fdd4 100644
--- a/frontend/src/app/main/data/workspace/booleans.cljs
+++ b/frontend/src/app/main/data/workspace/bool.cljs
@@ -4,8 +4,9 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.main.data.workspace.booleans
+(ns app.main.data.workspace.bool
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
@@ -35,19 +36,22 @@
head (if (= bool-type :difference) (first shapes) (last shapes))
head (cond-> head
(and (contains? head :svg-attrs) (nil? (:fill-color head)))
- (assoc :fill-color "#000000"))
+ (assoc :fill-color clr/black))
- head-data (select-keys head stp/style-properties)]
- [(-> {:id (uuid/next)
- :type :bool
- :bool-type bool-type
- :frame-id (:frame-id head)
- :parent-id (:parent-id head)
- :name name
- :shapes []}
- (merge head-data)
- (gsh/update-bool-selrect shapes objects))
- (cp/position-on-parent (:id head) objects)]))
+ head-data (select-keys head stp/style-properties)
+
+ bool-shape
+ (-> {:id (uuid/next)
+ :type :bool
+ :bool-type bool-type
+ :frame-id (:frame-id head)
+ :parent-id (:parent-id head)
+ :name name
+ :shapes (->> shapes (mapv :id))}
+ (merge head-data)
+ (gsh/update-bool-selrect shapes objects))]
+
+ [bool-shape (cp/position-on-parent (:id head) objects)]))
(defn group->bool
[group bool-type objects]
@@ -58,7 +62,7 @@
head (if (= bool-type :difference) (first shapes) (last shapes))
head (cond-> head
(and (contains? head :svg-attrs) (nil? (:fill-color head)))
- (assoc :fill-color "#000000"))
+ (assoc :fill-color clr/black))
head-data (select-keys head stp/style-properties)]
(-> group
diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs
index 241229fe88..9516d4db32 100644
--- a/frontend/src/app/main/data/workspace/changes.cljs
+++ b/frontend/src/app/main/data/workspace/changes.cljs
@@ -134,7 +134,6 @@
(let [current-file-id (get state :current-file-id)
file-id (or file-id current-file-id)
path (if (= file-id current-file-id)
-
[:workspace-data]
[:workspace-libraries file-id :data])]
(try
diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs
index f76b288d41..017cc967d6 100644
--- a/frontend/src/app/main/data/workspace/colors.cljs
+++ b/frontend/src/app/main/data/workspace/colors.cljs
@@ -6,6 +6,7 @@
(ns app.main.data.workspace.colors
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.main.data.modal :as md]
[app.main.data.workspace.changes :as dch]
@@ -122,7 +123,12 @@
text-ids (filter is-text? ids)
shape-ids (filter (comp not is-text?) ids)
- attrs (cond-> {}
+ attrs (cond-> {:fill-color nil
+ :fill-color-gradient nil
+ ::fill-color-ref-file nil
+ :fill-color-ref-id nil
+ :fill-opacity nil}
+
(contains? color :color)
(assoc :fill-color (:color color))
@@ -142,12 +148,31 @@
(rx/from (map #(dwt/update-text-attrs {:id % :attrs attrs}) text-ids))
(rx/of (dch/update-shapes shape-ids (fn [shape] (d/merge shape attrs)))))))))
+(defn change-hide-fill-on-export
+ [ids hide-fill-on-export]
+ (ptk/reify ::change-hide-fill-on-export
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ is-text? #(= :text (:type (get objects %)))
+ shape-ids (filter (complement is-text?) ids)
+ attrs {:hide-fill-on-export hide-fill-on-export}]
+ (rx/of (dch/update-shapes shape-ids (fn [shape]
+ (if (= (:type shape) :frame)
+ (d/merge shape attrs)
+ shape))))))))
+
(defn change-stroke
[ids color]
(ptk/reify ::change-stroke
ptk/WatchEvent
(watch [_ _ _]
- (let [attrs (cond-> {}
+ (let [attrs (cond-> {:stroke-color nil
+ :stroke-color-ref-id nil
+ :stroke-color-ref-file nil
+ :stroke-color-gradient nil
+ :stroke-opacity nil}
(contains? color :color)
(assoc :stroke-color (:color color))
@@ -203,7 +228,7 @@
(-> state
(assoc-in [:workspace-local :picking-color?] true)
(assoc ::md/modal {:id (random-uuid)
- :data {:color "#000000" :opacity 1}
+ :data {:color clr/black :opacity 1}
:type :colorpicker
:props {:on-change handle-change-color}
:allow-click-outside true})))))))
diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs
index 8a07d1d900..ede10867f2 100644
--- a/frontend/src/app/main/data/workspace/comments.cljs
+++ b/frontend/src/app/main/data/workspace/comments.cljs
@@ -76,15 +76,13 @@
(update [_ state]
(update state :workspace-local
(fn [{:keys [vbox zoom] :as local}]
- (let [pw (/ 50 zoom)
- ph (/ 200 zoom)
+ (let [pw (/ 160 zoom)
+ ph (/ 160 zoom)
nw (mth/round (- (/ (:width vbox) 2) pw))
nh (mth/round (- (/ (:height vbox) 2) ph))
nx (- (:x position) nw)
ny (- (:y position) nh)]
-
-
- (update local :vbox assoc :x nx :y ny)))))))
+ (update local :vbox assoc :x nx :y ny)))))))
(defn navigate
[thread]
@@ -96,8 +94,7 @@
:file-id (:file-id thread)}
qparams {:page-id (:page-id thread)}]
(rx/merge
- (rx/of (rt/nav :workspace pparams qparams)
- (dw/select-for-drawing :comments))
+ (rx/of (rt/nav :workspace pparams qparams))
(->> stream
(rx/filter (ptk/type? ::dw/initialize-viewport))
(rx/take 1)
diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs
index 6b4a1c41df..1fb8fc85b7 100644
--- a/frontend/src/app/main/data/workspace/common.cljs
+++ b/frontend/src/app/main/data/workspace/common.cljs
@@ -258,7 +258,7 @@
[objects selected attrs]
(if (= :frame (:type attrs))
- ;; Frames are alwasy positioned on the root frame
+ ;; Frames are always positioned on the root frame
[uuid/zero uuid/zero nil]
;; Calculate the frame over which we're drawing
diff --git a/frontend/src/app/main/data/workspace/fix_bool_contents.cljs b/frontend/src/app/main/data/workspace/fix_bool_contents.cljs
new file mode 100644
index 0000000000..b59bc401a6
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/fix_bool_contents.cljs
@@ -0,0 +1,94 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.workspace.fix-bool-contents
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [beicon.core :as rx]
+ [potok.core :as ptk]))
+
+;; This event will update the file so the boolean data has a pre-generated path data
+;; to increase performance.
+;; For new shapes this will be generated in the :reg-objects but we need to do this for
+;; old files.
+
+;; FIXME: Remove me after June 2022
+
+(defn fix-bool-contents
+ "This event will calculate the bool content and update the page. This is kind of a 'addhoc' migration
+ to fill the optional value 'bool-content'"
+ []
+
+ (letfn [(should-migrate-shape? [shape]
+ (and (= :bool (:type shape)) (not (contains? shape :bool-content))))
+
+ (should-migrate-component? [component]
+ (->> (:objects component)
+ (vals)
+ (d/seek should-migrate-shape?)))
+
+ (update-shape [shape objects]
+ (cond-> shape
+ (should-migrate-shape? shape)
+ (assoc :bool-content (gsh/calc-bool-content shape objects))))
+
+ (migrate-component [component]
+ (-> component
+ (update
+ :objects
+ (fn [objects]
+ (d/mapm #(update-shape %2 objects) objects)))))
+
+ (update-library
+ [library]
+ (-> library
+ (d/update-in-when
+ [:data :components]
+ (fn [components]
+ (d/mapm #(migrate-component %2) components)))))]
+
+ (ptk/reify ::fix-bool-contents
+ ptk/UpdateEvent
+ (update [_ state]
+ ;; Update (only-local) the imported libraries
+ (-> state
+ (d/update-when
+ :workspace-libraries
+ (fn [libraries] (d/mapm #(update-library %2) libraries)))))
+
+ ptk/WatchEvent
+ (watch [it state _]
+ (let [objects (wsh/lookup-page-objects state)
+
+ ids (into #{}
+ (comp (filter should-migrate-shape?) (map :id))
+ (vals objects))
+
+ components (->> (wsh/lookup-local-components state)
+ (vals)
+ (filter should-migrate-component?))
+
+ component-changes
+ (into []
+ (map (fn [component]
+ {:type :mod-component
+ :id (:id component)
+ :objects (-> component migrate-component :objects)}))
+ components)]
+
+ (rx/of (dch/update-shapes ids #(update-shape % objects) {:reg-objects? false
+ :save-undo? false
+ :ignore-tree true}))
+
+ (if (empty? component-changes)
+ (rx/empty)
+ (rx/of (dch/commit-changes {:origin it
+ :redo-changes component-changes
+ :undo-changes []
+ :save-undo? false}))))))))
diff --git a/frontend/src/app/main/data/workspace/grid.cljs b/frontend/src/app/main/data/workspace/grid.cljs
index ed3e2944d8..3e2cde9073 100644
--- a/frontend/src/app/main/data/workspace/grid.cljs
+++ b/frontend/src/app/main/data/workspace/grid.cljs
@@ -6,6 +6,7 @@
(ns app.main.data.workspace.grid
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.common.spec :as us]
[app.main.data.workspace.changes :as dch]
@@ -18,7 +19,7 @@
(defonce ^:private default-square-params
{:size 16
- :color {:color "#59B9E2"
+ :color {:color clr/info
:opacity 0.4}})
(defonce ^:private default-layout-params
@@ -27,7 +28,7 @@
:item-length nil
:gutter 8
:margin 0
- :color {:color "#DE4762"
+ :color {:color clr/default-layout
:opacity 0.1}})
(defonce default-grid-params
diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs
index 1489e90074..bf452c58fe 100644
--- a/frontend/src/app/main/data/workspace/groups.cljs
+++ b/frontend/src/app/main/data/workspace/groups.cljs
@@ -9,6 +9,8 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
+ [app.common.pages.changes-builder :as cb]
+ [app.common.spec :as us]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.state-helpers :as wsh]
@@ -104,7 +106,7 @@
:page-id page-id}))
;; Look at the `get-empty-groups-after-group-creation`
- ;; doctring to understand the real purpuse of this code
+ ;; docstring to understand the real purpose of this code
ids-to-delete (get-empty-groups-after-group-creation objects parent-id shapes)
delete-group
@@ -135,8 +137,8 @@
[group rchanges uchanges]))
(defn prepare-remove-group
- [page-id group objects]
- (let [shapes (into [] (:shapes group)) ; ensure we always have vector
+ [it page-id group objects]
+ (let [children (mapv #(get objects %) (:shapes group))
parent-id (cp/get-parent (:id group) objects)
parent (get objects parent-id)
@@ -146,28 +148,46 @@
(filter #(#{(:id group)} (second %)))
(ffirst))
- rchanges [{:type :mov-objects
+ ids-to-detach (when (:component-id group)
+ (cp/get-children (:id group) objects))
+
+ detach-fn (fn [attrs]
+ (dissoc attrs
+ :component-id
+ :component-file
+ :component-root?
+ :remote-synced?
+ :shape-ref
+ :touched))]
+
+ (cond-> (-> (cb/empty-changes it page-id)
+ (cb/with-objects objects)
+ (cb/change-parent parent-id children index-in-parent)
+ (cb/remove-objects [(:id group)]))
+
+ (some? ids-to-detach)
+ (cb/update-shapes ids-to-detach detach-fn))))
+
+(defn prepare-remove-mask
+ [page-id mask]
+ (let [rchanges [{:type :mod-obj
:page-id page-id
- :parent-id parent-id
- :shapes shapes
- :index index-in-parent}
- {:type :del-obj
+ :id (:id mask)
+ :operations [{:type :set
+ :attr :masked-group?
+ :val nil}]}
+ {:type :reg-objects
:page-id page-id
- :id (:id group)}]
- uchanges [{:type :add-obj
+ :shapes [(:id mask)]}]
+ uchanges [{:type :mod-obj
:page-id page-id
- :id (:id group)
- :frame-id (:frame-id group)
- :obj (assoc group :shapes [])}
- {:type :mov-objects
+ :id (:id mask)
+ :operations [{:type :set
+ :attr :masked-group?
+ :val (:masked-group? mask)}]}
+ {:type :reg-objects
:page-id page-id
- :parent-id (:id group)
- :shapes shapes}
- {:type :mov-objects
- :page-id page-id
- :parent-id parent-id
- :shapes [(:id group)]
- :index index-in-parent}]]
+ :shapes [(:id mask)]}]]
[rchanges uchanges]))
@@ -196,18 +216,23 @@
(ptk/reify ::ungroup-selected
ptk/WatchEvent
(watch [it state _]
- (let [page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- selected (wsh/lookup-selected state)
- group-id (first selected)
- group (get objects group-id)]
- (when (and (= 1 (count selected))
- (contains? #{:group :bool} (:type group)))
- (let [[rchanges uchanges]
- (prepare-remove-group page-id group objects)]
- (rx/of (dch/commit-changes {:redo-changes rchanges
- :undo-changes uchanges
- :origin it}))))))))
+ (let [page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ is-group? #(or (= :bool (:type %)) (= :group (:type %)))
+ lookup #(get objects %)
+ prepare #(prepare-remove-group it page-id % objects)
+
+ changes-list (sequence
+ (comp (map lookup)
+ (filter is-group?)
+ (map prepare))
+ (wsh/lookup-selected state))
+
+ changes {:redo-changes (vec (mapcat :redo-changes changes-list))
+ :undo-changes (vec (mapcat :undo-changes changes-list))
+ :origin it}]
+
+ (rx/of (dch/commit-changes changes))))))
(def mask-group
(ptk/reify ::mask-group
@@ -227,8 +252,24 @@
[(first shapes) [] []]
(prepare-create-group objects page-id shapes "Group-1" true))
+ ;; Assertions just for documentation purposes
+ _ (us/assert vector? rchanges)
+ _ (us/assert vector? uchanges)
+
+ children (map #(get objects %) (:shapes group))
+
rchanges (d/concat-vec
rchanges
+ (for [child children]
+ {:type :mod-obj
+ :page-id page-id
+ :id (:id child)
+ :operations [{:type :set
+ :attr :constraints-h
+ :val :scale}
+ {:type :set
+ :attr :constraints-v
+ :val :scale}]})
[{:type :mod-obj
:page-id page-id
:id (:id group)
@@ -253,15 +294,25 @@
uchanges (d/concat-vec
uchanges
- {:type :mod-obj
- :page-id page-id
- :id (:id group)
- :operations [{:type :set
- :attr :masked-group?
- :val nil}]}
- {:type :reg-objects
- :page-id page-id
- :shapes [(:id group)]})]
+ (for [child children]
+ {:type :mod-obj
+ :page-id page-id
+ :id (:id child)
+ :operations [{:type :set
+ :attr :constraints-h
+ :val (:constraints-h child)}
+ {:type :set
+ :attr :constraints-v
+ :val (:constraints-v child)}]})
+ [{:type :mod-obj
+ :page-id page-id
+ :id (:id group)
+ :operations [{:type :set
+ :attr :masked-group?
+ :val nil}]}
+ {:type :reg-objects
+ :page-id page-id
+ :shapes [(:id group)]}])]
(rx/of (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges
@@ -274,33 +325,14 @@
(watch [it state _]
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
- selected (wsh/lookup-selected state)]
- (when (= (count selected) 1)
- (let [group (get objects (first selected))
-
- rchanges [{:type :mod-obj
- :page-id page-id
- :id (:id group)
- :operations [{:type :set
- :attr :masked-group?
- :val nil}]}
- {:type :reg-objects
- :page-id page-id
- :shapes [(:id group)]}]
-
- uchanges [{:type :mod-obj
- :page-id page-id
- :id (:id group)
- :operations [{:type :set
- :attr :masked-group?
- :val (:masked-group? group)}]}
- {:type :reg-objects
- :page-id page-id
- :shapes [(:id group)]}]]
-
- (rx/of (dch/commit-changes {:redo-changes rchanges
- :undo-changes uchanges
- :origin it})
- (dwc/select-shapes (d/ordered-set (:id group))))))))))
+ changes-in-bulk (->> (wsh/lookup-selected state)
+ (map #(get objects %))
+ (filter #(or (= :bool (:type %)) (= :group (:type %))))
+ (map #(prepare-remove-mask page-id %)))
+ rchanges-in-bulk (into [] (mapcat first) changes-in-bulk)
+ uchanges-in-bulk (into [] (mapcat second) changes-in-bulk)]
+ (rx/of (dch/commit-changes {:redo-changes rchanges-in-bulk
+ :undo-changes uchanges-in-bulk
+ :origin it}))))))
diff --git a/frontend/src/app/main/data/workspace/layers.cljs b/frontend/src/app/main/data/workspace/layers.cljs
new file mode 100644
index 0000000000..4344e13e89
--- /dev/null
+++ b/frontend/src/app/main/data/workspace/layers.cljs
@@ -0,0 +1,81 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.data.workspace.layers
+ "Events related with layers transformations"
+ (:require
+ [app.common.data :as d]
+ [app.common.math :as mth]
+ [app.main.data.workspace.changes :as dch]
+ [app.main.data.workspace.state-helpers :as wsh]
+ [beicon.core :as rx]
+ [cuerdas.core :as str]
+ [potok.core :as ptk]))
+
+;; -- Opacity ----------------------------------------------------------
+
+;; The opacity of an element can be changed by typing numbers on the keyboard:
+;; 1 --> 0.1
+;; 2 --> 0.2
+;; 3 --> 0.3
+;; 4 --> 0.4
+;; ...
+;; 9 --> 0.9
+;; 0 --> 1
+;; 00 --> 0%
+;; The user can also type a more exact number:
+;; 45 --> 45%
+;; 05 --> 5%
+
+(defn calculate-opacity [numbers]
+ (let [total (->> numbers
+ (str/join "")
+ (d/parse-integer))]
+ (if (= numbers [0])
+ 1
+ (/ total (mth/pow 10 (count numbers))))))
+
+(defn set-opacity
+ [opacity]
+ (ptk/reify ::set-opacity
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [objects (wsh/lookup-page-objects state)
+ selected (wsh/lookup-selected state {:omit-blocked? true})
+ shapes (map #(get objects %) selected)
+ shapes-ids (->> shapes
+ (map :id))]
+ (rx/of (dch/update-shapes shapes-ids #(assoc % :opacity opacity)))))))
+
+(defn pressed-opacity
+ [opacity]
+ (let [same-event (js/Symbol "same-event")]
+ (ptk/reify ::pressed-opacity
+ IDeref
+ (-deref [_] opacity)
+
+ ptk/UpdateEvent
+ (update [_ state]
+ (if (nil? (:press-opacity-id state)) ;; avoiding duplicated events
+ (assoc state :press-opacity-id same-event)
+ state))
+
+ ptk/WatchEvent
+ (watch [_ state stream]
+ (if (not= same-event (:press-opacity-id state))
+ (rx/empty)
+ (let [opacity-events (->> stream ;; Stop buffering after time without opacities
+ (rx/filter (ptk/type? ::pressed-opacity))
+ (rx/buffer-time 600)
+ (rx/first)
+ (rx/map #(set-opacity (calculate-opacity (map deref %)))))]
+ (rx/concat
+ (rx/of (set-opacity (calculate-opacity [opacity]))) ;; First opacity is always fired
+ (rx/merge
+ opacity-events
+ (rx/of (pressed-opacity opacity)))
+ (rx/of (fn [state]
+ (dissoc state :press-opacity-id))))))))))
diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs
index be4d97e8f7..fa8132e043 100644
--- a/frontend/src/app/main/data/workspace/libraries.cljs
+++ b/frontend/src/app/main/data/workspace/libraries.cljs
@@ -635,6 +635,20 @@
:origin it
:file-id file-id})))))))
+(defn update-component-sync
+ [shape-id file-id]
+ (ptk/reify ::update-component-sync
+ ptk/WatchEvent
+ (watch [_ state _]
+ (let [current-file-id (:current-file-id state)]
+ (rx/of
+ (dwu/start-undo-transaction)
+ (update-component shape-id)
+ (sync-file current-file-id file-id)
+ (when (not= current-file-id file-id)
+ (sync-file file-id file-id))
+ (dwu/commit-undo-transaction))))))
+
(declare sync-file-2nd-stage)
(defn sync-file
@@ -690,7 +704,7 @@
;; update to finish, before marking this file as synced.
;; TODO: look for a more precise way of syncing this.
;; Maybe by using the stream (second argument passed to watch)
- ;; to wait for the corresponding changes-commited and then proced
+ ;; to wait for the corresponding changes-committed and then proceed
;; with the :update-sync mutation.
(rx/concat (rx/timer 3000)
(rp/mutation :update-sync
diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
index 48a77a9e9e..b917a971d6 100644
--- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs
@@ -53,9 +53,11 @@
(declare make-change)
(defn concat-changes
- [[rchanges1 uchanges1] [rchanges2 uchanges2]]
- [(d/concat-vec rchanges1 rchanges2)
- (d/concat-vec uchanges1 uchanges2)])
+ [& rest]
+ (letfn [(concat-changes' [[rchanges1 uchanges1] [rchanges2 uchanges2]]
+ [(d/concat-vec rchanges1 rchanges2)
+ (d/concat-vec uchanges1 uchanges2)])]
+ (transduce (remove nil?) concat-changes' empty-changes rest)))
(defn get-local-file
[state]
@@ -128,11 +130,12 @@
(if (and (= (count shapes) 1)
(:component-id (first shapes)))
empty-changes
- (let [[group rchanges uchanges]
+ (let [name (if (= 1 (count shapes)) (:name (first shapes)) "Component-1")
+ [group rchanges uchanges]
(if (and (= (count shapes) 1)
(= (:type (first shapes)) :group))
[(first shapes) [] []]
- (dwg/prepare-create-group objects page-id shapes "Component-1" true))
+ (dwg/prepare-create-group objects page-id shapes name true))
;; Asserts for documentation purposes
_ (us/assert vector? rchanges)
@@ -144,7 +147,7 @@
rchanges (conj rchanges
{:type :add-component
:id (:id new-shape)
- :name (:name new-shape)
+ :name name
:shapes new-shapes})
rchanges (into rchanges
@@ -506,7 +509,7 @@
(let [typographies (get-assets library-id :typographies state)
update-node (fn [node]
(if-let [typography (get typographies (:typography-ref-id node))]
- (merge node (d/without-keys typography [:name :id]))
+ (merge node (dissoc typography :name :id))
(dissoc node :typography-ref-id
:typography-ref-file)))]
(generate-sync-text-shape shape container update-node)))
@@ -663,26 +666,22 @@
[rchanges uchanges]
(concat-changes
- (update-attrs shape-inst
- shape-main
- root-inst
- root-main
- container
- omit-touched?)
- (concat-changes
- (if reset?
- (change-touched shape-inst
- shape-main
- container
- {:reset-touched? true})
- empty-changes)
- (concat-changes
- (if clear-remote-synced?
- (change-remote-synced shape-inst container nil)
- empty-changes)
- (if set-remote-synced?
- (change-remote-synced shape-inst container true)
- empty-changes))))
+ (update-attrs shape-inst
+ shape-main
+ root-inst
+ root-main
+ container
+ omit-touched?)
+ (when reset?
+ (change-touched shape-inst
+ shape-main
+ container
+ {:reset-touched? true}))
+ (when clear-remote-synced?
+ (change-remote-synced shape-inst container nil))
+
+ (when set-remote-synced?
+ (change-remote-synced shape-inst container true)))
children-inst (mapv #(cp/get-shape container %)
(:shapes shape-inst))
@@ -792,23 +791,19 @@
root-inst
component-container
omit-touched?)
- (concat-changes
- (change-touched shape-inst
- shape-main
- container
- {:reset-touched? true})
- (concat-changes
- (change-touched shape-main
- shape-inst
- component-container
- {:copy-touched? true})
- (concat-changes
- (if clear-remote-synced?
- (change-remote-synced shape-inst container nil)
- empty-changes)
- (if set-remote-synced?
- (change-remote-synced shape-inst container true)
- empty-changes)))))
+ (change-touched shape-inst
+ shape-main
+ container
+ {:reset-touched? true})
+ (change-touched shape-main
+ shape-inst
+ component-container
+ {:copy-touched? true})
+ (when clear-remote-synced?
+ (change-remote-synced shape-inst container nil))
+
+ (when set-remote-synced?
+ (change-remote-synced shape-inst container true)))
children-inst (mapv #(cp/get-shape container %)
(:shapes shape-inst))
@@ -876,61 +871,50 @@
[children-inst children-main only-inst-cb only-main-cb both-cb moved-cb inverse?]
(loop [children-inst (seq (or children-inst []))
children-main (seq (or children-main []))
- [rchanges uchanges] [[] []]]
+ changes [[] []]]
(let [child-inst (first children-inst)
child-main (first children-main)]
(cond
(and (nil? child-inst) (nil? child-main))
- [rchanges uchanges]
+ changes
(nil? child-inst)
- (reduce (fn [changes child]
- (concat-changes changes (only-main-cb child)))
- [rchanges uchanges]
- children-main)
+ (transduce (map only-main-cb) concat-changes changes children-main)
(nil? child-main)
- (reduce (fn [changes child]
- (concat-changes changes (only-inst-cb child)))
- [rchanges uchanges]
- children-inst)
+ (transduce (map only-inst-cb) concat-changes changes children-inst)
:else
(if (cp/is-main-of child-main child-inst)
(recur (next children-inst)
(next children-main)
- (concat-changes [rchanges uchanges]
- (both-cb child-inst child-main)))
+ (concat-changes changes (both-cb child-inst child-main)))
- (let [child-inst' (d/seek #(cp/is-main-of child-main %)
- children-inst)
- child-main' (d/seek #(cp/is-main-of % child-inst)
- children-main)]
+ (let [child-inst' (d/seek #(cp/is-main-of child-main %) children-inst)
+ child-main' (d/seek #(cp/is-main-of % child-inst) children-main)]
(cond
(nil? child-inst')
(recur children-inst
(next children-main)
- (concat-changes [rchanges uchanges]
- (only-main-cb child-main)))
+ (concat-changes changes (only-main-cb child-main)))
(nil? child-main')
(recur (next children-inst)
children-main
- (concat-changes [rchanges uchanges]
- (only-inst-cb child-inst)))
+ (concat-changes changes (only-inst-cb child-inst)))
:else
(if inverse?
(recur (next children-inst)
(remove #(= (:id %) (:id child-main')) children-main)
- (-> [rchanges uchanges]
- (concat-changes (both-cb child-inst' child-main))
- (concat-changes (moved-cb child-inst child-main'))))
+ (concat-changes changes
+ (both-cb child-inst' child-main)
+ (moved-cb child-inst child-main')))
(recur (remove #(= (:id %) (:id child-inst')) children-inst)
(next children-main)
- (-> [rchanges uchanges]
- (concat-changes (both-cb child-inst child-main'))
- (concat-changes (moved-cb child-inst' child-main))))))))))))
+ (concat-changes changes
+ (both-cb child-inst child-main')
+ (moved-cb child-inst' child-main)))))))))))
(defn- add-shape-to-instance
[component-shape index component container root-instance root-main omit-touched? set-remote-synced?]
diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs
index ac5fdbe12b..73172018c1 100644
--- a/frontend/src/app/main/data/workspace/notifications.cljs
+++ b/frontend/src/app/main/data/workspace/notifications.cljs
@@ -131,17 +131,17 @@
;; --- Handle: Presence
(def ^:private presence-palette
- #{"#2e8b57" ; seagreen
- "#808000" ; olive
- "#b22222" ; firebrick
- "#ff8c00" ; darkorage
- "#ffd700" ; gold
- "#ba55d3" ; mediumorchid
- "#00fa9a" ; mediumspringgreen
- "#00bfff" ; deepskyblue
- "#dda0dd" ; plum
- "#ff1493" ; deeppink
- "#ffa07a" ; lightsalmon
+ #{"#02bf51" ; darkpastelgreen text white
+ "#00fa9a" ; mediumspringgreen text black
+ "#b22222" ; firebrick text white
+ "#ff8c00" ; darkorage text white
+ "#ffd700" ; gold text black
+ "#ba55d3" ; mediumorchid text white
+ "#dda0dd" ; plum text black
+ "#008ab8" ; blueNCS text white
+ "#00bfff" ; deepskyblue text white
+ "#ff1493" ; deeppink text white
+ "#ffafda" ; carnationpink text black
})
(defn handle-presence
@@ -152,23 +152,26 @@
(remove nil?))
used (into #{} xfm presence)
avail (set/difference presence-palette used)]
- (or (first avail) "#000000")))
+ (or (first avail) "var(--color-black)")))
(update-color [color presence]
(if (some? color)
color
(get-next-color presence)))
- (update-sesion [session presence]
+ (update-session [session presence]
(-> session
(assoc :id session-id)
(assoc :profile-id profile-id)
(assoc :updated-at (dt/now))
- (update :color update-color presence)))
+ (update :color update-color presence)
+ (assoc :text-color (if (contains? ["#00fa9a" "#ffd700" "#dda0dd" "#ffafda"] (update-color (:color presence) presence))
+ "#000"
+ "#fff"))))
(update-presence [presence]
(-> presence
- (update session-id update-sesion presence)
+ (update session-id update-session presence)
(d/without-nils)))
]
diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs
index ba0700bf3f..c31745babd 100644
--- a/frontend/src/app/main/data/workspace/path/changes.cljs
+++ b/frontend/src/app/main/data/workspace/path/changes.cljs
@@ -29,7 +29,12 @@
[old-points old-selrect] (helpers/content->points+selrect shape old-content)
[new-points new-selrect] (helpers/content->points+selrect shape new-content)
- rch (if (empty? new-content)
+ rch (cond
+ ;; https://tree.taiga.io/project/penpot/issue/2366
+ (nil? shape-id)
+ []
+
+ (empty? new-content)
[{:type :del-obj
:id shape-id
:page-id page-id}
@@ -37,6 +42,7 @@
:page-id page-id
:shapes [shape-id]}]
+ :else
[{:type :mod-obj
:id shape-id
:page-id page-id
@@ -47,7 +53,12 @@
:page-id page-id
:shapes [shape-id]}])
- uch (if (empty? new-content)
+ uch (cond
+ ;; https://tree.taiga.io/project/penpot/issue/2366
+ (nil? shape-id)
+ []
+
+ (empty? new-content)
[{:type :add-obj
:id shape-id
:obj shape
@@ -58,6 +69,8 @@
{:type :reg-objects
:page-id page-id
:shapes [shape-id]}]
+
+ :else
[{:type :mod-obj
:id shape-id
:page-id page-id
diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs
index 9df8b6e9a5..f25960d442 100644
--- a/frontend/src/app/main/data/workspace/path/edition.cljs
+++ b/frontend/src/app/main/data/workspace/path/edition.cljs
@@ -161,7 +161,7 @@
points (upg/content->points content)]
(rx/concat
- ;; This stream checks the consecutive mouse positions to do the draging
+ ;; This stream checks the consecutive mouse positions to do the dragging
(->> points
(streams/move-points-stream snap-toggled start-position selected-points)
(rx/take-until stopper)
diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs
index a7d47d238f..8ce3ca9c73 100644
--- a/frontend/src/app/main/data/workspace/path/helpers.cljs
+++ b/frontend/src/app/main/data/workspace/path/helpers.cljs
@@ -108,7 +108,7 @@
:params position})))
(defn append-node
- "Creates a new node in the path. Usualy used when drawing."
+ "Creates a new node in the path. Usually used when drawing."
[shape position prev-point prev-handler]
(let [command (next-node shape position prev-point prev-handler)]
(-> shape
diff --git a/frontend/src/app/main/data/workspace/path/shortcuts.cljs b/frontend/src/app/main/data/workspace/path/shortcuts.cljs
index 60c7eae67c..15cd8182a1 100644
--- a/frontend/src/app/main/data/workspace/path/shortcuts.cljs
+++ b/frontend/src/app/main/data/workspace/path/shortcuts.cljs
@@ -28,12 +28,12 @@
(get-in state [:workspace-local :edition]))
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
(if-not (= :draw path-edit-mode)
- (rx/of :interrupt (dw/deselect-all true))
+ (rx/of :interrupt)
(rx/empty))))))
(def shortcuts
- {:move-nodes {:tooltip "V"
- :command "v"
+ {:move-nodes {:tooltip "M"
+ :command "m"
:fn #(st/emit! (drp/change-edit-mode :move))}
:draw-nodes {:tooltip "P"
@@ -60,12 +60,12 @@
:command "k"
:fn #(st/emit! (drp/separate-nodes))}
- :make-corner {:tooltip "B"
- :command "b"
+ :make-corner {:tooltip "X"
+ :command "x"
:fn #(st/emit! (drp/make-corner))}
- :make-curve {:tooltip (ds/meta "B")
- :command (ds/c-mod "b")
+ :make-curve {:tooltip "C"
+ :command "c"
:fn #(st/emit! (drp/make-curve))}
:snap-nodes {:tooltip (ds/meta "'")
@@ -73,13 +73,9 @@
:fn #(st/emit! (drp/toggle-snap))}
:escape {:tooltip (ds/esc)
- :command "escape"
+ :command ["escape" "enter" "v"]
:fn #(st/emit! (esc-pressed))}
- :start-editing {:tooltip (ds/enter)
- :command "enter"
- :fn #(st/emit! (dw/start-editing-selected))}
-
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! (drp/undo-path))}
@@ -142,9 +138,7 @@
:move-unit-right {:tooltip ds/left-arrow
:command "left"
- :fn #(st/emit! (drp/move-selected :left false))}
-
- })
+ :fn #(st/emit! (drp/move-selected :left false))}})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs
index 0b00dc0994..382f13717d 100644
--- a/frontend/src/app/main/data/workspace/path/state.cljs
+++ b/frontend/src/app/main/data/workspace/path/state.cljs
@@ -24,7 +24,7 @@
ks)))
(defn get-path
- "Retrieves the location of the path object and additionaly can pass
+ "Retrieves the location of the path object and additionally can pass
the arguments. This location can be used in get-in, assoc-in... functions"
[state & ks]
(let [path-loc (get-path-location state)
diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs
index f16b637836..eb89d83b2e 100644
--- a/frontend/src/app/main/data/workspace/persistence.cljs
+++ b/frontend/src/app/main/data/workspace/persistence.cljs
@@ -39,7 +39,7 @@
[tubax.core :as tubax]))
(declare persist-changes)
-(declare persist-sychronous-changes)
+(declare persist-synchronous-changes)
(declare shapes-changes-persisted)
(declare update-persistence-status)
@@ -81,32 +81,32 @@
(obj/set! js/window "onbeforeunload" nil)
(st/emit! (update-persistence-status {:status :saved})))]
(->> (rx/merge
- (->> stream
- (rx/filter dch/commit-changes?)
- (rx/map deref)
- (rx/filter local-file?)
- (rx/tap on-dirty)
- (rx/buffer-until notifier)
- (rx/filter (complement empty?))
- (rx/map (fn [buf]
- (->> (into [] (comp (map #(assoc % :id (uuid/next)))
- (map #(assoc % :file-id file-id)))
- buf)
- (persist-changes file-id))))
- (rx/tap on-saving)
- (rx/take-until (rx/delay 100 stoper)))
- (->> stream
- (rx/filter dch/commit-changes?)
- (rx/map deref)
- (rx/filter library-file?)
- (rx/filter (complement #(empty? (:changes %))))
- (rx/map persist-sychronous-changes)
- (rx/take-until (rx/delay 100 stoper)))
- (->> stream
- (rx/filter (ptk/type? ::changes-persisted))
- (rx/tap on-saved)
- (rx/ignore)
- (rx/take-until stoper)))
+ (->> stream
+ (rx/filter dch/commit-changes?)
+ (rx/map deref)
+ (rx/filter local-file?)
+ (rx/tap on-dirty)
+ (rx/buffer-until notifier)
+ (rx/filter (complement empty?))
+ (rx/map (fn [buf]
+ (->> (into [] (comp (map #(assoc % :id (uuid/next)))
+ (map #(assoc % :file-id file-id)))
+ buf)
+ (persist-changes file-id))))
+ (rx/tap on-saving)
+ (rx/take-until (rx/delay 100 stoper)))
+ (->> stream
+ (rx/filter dch/commit-changes?)
+ (rx/map deref)
+ (rx/filter library-file?)
+ (rx/filter (complement #(empty? (:changes %))))
+ (rx/map persist-synchronous-changes)
+ (rx/take-until (rx/delay 100 stoper)))
+ (->> stream
+ (rx/filter (ptk/type? ::changes-persisted))
+ (rx/tap on-saved)
+ (rx/ignore)
+ (rx/take-until stoper)))
(rx/subs #(st/emit! %)
(constantly nil)
(fn []
@@ -168,7 +168,7 @@
(rx/mapcat handle-response)
(rx/catch on-error)))))))
-(defn persist-sychronous-changes
+(defn persist-synchronous-changes
[{:keys [file-id changes]}]
(us/verify ::us/uuid file-id)
(ptk/reify ::persist-synchronous-changes
@@ -202,7 +202,7 @@
(s/def ::shapes-changes-persisted
(s/keys :req-un [::revn ::cp/changes]))
-(defn shapes-persited-event? [event]
+(defn shapes-persisted-event? [event]
(= (ptk/type event) ::changes-persisted))
(defn shapes-changes-persisted
@@ -377,7 +377,7 @@
(= (:code error) :media-type-not-allowed)
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
- (= (:code error) :ubable-to-access-to-url)
+ (= (:code error) :unable-to-access-to-url)
(rx/of (dm/error (tr "errors.media-type-not-allowed")))
(= (:code error) :invalid-image)
@@ -487,7 +487,7 @@
;; Media objects are blob of data to be upload
(process-blobs params))
- ;; Every stream has its own sideffect. We need to ignore the result
+ ;; Every stream has its own sideeffect. We need to ignore the result
(rx/ignore)
(handle-upload-error on-error)
(rx/finalize (st/emitf (dm/hide-tag :media-loading))))))))
@@ -520,25 +520,25 @@
(defn clone-media-object
[{:keys [file-id object-id] :as params}]
(us/assert ::clone-media-objects-params params)
- (ptk/reify ::clone-media-objects
- ptk/WatchEvent
- (watch [_ _ _]
- (let [{:keys [on-success on-error]
- :or {on-success identity
- on-error identity}} (meta params)
- params {:is-local true
- :file-id file-id
- :id object-id}]
+ (ptk/reify ::clone-media-objects
+ ptk/WatchEvent
+ (watch [_ _ _]
+ (let [{:keys [on-success on-error]
+ :or {on-success identity
+ on-error identity}} (meta params)
+ params {:is-local true
+ :file-id file-id
+ :id object-id}]
- (rx/concat
- (rx/of (dm/show {:content (tr "media.loading")
- :type :info
- :timeout nil
- :tag :media-loading}))
- (->> (rp/mutation! :clone-file-media-object params)
- (rx/do on-success)
- (rx/catch on-error)
- (rx/finalize #(st/emit! (dm/hide-tag :media-loading)))))))))
+ (rx/concat
+ (rx/of (dm/show {:content (tr "media.loading")
+ :type :info
+ :timeout nil
+ :tag :media-loading}))
+ (->> (rp/mutation! :clone-file-media-object params)
+ (rx/do on-success)
+ (rx/catch on-error)
+ (rx/finalize #(st/emit! (dm/hide-tag :media-loading)))))))))
;; --- Helpers
@@ -555,12 +555,17 @@
[ids]
(ptk/reify ::remove-thumbnails
ptk/WatchEvent
- (watch [_ _ _]
+ (watch [_ state _]
;; Removes the thumbnail while it's regenerated
- (rx/of (dch/update-shapes
- ids
- #(dissoc % :thumbnail)
- {:save-undo? false})))))
+ (let [moving? (= :move (get-in state [:workspace-local :transform]))
+ selected? (wsh/lookup-selected state)
+ ;; When we're moving the current frame it's safe to keep the thumbnail
+ ;; if it's resize we need to remove it immeditely
+ ids (cond->> ids moving? (remove selected?))]
+
+ (if (empty? ids)
+ (rx/empty)
+ (rx/of (dch/update-shapes ids #(dissoc % :thumbnail) {:save-undo? false})))))))
(defn update-frame-thumbnail
[frame-id]
@@ -581,7 +586,7 @@
{:save-undo? false}))))))
(defn- extract-frame-changes
- "Process a changes set in a commit to extract the frames that are channging"
+ "Process a changes set in a commit to extract the frames that are changing"
[[event [old-objects new-objects]]]
(let [changes (-> event deref :changes)
@@ -607,7 +612,8 @@
xform (comp (mapcat extract-ids)
(map get-frame-id)
(remove nil?)
- (filter #(not= uuid/zero %)))]
+ (filter #(not= uuid/zero %))
+ (filter #(contains? new-objects %)))]
(into #{} xform changes)))
@@ -647,7 +653,8 @@
(rx/filter dch/commit-changes?)
(rx/filter (comp not thumbnail-change?))
(rx/with-latest-from objects-stream)
- (rx/map extract-frame-changes))
+ (rx/map extract-frame-changes)
+ (rx/share))
frames (-> state wsh/lookup-page-objects cp/select-frames)
no-thumb-frames (->> frames
@@ -658,7 +665,7 @@
(->> (rx/from no-thumb-frames)
(rx/map #(update-frame-thumbnail %)))
- ;; We remove the thumbnails inmediately but defer their generation
+ ;; We remove the thumbnails immediately but defer their generation
(rx/merge
(->> frame-changes
(rx/take-until stopper)
diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs
index ce5ff4160b..5763b5e840 100644
--- a/frontend/src/app/main/data/workspace/selection.cljs
+++ b/frontend/src/app/main/data/workspace/selection.cljs
@@ -65,27 +65,33 @@
(watch [_ state stream]
(let [zoom (get-in state [:workspace-local :zoom] 1)
stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event)))
- stoper (->> stream (rx/filter stop?))]
+ stoper (->> stream (rx/filter stop?))
+
+ calculate-selrect
+ (fn [data pos]
+ (if data
+ (assoc data :stop pos)
+ {:start pos :stop pos}))
+
+ selrect-stream
+ (->> ms/mouse-position
+ (rx/scan calculate-selrect nil)
+ (rx/map data->selrect)
+ (rx/filter #(or (> (:width %) (/ 10 zoom))
+ (> (:height %) (/ 10 zoom))))
+ (rx/take-until stoper))]
(rx/concat
- (when-not preserve?
- (rx/of (deselect-all)))
- (->> ms/mouse-position
- (rx/scan (fn [data pos]
- (if data
- (assoc data :stop pos)
- {:start pos :stop pos}))
- nil)
- (rx/map data->selrect)
- (rx/filter #(or (> (:width %) (/ 10 zoom))
- (> (:height %) (/ 10 zoom))))
+ (if preserve?
+ (rx/empty)
+ (rx/of (deselect-all)))
- (rx/flat-map
- (fn [selrect]
- (rx/of (update-selrect selrect)
- (select-shapes-by-current-selrect preserve?))))
+ (rx/merge
+ (->> selrect-stream (rx/map update-selrect))
+ (->> selrect-stream
+ (rx/debounce 50)
+ (rx/map #(select-shapes-by-current-selrect preserve?))))
- (rx/take-until stoper))
- (rx/of (update-selrect nil))))))))
+ (rx/of (update-selrect nil))))))))
;; --- Toggle shape's selection status (selected or deselected)
@@ -178,11 +184,13 @@
is-not-blocked (fn [shape-id] (not (get-in state [:workspace-data
:pages-index page-id
:objects shape-id
- :blocked] false)))]
- (rx/of (->> new-selected
- (filter is-not-blocked)
- (into lks/empty-linked-set)
- (select-shapes)))))))
+ :blocked] false)))
+
+ selected-ids (into lks/empty-linked-set
+ (comp (filter some?)
+ (filter is-not-blocked))
+ new-selected)]
+ (rx/of (select-shapes selected-ids))))))
(defn deselect-all
"Clear all possible state of drawing, edition
@@ -221,11 +229,13 @@
selrect (get-in state [:workspace-local :selrect])
blocked? (fn [id] (get-in objects [id :blocked] false))]
(when selrect
- (->> (uw/ask! {:cmd :selection/query
- :page-id page-id
- :rect selrect
- :include-frames? true
- :full-frame? true})
+ (rx/empty)
+ (->> (uw/ask-buffered!
+ {:cmd :selection/query
+ :page-id page-id
+ :rect selrect
+ :include-frames? true
+ :full-frame? true})
(rx/map #(cp/clean-loops objects %))
(rx/map #(into initial-set (filter (comp not blocked?)) %))
(rx/map select-shapes)))))))
@@ -307,7 +317,7 @@
chgs))))
(defn duplicate-changes-update-indices
- "Parses the change set when duplicating to set-up the appropiate indices"
+ "Parses the change set when duplicating to set-up the appropriate indices"
[objects ids changes]
(let [process-id
@@ -441,7 +451,7 @@
(ptk/reify ::duplicate-selected
ptk/WatchEvent
(watch [it state _]
- (when (nil? (get-in state [:workspace-local :transform]))
+ (when (or (not move-delta?) (nil? (get-in state [:workspace-local :transform])))
(let [page-id (:current-page-id state)
objects (wsh/lookup-page-objects state page-id)
selected (wsh/lookup-selected state)
@@ -467,10 +477,10 @@
id-duplicated (when (= (count selected) 1) (first selected))]
- (rx/of (dch/commit-changes {:redo-changes rchanges
+ (rx/of (select-shapes selected)
+ (dch/commit-changes {:redo-changes rchanges
:undo-changes uchanges
:origin it})
- (select-shapes selected)
(memorize-duplicated id-original id-duplicated)))))))
(defn change-hover-state
diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs
index 87d4735a7c..939f9e26ed 100644
--- a/frontend/src/app/main/data/workspace/shortcuts.cljs
+++ b/frontend/src/app/main/data/workspace/shortcuts.cljs
@@ -11,6 +11,7 @@
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
+ [app.main.data.workspace.layers :as dwly]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwtxt]
[app.main.data.workspace.transforms :as dwt]
@@ -24,7 +25,7 @@
;; Shortcuts format https://github.com/ccampbell/mousetrap
-(def shortcuts
+(def base-shortcuts
{:toggle-layers {:tooltip (ds/alt "L")
:command (ds/a-mod "l")
:fn #(st/emit! (dw/go-to-layout :layers))}
@@ -137,6 +138,10 @@
:command "a"
:fn #(st/emit! (dwd/select-for-drawing :frame))}
+ :move {:tooltip "V"
+ :command "v"
+ :fn #(st/emit! :interrupt)}
+
:draw-rect {:tooltip "R"
:command "r"
:fn #(st/emit! (dwd/select-for-drawing :rect))}
@@ -233,12 +238,20 @@
:open-color-picker {:tooltip "I"
:command "i"
- :fn #(st/emit! (mdc/picker-for-selected-shape ))}
+ :fn #(st/emit! (mdc/picker-for-selected-shape))}
:open-viewer {:tooltip "G V"
:command "g v"
:fn #(st/emit! (dw/go-to-viewer))}
+ :open-handoff {:tooltip "G H"
+ :command "g h"
+ :fn #(st/emit! (dw/go-to-viewer {:section :handoff}))}
+
+ :open-comments {:tooltip "G C"
+ :command "g c"
+ :fn #(st/emit! (dw/go-to-viewer {:section :comments}))}
+
:open-dashboard {:tooltip "G D"
:command "g d"
:fn #(st/emit! (dw/go-to-dashboard))}
@@ -261,23 +274,80 @@
:type "keyup"
:fn #(st/emit! (dw/toggle-distances-display false))}
- :boolean-union {:tooltip (ds/alt "U")
- :command "alt+u"
- :fn #(st/emit! (dw/create-bool :union))}
+ :bool-union {:tooltip (ds/meta (ds/alt "U"))
+ :command (ds/c-mod "alt+u")
+ :fn #(st/emit! (dw/create-bool :union))}
- :boolean-difference {:tooltip (ds/alt "D")
- :command "alt+d"
- :fn #(st/emit! (dw/create-bool :difference))}
+ :bool-difference {:tooltip (ds/meta (ds/alt "D"))
+ :command (ds/c-mod "alt+d")
+ :fn #(st/emit! (dw/create-bool :difference))}
- :boolean-intersection {:tooltip (ds/alt "I")
- :command "alt+i"
+ :bool-intersection {:tooltip (ds/meta (ds/alt "I"))
+ :command (ds/c-mod "alt+i")
:fn #(st/emit! (dw/create-bool :intersection))}
- :boolean-exclude {:tooltip (ds/alt "E")
- :command "alt+e"
+ :bool-exclude {:tooltip (ds/meta (ds/alt "E"))
+ :command (ds/c-mod "alt+e")
:fn #(st/emit! (dw/create-bool :exclude))}
- })
+ :align-left {:tooltip (ds/alt "A")
+ :command "alt+a"
+ :fn #(st/emit! (dw/align-objects :hleft))}
+
+ :align-right {:tooltip (ds/alt "D")
+ :command "alt+d"
+ :fn #(st/emit! (dw/align-objects :hright))}
+
+ :align-top {:tooltip (ds/alt "W")
+ :command "alt+w"
+ :fn #(st/emit! (dw/align-objects :vtop))}
+
+ :align-hcenter {:tooltip (ds/alt "H")
+ :command "alt+h"
+ :fn #(st/emit! (dw/align-objects :hcenter))}
+
+ :align-vcenter {:tooltip (ds/alt "V")
+ :command "alt+v"
+ :fn #(st/emit! (dw/align-objects :vcenter))}
+
+ :align-bottom {:tooltip (ds/alt "S")
+ :command "alt+s"
+ :fn #(st/emit! (dw/align-objects :vbottom))}
+
+ :h-distribute {:tooltip (ds/meta-shift (ds/alt "H"))
+ :command (ds/c-mod "shift+alt+h")
+ :fn #(st/emit! (dw/distribute-objects :horizontal))}
+
+ :v-distribute {:tooltip (ds/meta-shift (ds/alt "V"))
+ :command (ds/c-mod "shift+alt+v")
+ :fn #(st/emit! (dw/distribute-objects :vertical))}
+
+ :toggle-visibility {:tooltip (ds/meta-shift "H")
+ :command (ds/c-mod "shift+h")
+ :fn #(st/emit! (dw/toggle-visibility-selected))}
+
+ :toggle-lock {:tooltip (ds/meta-shift "L")
+ :command (ds/c-mod "shift+l")
+ :fn #(st/emit! (dw/toggle-lock-selected))}
+
+ :toggle-lock-size {:tooltip (ds/meta (ds/alt "L"))
+ :command (ds/c-mod "alt+l")
+ :fn #(st/emit! (dw/toggle-proportion-lock))}
+
+ :create-artboard-from-selection {:tooltip (ds/meta (ds/alt "G"))
+ :command (ds/c-mod "alt+g")
+ :fn #(st/emit! (dw/create-artboard-from-selection))}})
+
+(def opacity-shortcuts
+ (into {} (->>
+ (range 10)
+ (map (fn [n] [(keyword (str "opacity-" n))
+ {:tooltip (str n)
+ :command (str n)
+ :fn #(st/emit! (dwly/pressed-opacity n))}])))))
+
+(def shortcuts
+ (merge base-shortcuts opacity-shortcuts))
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs
index 0710aba559..f144670bac 100644
--- a/frontend/src/app/main/data/workspace/state_helpers.cljs
+++ b/frontend/src/app/main/data/workspace/state_helpers.cljs
@@ -25,6 +25,10 @@
([state component-id]
(get-in state [:workspace-data :components component-id :objects])))
+(defn lookup-local-components
+ ([state]
+ (get-in state [:workspace-data :components])))
+
(defn lookup-selected
([state]
(lookup-selected state nil))
diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs
index 5cb469e1b9..118fbc0dad 100644
--- a/frontend/src/app/main/data/workspace/svg_upload.cljs
+++ b/frontend/src/app/main/data/workspace/svg_upload.cljs
@@ -416,7 +416,7 @@
reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)]
(reduce reducer-fn [unames changes] (d/enumerate children)))
- ;; Cannot create the data from curren tags
+ ;; Cannot create the data from current tags
[unames [rchs uchs]])))
(declare create-svg-shapes)
@@ -427,7 +427,7 @@
ptk/WatchEvent
(watch [_ _ _]
;; Once the SVG is uploaded, we need to extract all the bitmap
- ;; images and upload them separatelly, then proceed to create
+ ;; images and upload them separately, then proceed to create
;; all shapes.
(->> (rx/from (usvg/collect-images svg-data))
(rx/map (fn [uri]
diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs
index 54421964eb..04d6b0eb9d 100644
--- a/frontend/src/app/main/data/workspace/texts.cljs
+++ b/frontend/src/app/main/data/workspace/texts.cljs
@@ -300,7 +300,7 @@
;; Stop buffering after time without resizes
stop-buffer (->> resize-events (rx/debounce 100))
- ;; Agregates the resizes so only send the resize when the sizes are stable
+ ;; Aggregates the resizes so only send the resize when the sizes are stable
resize-batch
(->> resize-events
(rx/take-until stop-buffer)
diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs
index 2905546d04..94c3c3af0f 100644
--- a/frontend/src/app/main/data/workspace/transforms.cljs
+++ b/frontend/src/app/main/data/workspace/transforms.cljs
@@ -106,10 +106,9 @@
;; apply-modifiers event is done, that consolidates all modifiers into the base
;; geometric attributes of the shapes.
-(declare set-modifiers-recursive)
-(declare check-delta)
-(declare set-local-displacement)
(declare clear-local-transform)
+(declare set-modifiers-recursive)
+(declare get-ignore-tree)
(defn- set-modifiers
([ids] (set-modifiers ids nil false))
@@ -120,21 +119,17 @@
ptk/UpdateEvent
(update [_ state]
(let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {}))
- page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
- ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)]
+ page-id (:current-page-id state)
+ objects (wsh/lookup-page-objects state page-id)
+ ids (into #{} (remove #(get-in objects [% :blocked] false)) ids)
- (reduce (fn [state id]
- (update state :workspace-modifiers
- #(set-modifiers-recursive %
- objects
- (get objects id)
- modifiers
- nil
- nil
- ignore-constraints)))
- state
- ids))))))
+ setup-modifiers
+ (fn [state id]
+ (let [shape (get objects id)]
+ (update state :workspace-modifiers
+ #(set-modifiers-recursive % objects shape modifiers ignore-constraints))))]
+
+ (reduce setup-modifiers state ids))))))
;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints).
(defn- set-rotation-modifiers
@@ -170,15 +165,14 @@
children-ids (->> ids (mapcat #(cp/get-children % objects)))
ids-with-children (d/concat-vec children-ids ids)
object-modifiers (get state :workspace-modifiers)
- ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)]
+ ignore-tree (get-ignore-tree object-modifiers objects ids)]
(rx/of (dwu/start-undo-transaction)
(dch/update-shapes
ids-with-children
(fn [shape]
- (-> shape
- (merge (get object-modifiers (:id shape)))
- (gsh/transform-shape)))
+ (let [modif (get object-modifiers (:id shape))]
+ (gsh/transform-shape (merge shape modif))))
{:reg-objects? true
:ignore-tree ignore-tree
;; Attributes that can change in the transform. This way we don't have to check
@@ -195,75 +189,83 @@
(clear-local-transform)
(dwu/commit-undo-transaction))))))
-(defn- set-modifiers-recursive
- [modif-tree objects shape modifiers root transformed-root ignore-constraints]
- (let [children (->> (get shape :shapes [])
- (map #(get objects %)))
-
- transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers))
-
- [root transformed-root ignore-geometry?]
- (check-delta shape root transformed-shape transformed-root objects)
-
- modifiers (assoc modifiers :ignore-geometry? ignore-geometry?)
-
- set-child (fn [modif-tree child]
- (let [child-modifiers (gsh/calc-child-modifiers shape
- child
- modifiers
- ignore-constraints)]
- (set-modifiers-recursive modif-tree
- objects
- child
- child-modifiers
- root
- transformed-root
- ignore-constraints)))]
- (reduce set-child
- (assoc-in modif-tree [(:id shape) :modifiers] modifiers)
- children)))
-
(defn- check-delta
"If the shape is a component instance, check its relative position respect the
root of the component, and see if it changes after applying a transformation."
[shape root transformed-shape transformed-root objects]
- (let [root (cond
- (:component-root? shape)
- shape
+ (let [root
+ (cond
+ (:component-root? shape)
+ shape
- (nil? root)
- (cp/get-root-shape shape objects)
+ (nil? root)
+ (cp/get-root-shape shape objects)
- :else root)
+ :else root)
- transformed-root (cond
- (:component-root? transformed-shape)
- transformed-shape
+ transformed-root
+ (cond
+ (:component-root? transformed-shape)
+ transformed-shape
- (nil? transformed-root)
- (cp/get-root-shape transformed-shape objects)
+ (nil? transformed-root)
+ (cp/get-root-shape transformed-shape objects)
- :else transformed-root)
+ :else transformed-root)
- shape-delta (when root
- (gpt/point (- (:x shape) (:x root))
- (- (:y shape) (:y root))))
+ shape-delta
+ (when root
+ (gpt/point (- (:x shape) (:x root))
+ (- (:y shape) (:y root))))
- transformed-shape-delta (when transformed-root
- (gpt/point (- (:x transformed-shape) (:x transformed-root))
- (- (:y transformed-shape) (:y transformed-root))))
+ transformed-shape-delta
+ (when transformed-root
+ (gpt/point (- (:x transformed-shape) (:x transformed-root))
+ (- (:y transformed-shape) (:y transformed-root))))
ignore-geometry? (= shape-delta transformed-shape-delta)]
[root transformed-root ignore-geometry?]))
-(defn- set-local-displacement [point]
- (ptk/reify ::start-local-displacement
- ptk/UpdateEvent
- (update [_ state]
- (let [mtx (gmt/translate-matrix point)]
- (-> state
- (assoc-in [:workspace-local :modifiers] {:displacement mtx}))))))
+(defn- set-modifiers-recursive
+ [modif-tree objects shape modifiers ignore-constraints]
+ (let [children (map (d/getf objects) (:shapes shape))
+ transformed-rect (gsh/transform-selrect (:selrect shape) modifiers)
+
+ set-child
+ (fn [modif-tree child]
+ (let [child-modifiers (gsh/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect)]
+ (cond-> modif-tree
+ (not (gsh/empty-modifiers? child-modifiers))
+ (set-modifiers-recursive objects child child-modifiers ignore-constraints))))
+
+ modif-tree
+ (-> modif-tree
+ (assoc-in [(:id shape) :modifiers] modifiers))]
+
+ (reduce set-child modif-tree children)))
+
+(defn- get-ignore-tree
+ "Retrieves a map with the flag `ignore-tree` given a tree of modifiers"
+ ([modif-tree objects shape]
+ (get-ignore-tree modif-tree objects shape nil nil {}))
+
+ ([modif-tree objects shape root transformed-root ignore-tree]
+ (let [children (map (d/getf objects) (:shapes shape))
+
+ shape-id (:id shape)
+ transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id)))
+
+ [root transformed-root ignore-geometry?]
+ (check-delta shape root transformed-shape transformed-root objects)
+
+ ignore-tree (assoc ignore-tree shape-id ignore-geometry?)
+
+ set-child
+ (fn [modif-tree child]
+ (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))]
+
+ (reduce set-child ignore-tree children))))
(defn- clear-local-transform []
(ptk/reify ::clear-local-transform
@@ -271,7 +273,7 @@
(update [_ state]
(-> state
(dissoc :workspace-modifiers)
- (update :workspace-local dissoc :modifiers :current-move-selected)))))
+ (update :workspace-local dissoc :current-move-selected)))))
;; -- Resize --------------------------------------------------------
@@ -366,7 +368,7 @@
(assoc-in [:workspace-local :transform] :resize)))
ptk/WatchEvent
- (watch [it state stream]
+ (watch [_ state stream]
(let [initial-position @ms/mouse-position
stoper (rx/filter ms/mouse-up? stream)
layout (:workspace-layout state)
@@ -406,26 +408,13 @@
(let [shape (get objects id)
modifiers (gsh/resize-modifiers shape attr value)]
(update state :workspace-modifiers
- #(set-modifiers-recursive %
- objects
- shape
- modifiers
- nil
- nil
- false))))
+ #(set-modifiers-recursive % objects shape modifiers false))))
state
ids)))
ptk/WatchEvent
- (watch [_ state _]
- (let [page-id (:current-page-id state)
- objects (wsh/lookup-page-objects state page-id)
-
- ;; TODO: looks completly redundant operation because
- ;; apply-modifiers already finds all children.
- ids (d/concat-vec ids (mapcat #(cp/get-children % objects) ids))]
- (rx/of (apply-modifiers ids))))))
-
+ (watch [_ _ _]
+ (rx/of (apply-modifiers ids)))))
;; -- Rotate --------------------------------------------------------
@@ -521,6 +510,11 @@
(defn- start-move-duplicate
[from-position]
(ptk/reify ::start-move-duplicate
+ ptk/UpdateEvent
+ (update [_ state]
+ (-> state
+ (assoc-in [:workspace-local :transform] :move)))
+
ptk/WatchEvent
(watch [_ _ stream]
(->> stream
@@ -575,11 +569,11 @@
(->> position
(rx/with-latest vector snap-delta)
(rx/map snap/correct-snap-point)
- (rx/map set-local-displacement)
+ (rx/map #(hash-map :displacement (gmt/translate-matrix %)))
+ (rx/map (partial set-modifiers ids))
(rx/take-until stopper))
- (rx/of (set-modifiers ids)
- (apply-modifiers ids)
+ (rx/of (apply-modifiers ids)
(calculate-frame-for-move ids)
(finish-transform)))))))))
@@ -622,11 +616,11 @@
(->> move-events
(rx/take-until stopper)
(rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0))
- (rx/map set-local-displacement))
+ (rx/map #(hash-map :displacement (gmt/translate-matrix %)))
+ (rx/map (partial set-modifiers selected)))
(rx/of (move-selected direction shift?)))
- (rx/of (set-modifiers selected)
- (apply-modifiers selected)
+ (rx/of (apply-modifiers selected)
(finish-transform))))
(rx/empty))))))
@@ -741,7 +735,3 @@
:displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}
true)
(apply-modifiers selected))))))
-
-
-;; -- Transform to path ---------------------------------------------
-
diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs
index dd2ded3ce6..d25c22ea5f 100644
--- a/frontend/src/app/main/data/workspace/undo.cljs
+++ b/frontend/src/app/main/data/workspace/undo.cljs
@@ -70,7 +70,8 @@
(accumulate-undo-entry state entry)
(add-undo-entry state entry)))))
-(defonce empty-tx {:undo-changes [] :redo-changes []})
+(def empty-tx
+ {:undo-changes [] :redo-changes []})
(defn start-undo-transaction []
(ptk/reify ::start-undo-transaction
diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs
index 6514e8332d..ce0a0eb5e6 100644
--- a/frontend/src/app/main/errors.cljs
+++ b/frontend/src/app/main/errors.cljs
@@ -65,7 +65,7 @@
(ts/schedule
(st/emitf (rt/assign-exception error))))
-;; Error that happens on an active bussines model validation does not
+;; Error that happens on an active business model validation does not
;; passes an validation (example: profile can't leave a team). From
;; the user perspective a error flash message should be visualized but
;; user can continue operate on the application.
@@ -81,7 +81,13 @@
(js/console.group "Validation Error:")
(ex/ignoring
(js/console.info
- (with-out-str (pprint error))))
+ (with-out-str (pprint (dissoc error :explain)))))
+
+ (when-let [explain (:explain error)]
+ (js/console.group "Spec explain:")
+ (js/console.log explain)
+ (js/console.groupEnd "Spec explain:"))
+
(js/console.groupEnd "Validation Error:"))
@@ -106,14 +112,14 @@
;; assertion (assertion that is preserved on production builds). From
;; the user perspective this should be treated as internal error.
(defmethod ptk/handle-error :assertion
- [{:keys [data stack message hint context] :as error}]
+ [{:keys [message hint] :as error}]
(let [message (or message hint)
message (str "Internal Assertion Error: " message)
context (str/fmt "ns: '%s'\nname: '%s'\nfile: '%s:%s'"
- (:ns context)
- (:name context)
- (str cf/public-uri "js/cljs-runtime/" (:file context))
- (:line context))]
+ (:ns error)
+ (:name error)
+ (str cf/public-uri "js/cljs-runtime/" (:file error))
+ (:line error))]
(ts/schedule
(st/emitf
(dm/show {:content "Internal error: assertion."
@@ -123,10 +129,7 @@
;; Print to the console some debugging info
(js/console.group message)
(js/console.info context)
- (js/console.groupCollapsed "Stack Trace")
- (js/console.info stack)
- (js/console.groupEnd "Stack Trace")
- (js/console.error (with-out-str (expound/printer data)))
+ (js/console.error (with-out-str (expound/printer error)))
(js/console.groupEnd message)))
;; This happens when the backed server fails to process the
diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs
deleted file mode 100644
index 726562f4cc..0000000000
--- a/frontend/src/app/main/exports.cljs
+++ /dev/null
@@ -1,330 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.main.exports
- "The main logic for SVG export functionality."
- (:require
- [app.common.geom.align :as gal]
- [app.common.geom.matrix :as gmt]
- [app.common.geom.point :as gpt]
- [app.common.geom.shapes :as gsh]
- [app.common.math :as mth]
- [app.common.pages :as cp]
- [app.common.uuid :as uuid]
- [app.main.ui.shapes.bool :as bool]
- [app.main.ui.shapes.circle :as circle]
- [app.main.ui.shapes.embed :as embed]
- [app.main.ui.shapes.export :as use]
- [app.main.ui.shapes.frame :as frame]
- [app.main.ui.shapes.group :as group]
- [app.main.ui.shapes.image :as image]
- [app.main.ui.shapes.path :as path]
- [app.main.ui.shapes.rect :as rect]
- [app.main.ui.shapes.shape :refer [shape-container]]
- [app.main.ui.shapes.svg-raw :as svg-raw]
- [app.main.ui.shapes.text :as text]
- [app.main.ui.shapes.text.fontfaces :as ff]
- [app.util.object :as obj]
- [app.util.timers :as ts]
- [cuerdas.core :as str]
- [rumext.alpha :as mf]))
-
-(def ^:private default-color "#E8E9EA") ;; $color-canvas
-
-(mf/defc background
- [{:keys [vbox color]}]
- [:rect
- {:x (:x vbox)
- :y (:y vbox)
- :width (:width vbox)
- :height (:height vbox)
- :fill color}])
-
-(defn- calculate-dimensions
- [{:keys [objects] :as data} vport]
- (let [shapes (cp/select-toplevel-shapes objects {:include-frames? true
- :include-frame-children? false})
- to-finite (fn [val fallback] (if (not (mth/finite? val)) fallback val))
- rect (cond->> (gsh/selection-rect shapes)
- (some? vport)
- (gal/adjust-to-viewport vport))]
- (-> rect
- (update :x to-finite 0)
- (update :y to-finite 0)
- (update :width to-finite 100000)
- (update :height to-finite 100000))))
-
-(declare shape-wrapper-factory)
-
-(defn frame-wrapper-factory
- [objects]
- (let [shape-wrapper (shape-wrapper-factory objects)
- frame-shape (frame/frame-shape shape-wrapper)]
- (mf/fnc frame-wrapper
- [{:keys [shape] :as props}]
- (let [childs (mapv #(get objects %) (:shapes shape))
- shape (gsh/transform-shape shape)]
- [:> shape-container {:shape shape}
- [:& frame-shape {:shape shape :childs childs}]]))))
-
-(defn group-wrapper-factory
- [objects]
- (let [shape-wrapper (shape-wrapper-factory objects)
- group-shape (group/group-shape shape-wrapper)]
- (mf/fnc group-wrapper
- [{:keys [shape frame] :as props}]
- (let [childs (mapv #(get objects %) (:shapes shape))]
- [:& group-shape {:frame frame
- :shape shape
- :is-child-selected? true
- :childs childs}]))))
-
-(defn bool-wrapper-factory
- [objects]
- (let [shape-wrapper (shape-wrapper-factory objects)
- bool-shape (bool/bool-shape shape-wrapper)]
- (mf/fnc bool-wrapper
- [{:keys [shape frame] :as props}]
- (let [childs (->> (cp/get-children (:id shape) objects)
- (select-keys objects))]
- [:& bool-shape {:frame frame
- :shape shape
- :childs childs}]))))
-
-(defn svg-raw-wrapper-factory
- [objects]
- (let [shape-wrapper (shape-wrapper-factory objects)
- svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
- (mf/fnc svg-raw-wrapper
- [{:keys [shape frame] :as props}]
- (let [childs (mapv #(get objects %) (:shapes shape))]
- (if (and (map? (:content shape))
- (or (= :svg (get-in shape [:content :tag]))
- (contains? shape :svg-attrs)))
- [:> shape-container {:shape shape}
- [:& svg-raw-shape {:frame frame
- :shape shape
- :childs childs}]]
-
- [:& svg-raw-shape {:frame frame
- :shape shape
- :childs childs}])))))
-
-(defn shape-wrapper-factory
- [objects]
- (mf/fnc shape-wrapper
- [{:keys [frame shape] :as props}]
- (let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
- svg-raw-wrapper (mf/use-memo (mf/deps objects) #(svg-raw-wrapper-factory objects))
- bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
- frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
- (when (and shape (not (:hidden shape)))
- (let [shape (-> (gsh/transform-shape shape)
- (gsh/translate-to-frame frame))
- opts #js {:shape shape}
- svg-raw? (= :svg-raw (:type shape))]
- (if-not svg-raw?
- [:> shape-container {:shape shape}
- (case (:type shape)
- :text [:> text/text-shape opts]
- :rect [:> rect/rect-shape opts]
- :path [:> path/path-shape opts]
- :image [:> image/image-shape opts]
- :circle [:> circle/circle-shape opts]
- :frame [:> frame-wrapper {:shape shape}]
- :group [:> group-wrapper {:shape shape :frame frame}]
- :bool [:> bool-wrapper {:shape shape :frame frame}]
- nil)]
-
- ;; Don't wrap svg elements inside a otherwise some can break
- [:> svg-raw-wrapper {:shape shape :frame frame}]))))))
-
-(defn get-viewbox [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
- (str/fmt "%s %s %s %s" x y width height))
-
-(mf/defc page-svg
- {::mf/wrap [mf/memo]}
- [{:keys [data width height thumbnails? embed? include-metadata?] :as props
- :or {embed? false include-metadata? false}}]
- (let [objects (:objects data)
- root (get objects uuid/zero)
- shapes
- (->> (:shapes root)
- (map #(get objects %)))
-
- root-children
- (->> shapes
- (filter #(not= :frame (:type %)))
- (mapcat #(cp/get-object-with-children (:id %) objects)))
-
- vport (when (and (some? width) (some? height))
- {:width width :height height})
- dim (calculate-dimensions data vport)
- vbox (get-viewbox dim)
- background-color (get-in data [:options :background] default-color)
- frame-wrapper
- (mf/use-memo
- (mf/deps objects)
- #(frame-wrapper-factory objects))
-
- shape-wrapper
- (mf/use-memo
- (mf/deps objects)
- #(shape-wrapper-factory objects))]
- [:& (mf/provider embed/context) {:value embed?}
- [:& (mf/provider use/include-metadata-ctx) {:value include-metadata?}
- [:svg {:view-box vbox
- :version "1.1"
- :xmlns "http://www.w3.org/2000/svg"
- :xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
- :style {:width "100%"
- :height "100%"
- :background background-color}}
-
- [:& use/export-page {:options (:options data)}]
- [:& ff/fontfaces-style {:shapes root-children}]
- (for [item shapes]
- (let [frame? (= (:type item) :frame)]
- (cond
- (and frame? thumbnails? (some? (:thumbnail item)))
- [:image {:xlinkHref (:thumbnail item)
- :x (:x item)
- :y (:y item)
- :width (:width item)
- :height (:height item)
- ;; DEBUG
- ;; :style {:filter "sepia(1)"}
- }]
- frame?
- [:& frame-wrapper {:shape item
- :key (:id item)}]
- :else
- [:& shape-wrapper {:shape item
- :key (:id item)}])))]]]))
-
-(mf/defc frame-svg
- {::mf/wrap [mf/memo]}
- [{:keys [objects frame zoom] :or {zoom 1} :as props}]
- (let [modifier (-> (gpt/point (:x frame) (:y frame))
- (gpt/negate)
- (gmt/translate-matrix))
-
- frame-id (:id frame)
-
- include-metadata? (mf/use-ctx use/include-metadata-ctx)
-
- modifier-ids (concat [frame-id] (cp/get-children frame-id objects))
- update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
- objects (reduce update-fn objects modifier-ids)
- frame (assoc-in frame [:modifiers :displacement] modifier)
-
- width (* (:width frame) zoom)
- height (* (:height frame) zoom)
- vbox (str "0 0 " (:width frame 0)
- " " (:height frame 0))
- wrapper (mf/use-memo
- (mf/deps objects)
- #(frame-wrapper-factory objects))]
-
- [:svg {:view-box vbox
- :width width
- :height height
- :version "1.1"
- :xmlns "http://www.w3.org/2000/svg"
- :xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
- [:& wrapper {:shape frame :view-box vbox}]]))
-
-(mf/defc component-svg
- {::mf/wrap [mf/memo
- #(mf/deferred % ts/idle-then-raf)]}
- [{:keys [objects group zoom] :or {zoom 1} :as props}]
- (let [modifier (-> (gpt/point (:x group) (:y group))
- (gpt/negate)
- (gmt/translate-matrix))
-
- group-id (:id group)
-
- include-metadata? (mf/use-ctx use/include-metadata-ctx)
-
- modifier-ids (concat [group-id] (cp/get-children group-id objects))
- update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
- objects (reduce update-fn objects modifier-ids)
- group (assoc-in group [:modifiers :displacement] modifier)
-
- width (* (:width group) zoom)
- height (* (:height group) zoom)
- vbox (str "0 0 " (:width group 0)
- " " (:height group 0))
- wrapper (mf/use-memo
- (mf/deps objects)
- #(group-wrapper-factory objects))]
-
- [:svg {:view-box vbox
- :width width
- :height height
- :version "1.1"
- :xmlns "http://www.w3.org/2000/svg"
- :xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
- [:> shape-container {:shape group}
- [:& wrapper {:shape group :view-box vbox}]]]))
-
-(mf/defc component-symbol
- [{:keys [id data] :as props}]
-
- (let [{:keys [name path objects]} data
- root (get objects id)
-
- {:keys [width height]} (:selrect root)
- vbox (str "0 0 " width " " height)
-
- modifier (-> (gpt/point (:x root) (:y root))
- (gpt/negate)
- (gmt/translate-matrix))
-
- modifier-ids (concat [id] (cp/get-children id objects))
- update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
- objects (reduce update-fn objects modifier-ids)
- root (assoc-in root [:modifiers :displacement] modifier)
-
- group-wrapper
- (mf/use-memo
- (mf/deps objects)
- #(group-wrapper-factory objects))]
-
- [:> "symbol" #js {:id (str id)
- :viewBox vbox
- "penpot:path" path}
- [:title name]
- [:> shape-container {:shape root}
- [:& group-wrapper {:shape root :view-box vbox}]]]))
-
-(mf/defc components-sprite-svg
- {::mf/wrap-props false}
- [props]
-
- (let [data (obj/get props "data")
- children (obj/get props "children")
- embed? (obj/get props "embed?")
- include-metadata? (obj/get props "include-metadata?")]
- [:& (mf/provider embed/context) {:value embed?}
- [:& (mf/provider use/include-metadata-ctx) {:value include-metadata?}
- [:svg {:version "1.1"
- :xmlns "http://www.w3.org/2000/svg"
- :xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
- :style {:width "100vw"
- :height "100vh"
- :display (when-not (some? children) "none")}}
- [:defs
- (for [[component-id component-data] (:components data)]
- [:& component-symbol {:id component-id
- :key (str component-id)
- :data component-data}])]
-
- children]]]))
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index efaaddaa93..f93a53ae99 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -116,6 +116,14 @@
:show-distances?])
workspace-local =))
+(def interactions-data
+ (l/derived #(select-keys % [:editing-interaction-index
+ :draw-interaction-to
+ :draw-interaction-to-frame
+ :move-overlay-to
+ :move-overlay-index])
+ workspace-local =))
+
(def local-displacement
(l/derived #(select-keys % [:modifiers :selected])
workspace-local =))
@@ -147,6 +155,9 @@
(def editors
(l/derived :editors workspace-local))
+(def selected-assets
+ (l/derived :selected-assets workspace-local))
+
(def workspace-layout
(l/derived :workspace-layout st/state))
@@ -232,22 +243,12 @@
(l/derived #(get % id) workspace-page-objects))
(defn objects-by-id
- ([ids]
- (objects-by-id ids nil))
-
- ([ids {:keys [with-modifiers?]
- :or { with-modifiers? false }}]
- (let [selector
- (fn [state]
- (let [objects (wsh/lookup-page-objects state)
- modifiers (:workspace-modifiers state)
- objects (cond-> objects
- with-modifiers?
- (gsh/merge-modifiers modifiers))
- xform (comp (map #(get objects %))
- (remove nil?))]
- (into [] xform ids)))]
- (l/derived selector st/state =))))
+ [ids]
+ (let [selector
+ (fn [state]
+ (let [objects (wsh/lookup-page-objects state)]
+ (into [] (keep (d/getf objects)) ids)))]
+ (l/derived selector st/state =)))
(defn- set-content-modifiers [state]
(fn [id shape]
@@ -256,21 +257,11 @@
(update shape :content upc/apply-content-modifiers content-modifiers)
shape))))
-(defn select-children [id]
+(defn select-bool-children [id]
(let [selector
(fn [state]
(let [objects (wsh/lookup-page-objects state)
-
- modifiers (-> (:workspace-modifiers state))
- {selected :selected disp-modifiers :modifiers}
- (-> (:workspace-local state)
- (select-keys [:modifiers :selected]))
-
- modifiers
- (d/deep-merge
- modifiers
- (into {} (map #(vector % {:modifiers disp-modifiers})) selected))]
-
+ modifiers (:workspace-modifiers state)]
(as-> (cp/select-children id objects) $
(gsh/merge-modifiers $ modifiers)
(d/mapm (set-content-modifiers state) $))))]
@@ -299,19 +290,10 @@
(def selected-shapes-with-children
(letfn [(selector [{:keys [selected objects]}]
- (let [children (->> selected
- (mapcat #(cp/get-children % objects))
- (filterv (comp not nil?)))]
- (into selected children)))]
- (l/derived selector selected-data =)))
-
-(def selected-objects-with-children
- (letfn [(selector [{:keys [selected objects]}]
- (let [children (->> selected
- (mapcat #(cp/get-children % objects))
- (filterv (comp not nil?)))
- shapes (into selected children)]
- (mapv #(get objects %) shapes)))]
+ (let [xform (comp (remove nil?)
+ (mapcat #(cp/get-children % objects)))
+ shapes (into selected xform selected)]
+ (mapv (d/getf objects) shapes)))]
(l/derived selector selected-data =)))
;; ---- Viewer refs
@@ -340,3 +322,7 @@
(def users
(l/derived :users st/state))
+(def fullscreen?
+ (l/derived (fn [state]
+ (get-in state [:viewer-local :fullscreen?] []))
+ st/state))
diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs
index ffe3452d04..b76f478593 100644
--- a/frontend/src/app/main/render.cljs
+++ b/frontend/src/app/main/render.cljs
@@ -5,18 +5,387 @@
;; Copyright (c) UXBOX Labs SL
(ns app.main.render
+ "Rendering utilities and components for penpot SVG.
+
+ NOTE: This namespace is used from worker and from many parts of the
+ workspace; we need to be careful when adding new requires because
+ this can cause to import too many deps on worker bundle."
+
(:require
["react-dom/server" :as rds]
+ [app.common.colors :as clr]
+ [app.common.geom.align :as gal]
+ [app.common.geom.matrix :as gmt]
+ [app.common.geom.point :as gpt]
+ [app.common.geom.shapes :as gsh]
+ [app.common.math :as mth]
+ [app.common.pages :as cp]
+ [app.common.uuid :as uuid]
[app.config :as cfg]
- [app.main.exports :as exports]
[app.main.fonts :as fonts]
+ [app.main.ui.shapes.bool :as bool]
+ [app.main.ui.shapes.circle :as circle]
+ [app.main.ui.shapes.embed :as embed]
+ [app.main.ui.shapes.export :as export]
+ [app.main.ui.shapes.frame :as frame]
+ [app.main.ui.shapes.group :as group]
+ [app.main.ui.shapes.image :as image]
+ [app.main.ui.shapes.path :as path]
+ [app.main.ui.shapes.rect :as rect]
+ [app.main.ui.shapes.shape :refer [shape-container]]
+ [app.main.ui.shapes.svg-raw :as svg-raw]
+ [app.main.ui.shapes.text :as text]
+ [app.main.ui.shapes.text.fontfaces :as ff]
[app.util.http :as http]
+ [app.util.object :as obj]
+ [app.util.strings :as ust]
+ [app.util.timers :as ts]
[beicon.core :as rx]
[clojure.set :as set]
+ [cuerdas.core :as str]
[rumext.alpha :as mf]))
-(defn- text? [{type :type}]
- (= type :text))
+(def ^:const viewbox-decimal-precision 3)
+(def ^:private default-color clr/canvas)
+
+(mf/defc background
+ [{:keys [vbox color]}]
+ [:rect
+ {:x (:x vbox)
+ :y (:y vbox)
+ :width (:width vbox)
+ :height (:height vbox)
+ :fill color}])
+
+(defn- calculate-dimensions
+ [{:keys [objects] :as data} vport]
+ (let [shapes (cp/select-toplevel-shapes objects {:include-frames? true
+ :include-frame-children? false})
+ to-finite (fn [val fallback] (if (not (mth/finite? val)) fallback val))
+ rect (cond->> (gsh/selection-rect shapes)
+ (some? vport)
+ (gal/adjust-to-viewport vport))]
+ (-> rect
+ (update :x to-finite 0)
+ (update :y to-finite 0)
+ (update :width to-finite 100000)
+ (update :height to-finite 100000))))
+
+(declare shape-wrapper-factory)
+
+(defn frame-wrapper-factory
+ [objects]
+ (let [shape-wrapper (shape-wrapper-factory objects)
+ frame-shape (frame/frame-shape shape-wrapper)]
+ (mf/fnc frame-wrapper
+ [{:keys [shape] :as props}]
+ (let [childs (mapv #(get objects %) (:shapes shape))
+ shape (gsh/transform-shape shape)]
+ [:> shape-container {:shape shape}
+ [:& frame-shape {:shape shape :childs childs}]]))))
+
+(defn group-wrapper-factory
+ [objects]
+ (let [shape-wrapper (shape-wrapper-factory objects)
+ group-shape (group/group-shape shape-wrapper)]
+ (mf/fnc group-wrapper
+ [{:keys [shape] :as props}]
+ (let [childs (mapv #(get objects %) (:shapes shape))]
+ [:& group-shape {:shape shape
+ :is-child-selected? true
+ :childs childs}]))))
+
+(defn bool-wrapper-factory
+ [objects]
+ (let [shape-wrapper (shape-wrapper-factory objects)
+ bool-shape (bool/bool-shape shape-wrapper)]
+ (mf/fnc bool-wrapper
+ [{:keys [shape] :as props}]
+ (let [childs (mf/use-memo
+ (mf/deps (:id shape) objects)
+ (fn []
+ (->> (cp/get-children (:id shape) objects)
+ (select-keys objects))))]
+ [:& bool-shape {:shape shape :childs childs}]))))
+
+(defn svg-raw-wrapper-factory
+ [objects]
+ (let [shape-wrapper (shape-wrapper-factory objects)
+ svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
+ (mf/fnc svg-raw-wrapper
+ [{:keys [shape] :as props}]
+ (let [childs (mapv #(get objects %) (:shapes shape))]
+ (if (and (map? (:content shape))
+ (or (= :svg (get-in shape [:content :tag]))
+ (contains? shape :svg-attrs)))
+ [:> shape-container {:shape shape}
+ [:& svg-raw-shape {:shape shape
+ :childs childs}]]
+
+ [:& svg-raw-shape {:shape shape
+ :childs childs}])))))
+
+(defn shape-wrapper-factory
+ [objects]
+ (mf/fnc shape-wrapper
+ [{:keys [frame shape] :as props}]
+ (let [group-wrapper (mf/use-memo (mf/deps objects) #(group-wrapper-factory objects))
+ svg-raw-wrapper (mf/use-memo (mf/deps objects) #(svg-raw-wrapper-factory objects))
+ bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects))
+ frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))]
+ (when (and shape (not (:hidden shape)))
+ (let [shape (gsh/transform-shape shape)
+ opts #js {:shape shape}
+ svg-raw? (= :svg-raw (:type shape))]
+ (if-not svg-raw?
+ [:> shape-container {:shape shape}
+ (case (:type shape)
+ :text [:> text/text-shape opts]
+ :rect [:> rect/rect-shape opts]
+ :path [:> path/path-shape opts]
+ :image [:> image/image-shape opts]
+ :circle [:> circle/circle-shape opts]
+ :frame [:> frame-wrapper {:shape shape}]
+ :group [:> group-wrapper {:shape shape :frame frame}]
+ :bool [:> bool-wrapper {:shape shape :frame frame}]
+ nil)]
+
+ ;; Don't wrap svg elements inside a otherwise some can break
+ [:> svg-raw-wrapper {:shape shape :frame frame}]))))))
+
+(defn format-viewbox
+ "Format a viewbox given a rectangle"
+ [{:keys [x y width height] :or {x 0 y 0 width 100 height 100}}]
+ (str/join
+ " "
+ (->> [x y width height]
+ (map #(ust/format-precision % viewbox-decimal-precision)))))
+
+(mf/defc page-svg
+ {::mf/wrap [mf/memo]}
+ [{:keys [data width height thumbnails? embed? include-metadata?] :as props
+ :or {embed? false include-metadata? false}}]
+ (let [objects (:objects data)
+ root (get objects uuid/zero)
+ shapes
+ (->> (:shapes root)
+ (map #(get objects %)))
+
+ root-children
+ (->> shapes
+ (filter #(not= :frame (:type %)))
+ (mapcat #(cp/get-object-with-children (:id %) objects)))
+
+ vport (when (and (some? width) (some? height))
+ {:width width :height height})
+
+ dim (calculate-dimensions data vport)
+ vbox (format-viewbox dim)
+ background-color (get-in data [:options :background] default-color)
+
+ frame-wrapper
+ (mf/use-memo
+ (mf/deps objects)
+ #(frame-wrapper-factory objects))
+
+ shape-wrapper
+ (mf/use-memo
+ (mf/deps objects)
+ #(shape-wrapper-factory objects))]
+
+ [:& (mf/provider embed/context) {:value embed?}
+ [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
+ [:svg {:view-box vbox
+ :version "1.1"
+ :xmlns "http://www.w3.org/2000/svg"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"
+ :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
+ :style {:width "100%"
+ :height "100%"
+ :background background-color}}
+
+ [:& export/export-page {:options (:options data)}]
+ [:& ff/fontfaces-style {:shapes root-children}]
+ (for [item shapes]
+ (let [frame? (= (:type item) :frame)]
+ (cond
+ (and frame? thumbnails? (some? (:thumbnail item)))
+ [:> shape-container {:shape item}
+ [:& frame/frame-thumbnail {:shape item}]]
+
+ frame?
+ [:& frame-wrapper {:shape item
+ :key (:id item)}]
+ :else
+ [:& shape-wrapper {:shape item
+ :key (:id item)}])))]]]))
+
+(mf/defc frame-svg
+ {::mf/wrap [mf/memo]}
+ [{:keys [objects frame zoom] :or {zoom 1} :as props}]
+ (let [frame-id (:id frame)
+ include-metadata? (mf/use-ctx export/include-metadata-ctx)
+
+ modifier
+ (mf/use-memo
+ (mf/deps (:x frame) (:y frame))
+ (fn []
+ (-> (gpt/point (:x frame) (:y frame))
+ (gpt/negate)
+ (gmt/translate-matrix))))
+
+ objects
+ (mf/use-memo
+ (mf/deps frame-id objects modifier)
+ (fn []
+ (let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
+ (->> (cp/get-children frame-id objects)
+ (into [frame-id])
+ (reduce update-fn objects)))))
+
+ frame
+ (mf/use-memo
+ (mf/deps modifier)
+ (fn [] (assoc-in frame [:modifiers :displacement] modifier)))
+
+ wrapper
+ (mf/use-memo
+ (mf/deps objects)
+ (fn [] (frame-wrapper-factory objects)))
+
+ width (* (:width frame) zoom)
+ height (* (:height frame) zoom)
+ vbox (format-viewbox {:width (:width frame 0) :height (:height frame 0)})]
+
+ [:svg {:view-box vbox
+ :width (ust/format-precision width viewbox-decimal-precision)
+ :height (ust/format-precision height viewbox-decimal-precision)
+ :version "1.1"
+ :xmlns "http://www.w3.org/2000/svg"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"
+ :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
+ [:& wrapper {:shape frame :view-box vbox}]]))
+
+(mf/defc component-svg
+ {::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]}
+ [{:keys [objects group zoom] :or {zoom 1} :as props}]
+ (let [group-id (:id group)
+ include-metadata? (mf/use-ctx export/include-metadata-ctx)
+
+ modifier
+ (mf/use-memo
+ (mf/deps (:x group) (:y group))
+ (fn []
+ (-> (gpt/point (:x group) (:y group))
+ (gpt/negate)
+ (gmt/translate-matrix))))
+
+ objects
+ (mf/use-memo
+ (mf/deps modifier objects group-id)
+ (fn []
+ (let [modifier-ids (concat [group-id] (cp/get-children group-id objects))
+ update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
+ modifiers (reduce update-fn {} modifier-ids)]
+ (gsh/merge-modifiers objects modifiers))))
+
+ group (get objects group-id)
+ width (* (:width group) zoom)
+ height (* (:height group) zoom)
+ vbox (format-viewbox {:width (:width group 0)
+ :height (:height group 0)})
+ group-wrapper
+ (mf/use-memo
+ (mf/deps objects)
+ (fn [] (group-wrapper-factory objects)))]
+
+ [:svg {:view-box vbox
+ :width (ust/format-precision width viewbox-decimal-precision)
+ :height (ust/format-precision height viewbox-decimal-precision)
+ :version "1.1"
+ :xmlns "http://www.w3.org/2000/svg"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"
+ :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")}
+
+ [:> shape-container {:shape group}
+ [:& group-wrapper {:shape group :view-box vbox}]]]))
+
+(mf/defc component-symbol
+ {::mf/wrap-props false}
+ [props]
+ (let [id (obj/get props "id")
+ data (obj/get props "data")
+ name (:name data)
+ path (:path data)
+ objects (:objects data)
+ root (get objects id)
+ selrect (:selrect root)
+
+ vbox
+ (format-viewbox
+ {:width (:width selrect)
+ :height (:height selrect)})
+
+ modifier
+ (mf/use-memo
+ (mf/deps (:x root) (:y root))
+ (fn []
+ (-> (gpt/point (:x root) (:y root))
+ (gpt/negate)
+ (gmt/translate-matrix))))
+
+ objects
+ (mf/use-memo
+ (mf/deps modifier id objects)
+ (fn []
+ (let [modifier-ids (concat [id] (cp/get-children id objects))
+ update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)]
+ (reduce update-fn objects modifier-ids))))
+
+ root
+ (mf/use-memo
+ (mf/deps modifier root)
+ (fn [] (assoc-in root [:modifiers :displacement] modifier)))
+
+ group-wrapper
+ (mf/use-memo
+ (mf/deps objects)
+ (fn [] (group-wrapper-factory objects)))]
+
+ [:> "symbol" #js {:id (str id)
+ :viewBox vbox
+ "penpot:path" path}
+ [:title name]
+ [:> shape-container {:shape root}
+ [:& group-wrapper {:shape root :view-box vbox}]]]))
+
+(mf/defc components-sprite-svg
+ {::mf/wrap-props false}
+ [props]
+ (let [data (obj/get props "data")
+ children (obj/get props "children")
+ embed? (obj/get props "embed?")
+ include-metadata? (obj/get props "include-metadata?")]
+ [:& (mf/provider embed/context) {:value embed?}
+ [:& (mf/provider export/include-metadata-ctx) {:value include-metadata?}
+ [:svg {:version "1.1"
+ :xmlns "http://www.w3.org/2000/svg"
+ :xmlnsXlink "http://www.w3.org/1999/xlink"
+ :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
+ :style {:width "100vw"
+ :height "100vh"
+ :display (when-not (some? children) "none")}}
+ [:defs
+ (for [[component-id component-data] (:components data)]
+ [:& component-symbol {:id component-id
+ :key (str component-id)
+ :data component-data}])]
+
+ children]]]))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; RENDERING
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- get-image-data [shape]
(cond
@@ -29,7 +398,7 @@
:else
[]))
-(defn populate-images-cache
+(defn- populate-images-cache
[objects]
(let [images (->> objects
(vals)
@@ -41,7 +410,7 @@
(defn populate-fonts-cache [objects]
(let [texts (->> objects
(vals)
- (filterv text?)
+ (filterv #(= (:type %) :text))
(mapv :content)) ]
(->> (rx/from texts)
@@ -63,7 +432,7 @@
(->> (rx/of data)
(rx/map
(fn [data]
- (let [elem (mf/element exports/page-svg #js {:data data :embed? true :include-metadata? true})]
+ (let [elem (mf/element page-svg #js {:data data :embed? true :include-metadata? true})]
(rds/renderToStaticMarkup elem)))))))
(defn render-components
@@ -82,5 +451,5 @@
(->> (rx/of data)
(rx/map
(fn [data]
- (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true :include-metadata? true})]
+ (let [elem (mf/element components-sprite-svg #js {:data data :embed? true :include-metadata? true})]
(rds/renderToStaticMarkup elem))))))))
diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs
index 743306a72b..41bd7ae79e 100644
--- a/frontend/src/app/main/snap.cljs
+++ b/frontend/src/app/main/snap.cljs
@@ -143,7 +143,7 @@
best-snap
(fn [acc val]
- ;; Using a number is faster than accesing the variable.
+ ;; Using a number is faster than accessing the variable.
;; Keep up to date with `snap-distance-accuracy`
(if (and (<= val snap-distance-accuracy) (>= val (- snap-distance-accuracy)))
(min acc val)
diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs
index 78a93c1a8c..35936207de 100644
--- a/frontend/src/app/main/store.cljs
+++ b/frontend/src/app/main/store.cljs
@@ -7,11 +7,7 @@
(ns app.main.store
(:require-macros [app.main.store])
(:require
- [app.common.data :as d]
- [app.common.pages :as cp]
- [app.util.debug :refer [debug? debug-exclude-events logjs]]
[beicon.core :as rx]
- [cuerdas.core :as str]
[okulary.core :as l]
[potok.core :as ptk]))
@@ -28,28 +24,28 @@
(ptk/input-stream state))
(defonce last-events
- (let [buffer (atom #queue [])
- remove #{:potok.core/undefined
- :app.main.data.workspace.notifications/handle-pointer-update}]
- (->> stream
- (rx/filter ptk/event?)
- (rx/map ptk/type)
- (rx/filter (complement remove))
- (rx/map str)
- (rx/dedupe)
- (rx/buffer 20 1)
- (rx/subs #(reset! buffer %)))
-
+ (let [buffer (atom [])
+ allowed #{:app.main.data.workspace/initialize-page
+ :app.main.data.workspace/finalize-page
+ :app.main.data.workspace/initialize-file
+ :app.main.data.workspace/finalize-file}]
+ (->> (rx/merge
+ (->> stream
+ (rx/filter (ptk/type? :app.main.data.workspace.changes/commit-changes))
+ (rx/map #(-> % deref :hint-origin str))
+ (rx/dedupe))
+ (->> stream
+ (rx/map ptk/type)
+ (rx/filter #(contains? allowed %))
+ (rx/map str)))
+ (rx/scan (fn [buffer event]
+ (cond-> (conj buffer event)
+ (> (count buffer) 20)
+ (pop)))
+ #queue [])
+ (rx/subs #(reset! buffer (vec %))))
buffer))
-(when *assert*
- (defonce debug-subscription
- (->> stream
- (rx/filter ptk/event?)
- (rx/filter (fn [s] (and (debug? :events)
- (not (debug-exclude-events (ptk/type s))))))
- (rx/subs #(println "[stream]: " (ptk/repr-event %))))))
-
(defn emit!
([] nil)
([event]
@@ -63,99 +59,4 @@
[& events]
#(apply ptk/emit! state events))
-(defn ^:export dump-state []
- (logjs "state" @state))
-
-(defn ^:export dump-buffer []
- (logjs "state" @last-events))
-
-(defn ^:export get-state [str-path]
- (let [path (->> (str/split str-path " ")
- (map d/read-string))]
- (clj->js (get-in @state path))))
-
-(defn ^:export dump-objects []
- (let [page-id (get @state :current-page-id)]
- (logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects]))))
-
-(defn ^:export dump-object [name]
- (let [page-id (get @state :current-page-id)
- objects (get-in @state [:workspace-data :pages-index page-id :objects])
- target (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects)
- (get objects (uuid name)))]
- (->> target
- (logjs "state"))))
-
-(defn ^:export dump-tree
- ([] (dump-tree false false))
- ([show-ids] (dump-tree show-ids false))
- ([show-ids show-touched]
- (let [page-id (get @state :current-page-id)
- objects (get-in @state [:workspace-data :pages-index page-id :objects])
- components (get-in @state [:workspace-data :components])
- libraries (get @state :workspace-libraries)
- root (d/seek #(nil? (:parent-id %)) (vals objects))]
-
- (letfn [(show-shape [shape-id level objects]
- (let [shape (get objects shape-id)]
- (println (str/pad (str (str/repeat " " level)
- (:name shape)
- (when (seq (:touched shape)) "*")
- (when show-ids (str/format " <%s>" (:id shape))))
- {:length 20
- :type :right})
- (show-component shape objects))
- (when show-touched
- (when (seq (:touched shape))
- (println (str (str/repeat " " level)
- " "
- (str (:touched shape)))))
- (when (:remote-synced? shape)
- (println (str (str/repeat " " level)
- " (remote-synced)"))))
- (when (:shapes shape)
- (dorun (for [shape-id (:shapes shape)]
- (show-shape shape-id (inc level) objects))))))
-
- (show-component [shape objects]
- (if (nil? (:shape-ref shape))
- ""
- (let [root-shape (cp/get-component-shape shape objects)
- component-id (when root-shape (:component-id root-shape))
- component-file-id (when root-shape (:component-file root-shape))
- component-file (when component-file-id (get libraries component-file-id nil))
- component (when component-id
- (if component-file
- (get-in component-file [:data :components component-id])
- (get components component-id)))
- component-shape (when (and component (:shape-ref shape))
- (get-in component [:objects (:shape-ref shape)]))]
- (str/format " %s--> %s%s%s"
- (cond (:component-root? shape) "#"
- (:component-id shape) "@"
- :else "-")
- (when component-file (str/format "<%s> " (:name component-file)))
- (or (:name component-shape) "?")
- (if (or (:component-root? shape)
- (nil? (:component-id shape))
- true)
- ""
- (let [component-id (:component-id shape)
- component-file-id (:component-file shape)
- component-file (when component-file-id (get libraries component-file-id nil))
- component (if component-file
- (get-in component-file [:data :components component-id])
- (get components component-id))]
- (str/format " (%s%s)"
- (when component-file (str/format "<%s> " (:name component-file)))
- (:name component))))))))]
-
- (println "[Page]")
- (show-shape (:id root) 0 objects)
-
- (dorun (for [component (vals components)]
- (do
- (println)
- (println (str/format "[%s]" (:name component)))
- (show-shape (:id component) 0 (:objects component)))))))))
diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs
index d12b62acca..7b5201ffa7 100644
--- a/frontend/src/app/main/streams.cljs
+++ b/frontend/src/app/main/streams.cljs
@@ -114,8 +114,8 @@
(rx/filter kbd/altKey?)
(rx/map #(= :down (:type %))))
;; Fix a situation caused by using `ctrl+alt` kind of shortcuts,
- ;; that makes keyboard-alt stream registring the key pressed but
- ;; on bluring the window (unfocus) the key down is never arrived.
+ ;; that makes keyboard-alt stream registering the key pressed but
+ ;; on blurring the window (unfocus) the key down is never arrived.
(->> window-blur
(rx/map (constantly false))))
(rx/dedupe))]
@@ -130,8 +130,8 @@
(rx/filter kbd/ctrlKey?)
(rx/map #(= :down (:type %))))
;; Fix a situation caused by using `ctrl+alt` kind of shortcuts,
- ;; that makes keyboard-alt stream registring the key pressed but
- ;; on bluring the window (unfocus) the key down is never arrived.
+ ;; that makes keyboard-alt stream registering the key pressed but
+ ;; on blurring the window (unfocus) the key down is never arrived.
(->> window-blur
(rx/map (constantly false))))
(rx/dedupe))]
diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs
index 9c1d79c451..c6392cc26b 100644
--- a/frontend/src/app/main/ui.cljs
+++ b/frontend/src/app/main/ui.cljs
@@ -11,7 +11,6 @@
[app.main.store :as st]
[app.main.ui.auth :refer [auth]]
[app.main.ui.auth.verify-token :refer [verify-token]]
- [app.main.ui.components.fullscreen :as fs]
[app.main.ui.context :as ctx]
[app.main.ui.cursors :as c]
[app.main.ui.dashboard :refer [dashboard]]
@@ -31,7 +30,7 @@
(mf/defc on-main-error
[{:keys [error] :as props}]
(mf/use-effect (st/emitf (rt/assign-exception error)))
- [:span "Internal application errror"])
+ [:span "Internal application error"])
(mf/defc main-page
{::mf/wrap [#(mf/catch % {:fallback on-main-error})]}
@@ -62,8 +61,7 @@
[:h1 "Cursors"]
[:& c/debug-preview]
[:h1 "Icons"]
- [:& i/debug-icons-preview]
- ])
+ [:& i/debug-icons-preview]])
(:dashboard-search
:dashboard-projects
@@ -78,8 +76,7 @@
#_[:div.modal-wrapper
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
#_[:& app.main.ui.onboarding/onboarding-modal]
- #_[:& app.main.ui.onboarding/onboarding-team-modal]
- ]
+ #_[:& app.main.ui.onboarding/onboarding-team-modal]]
(when-let [props (some-> profile (get :props {}))]
(cond
(and cf/onboarding-form-id
@@ -104,14 +101,13 @@
(let [{:keys [query-params path-params]} route
{:keys [index share-id section page-id] :or {section :interactions}} query-params
{:keys [file-id]} path-params]
- [:& fs/fullscreen-wrapper {}
- (if (:token query-params)
- [:& viewer/breaking-change-notice]
- [:& viewer/viewer-page {:page-id page-id
- :file-id file-id
- :section section
- :index index
- :share-id share-id}])])
+ (if (:token query-params)
+ [:& viewer/breaking-change-notice]
+ [:& viewer/viewer-page {:page-id page-id
+ :file-id file-id
+ :section section
+ :index index
+ :share-id share-id}]))
:render-object
(do
diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs
index e18471d2b3..5389c99e21 100644
--- a/frontend/src/app/main/ui/auth/recovery.cljs
+++ b/frontend/src/app/main/ui/auth/recovery.cljs
@@ -42,7 +42,7 @@
(defn- on-success
[_]
- (st/emit! (dm/info (tr "auth.notifications.password-changed-succesfully"))
+ (st/emit! (dm/info (tr "auth.notifications.password-changed-successfully"))
(rt/nav :auth-login)))
(defn- on-submit
diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs
index 89f22128bb..62faf19f00 100644
--- a/frontend/src/app/main/ui/components/color_bullet.cljs
+++ b/frontend/src/app/main/ui/components/color_bullet.cljs
@@ -10,24 +10,28 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
+(defn gradient-type->string [type]
+ (case type
+ :linear (tr "workspace.gradients.linear")
+ :radial (tr "workspace.gradients.radial")
+ nil))
+
(mf/defc color-bullet [{:keys [color on-click]}]
(if (uc/multiple? color)
[:div.color-bullet.multiple {:on-click #(when on-click (on-click %))}]
;; No multiple selection
- (let [color (if (string? color) {:color color :opacity 1} color)]
- [:div.color-bullet {:class (when (:id color) "is-library-color")
- :on-click #(when on-click (on-click %))}
- (when (not (:gradient color))
- [:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}])
-
- [:div.color-bullet-right {:style {:background (uc/color->background color)}}]])))
-
-(defn gradient-type->string [type]
- (case type
- :linear (tr "workspace.gradients.linear")
- :radial (tr "workspace.gradients.radial")
- (str "???" type)))
+ (let [color (if (string? color) {:color color :opacity 1} color)
+ background (if (:gradient color) (uc/color->background color) "auto")]
+ [:div.color-bullet.tooltip.tooltip-right {:class (if (:id color) "is-library-color" "is-not-library-color")
+ :on-click #(when on-click (on-click %))
+ :alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))
+ :style {:background background}}
+ (when (not(:gradient color))
+ [:*
+ [:div.color-bullet-left {:style {:background (uc/color->background (assoc color :opacity 1))}}]
+ [:div.color-bullet-right {:style {:background (uc/color->background color)}}]]
+ )])))
(mf/defc color-name [{:keys [color size on-click on-double-click]}]
(let [color (if (string? color) {:color color :opacity 1} color)
@@ -36,4 +40,4 @@
(when (or (not size) (= size :big))
[:span.color-text {:on-click #(when on-click (on-click %))
:on-double-click #(when on-double-click (on-double-click %))
- :title name } color-str])))
+ :title name} color-str])))
diff --git a/frontend/src/app/main/ui/components/color_input.cljs b/frontend/src/app/main/ui/components/color_input.cljs
index 254a6c25f2..da2cc7fb2f 100644
--- a/frontend/src/app/main/ui/components/color_input.cljs
+++ b/frontend/src/app/main/ui/components/color_input.cljs
@@ -8,10 +8,20 @@
(:require
[app.util.color :as uc]
[app.util.dom :as dom]
+ [app.util.globals :as globals]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
- [rumext.alpha :as mf]))
+ [goog.events :as events]
+ [rumext.alpha :as mf])
+ (:import goog.events.EventType))
+
+(defn clean-color
+ [value]
+ (-> value
+ (uc/expand-hex)
+ (uc/parse-color)
+ (uc/prepend-hash)))
(mf/defc color-input
{::mf/wrap-props false
@@ -26,16 +36,17 @@
local-ref (mf/use-ref)
ref (or external-ref local-ref)
+ ;; We need to store the handle-blur ref so we can call it on unmount
+ handle-blur-ref (mf/use-ref nil)
+ dirty-ref (mf/use-ref false)
+
parse-value
(mf/use-callback
(mf/deps ref)
(fn []
(let [input-node (mf/ref-val ref)]
(try
- (let [new-value (-> (dom/get-value input-node)
- (uc/expand-hex)
- (uc/parse-color)
- (uc/prepend-hash))]
+ (let [new-value (clean-color (dom/get-value input-node))]
(dom/set-validity! input-node "")
new-value)
(catch :default _e
@@ -53,7 +64,8 @@
(mf/use-callback
(mf/deps on-change update-input)
(fn [new-value]
- (when new-value
+ (mf/set-ref-val! dirty-ref false)
+ (when (and new-value (not= (uc/remove-hash new-value) value))
(when on-change
(on-change new-value))
(update-input new-value))))
@@ -62,12 +74,15 @@
(mf/use-callback
(mf/deps apply-value update-input)
(fn [event]
+ (mf/set-ref-val! dirty-ref true)
(let [enter? (kbd/enter? event)
- esc? (kbd/esc? event)]
+ esc? (kbd/esc? event)
+ input-node (mf/ref-val ref)]
(when enter?
(dom/prevent-default event)
(let [new-value (parse-value)]
- (apply-value new-value)))
+ (apply-value new-value)
+ (dom/blur! input-node)))
(when esc?
(dom/prevent-default event)
(update-input value)))))
@@ -81,7 +96,14 @@
(apply-value new-value)
(update-input value)))))
- ;; list-id (str "colors-" (uuid/next))
+ on-click
+ (mf/use-callback
+ (fn [event]
+ (let [target (dom/get-target event)]
+ (when (some? ref)
+ (let [current (mf/ref-val ref)]
+ (when (and (some? current) (not (.contains current target)))
+ (dom/blur! current)))))))
props (-> props
(obj/without ["value" "onChange"])
@@ -98,6 +120,25 @@
(when-let [node (mf/ref-val ref)]
(dom/set-value! node value))))
+ (mf/use-effect
+ (mf/deps handle-blur)
+ (fn []
+ (mf/set-ref-val! handle-blur-ref {:fn handle-blur})))
+
+ (mf/use-layout-effect
+ (fn []
+ #(when (mf/ref-val dirty-ref)
+ (let [handle-blur (:fn (mf/ref-val handle-blur-ref))]
+ (handle-blur)))))
+
+ (mf/use-layout-effect
+ (fn []
+ (let [keys [(events/listen globals/window EventType.POINTERDOWN on-click)
+ (events/listen globals/window EventType.MOUSEDOWN on-click)
+ (events/listen globals/window EventType.CLICK on-click)]]
+ #(doseq [key keys]
+ (events/unlistenByKey key)))))
+
[:*
[:> :input props]
;; FIXME: this causes some weird interactions because of using apply-value
diff --git a/frontend/src/app/main/ui/components/context_menu.cljs b/frontend/src/app/main/ui/components/context_menu.cljs
index e8198377dc..b83efcc14a 100644
--- a/frontend/src/app/main/ui/components/context_menu.cljs
+++ b/frontend/src/app/main/ui/components/context_menu.cljs
@@ -6,9 +6,12 @@
(ns app.main.ui.components.context-menu
(:require
+ [app.common.data :as d]
+ [app.main.refs :as refs]
[app.main.ui.components.dropdown :refer [dropdown']]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
+ [app.util.i18n :as i18n :refer [tr]]
[app.util.object :as obj]
[goog.object :as gobj]
[rumext.alpha :as mf]))
@@ -29,6 +32,8 @@
left (gobj/get props "left" 0)
fixed? (gobj/get props "fixed?" false)
min-width? (gobj/get props "min-width?" false)
+ route (mf/deref refs/route)
+ in-dashboard? (= :dashboard-projects (:name (:data route)))
local (mf/use-state {:offset 0
:levels nil})
@@ -96,18 +101,20 @@
[:span i/arrow-slide]
parent-option]]
[:li.separator]])
- (for [[option-name option-handler sub-options] (:options level)]
+ (for [[index [option-name option-handler sub-options]] (d/enumerate (:options level))]
(when option-name
(if (= option-name :separator)
[:li.separator]
[:li.context-menu-item
{:class (dom/classnames :is-selected (and selected (= option-name selected)))
- :key option-name}
+ :key index}
(if-not sub-options
[:a.context-menu-action {:on-click #(do (dom/stop-propagation %)
(on-close)
(option-handler %))}
- option-name]
+ (if (and in-dashboard? (= option-name "Default"))
+ (tr "dashboard.default-team-name")
+ option-name)]
[:a.context-menu-action.submenu
{:data-no-close true
:on-click (enter-submenu option-name sub-options)}
diff --git a/frontend/src/app/main/ui/components/fullscreen.cljs b/frontend/src/app/main/ui/components/fullscreen.cljs
deleted file mode 100644
index 21ee1f29b7..0000000000
--- a/frontend/src/app/main/ui/components/fullscreen.cljs
+++ /dev/null
@@ -1,53 +0,0 @@
-;; This Source Code Form is subject to the terms of the Mozilla Public
-;; License, v. 2.0. If a copy of the MPL was not distributed with this
-;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
-;;
-;; Copyright (c) UXBOX Labs SL
-
-(ns app.main.ui.components.fullscreen
- (:require
- [app.util.dom :as dom]
- [app.util.webapi :as wapi]
- [rumext.alpha :as mf]))
-
-(def fullscreen-context
- (mf/create-context))
-
-(mf/defc fullscreen-wrapper
- [{:keys [children] :as props}]
- (let [container (mf/use-ref)
- state (mf/use-state (dom/fullscreen?))
-
- change
- (mf/use-callback
- (fn [_]
- (let [val (dom/fullscreen?)]
- (reset! state val))))
-
- manager
- (mf/use-memo
- (mf/deps @state)
- (fn []
- (specify! state
- cljs.core/IFn
- (-invoke
- ([it val]
- (if val
- (wapi/request-fullscreen (mf/ref-val container))
- (wapi/exit-fullscreen)))))))]
-
- ;; NOTE: the user interaction with F11 keyboard hot-key does not
- ;; emits the `fullscreenchange` event; that event is emmited only
- ;; when API is used. There are no way to detect the F11 behavior
- ;; in a uniform cross browser way.
-
- (mf/use-effect
- (fn []
- (.addEventListener js/document "fullscreenchange" change)
- (fn []
- (.removeEventListener js/document "fullscreenchange" change))))
-
- [:div.fulllscreen-wrapper {:ref container :class (dom/classnames :fullscreen @state)}
- [:& (mf/provider fullscreen-context) {:value manager}
- children]]))
-
diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs
index 5f5158aaf6..c2e81f8371 100644
--- a/frontend/src/app/main/ui/components/numeric_input.cljs
+++ b/frontend/src/app/main/ui/components/numeric_input.cljs
@@ -10,10 +10,14 @@
[app.common.math :as math]
[app.common.spec :as us]
[app.util.dom :as dom]
+ [app.util.globals :as globals]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.simple-math :as sm]
- [rumext.alpha :as mf]))
+ [app.util.strings :as ust]
+ [goog.events :as events]
+ [rumext.alpha :as mf])
+ (:import goog.events.EventType))
(defn num? [val]
(and (number? val)
@@ -24,13 +28,16 @@
{::mf/wrap-props false
::mf/forward-ref true}
[props external-ref]
- (let [value-str (obj/get props "value")
- min-val-str (obj/get props "min")
- max-val-str (obj/get props "max")
- wrap-value? (obj/get props "data-wrap")
- on-change (obj/get props "onChange")
- title (obj/get props "title")
- default-val (obj/get props "default" 0)
+ (let [value-str (obj/get props "value")
+ min-val-str (obj/get props "min")
+ max-val-str (obj/get props "max")
+ step-val-str (obj/get props "step")
+ wrap-value? (obj/get props "data-wrap")
+ on-change (obj/get props "onChange")
+ on-blur (obj/get props "onBlur")
+ title (obj/get props "title")
+ default-val (obj/get props "default" 0)
+ precision (obj/get props "precision")
;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here).
@@ -38,23 +45,36 @@
local-ref (mf/use-ref)
ref (or external-ref local-ref)
+ ;; We need to store the handle-blur ref so we can call it on unmount
+ handle-blur-ref (mf/use-ref nil)
+ dirty-ref (mf/use-ref false)
+
;; This `value` represents the previous value and is used as
;; initil value for the simple math expression evaluation.
- value (d/parse-integer value-str default-val)
+ value (d/parse-double value-str default-val)
min-val (cond
(number? min-val-str)
min-val-str
(string? min-val-str)
- (d/parse-integer min-val-str))
+ (d/parse-double min-val-str))
max-val (cond
(number? max-val-str)
max-val-str
(string? max-val-str)
- (d/parse-integer max-val-str))
+ (d/parse-double max-val-str))
+
+ step-val (cond
+ (number? step-val-str)
+ step-val-str
+
+ (string? step-val-str)
+ (d/parse-double step-val-str)
+
+ :else 1)
parse-value
(mf/use-callback
@@ -65,7 +85,10 @@
(sm/expr-eval value))]
(when (num? new-value)
(-> new-value
- (math/round)
+ (cond-> (number? precision)
+ (math/precision precision))
+ (cond-> (nil? precision)
+ (math/round))
(cljs.core/max us/min-safe-int)
(cljs.core/min us/max-safe-int)
(cond->
@@ -80,12 +103,15 @@
(mf/deps ref)
(fn [new-value]
(let [input-node (mf/ref-val ref)]
- (dom/set-value! input-node (str new-value)))))
+ (dom/set-value! input-node (if (some? precision)
+ (ust/format-precision new-value precision)
+ (str new-value))))))
apply-value
(mf/use-callback
(mf/deps on-change update-input value)
(fn [new-value]
+ (mf/set-ref-val! dirty-ref false)
(when (and (not= new-value value) (some? on-change))
(on-change new-value))
(update-input new-value)))
@@ -97,18 +123,18 @@
(let [current-value (parse-value)]
(when current-value
(let [increment (if (kbd/shift? event)
- (if up? 10 -10)
- (if up? 1 -1))
+ (if up? (* step-val 10) (* step-val -10))
+ (if up? step-val (- step-val)))
new-value (+ current-value increment)
new-value (cond
(and wrap-value? (num? max-val) (num? min-val)
(> new-value max-val) up?)
- (-> new-value (- max-val) (+ min-val) (- 1))
+ (-> new-value (- max-val) (+ min-val) (- step-val))
(and wrap-value? (num? min-val) (num? max-val)
(< new-value min-val) down?)
- (-> new-value (- min-val) (+ max-val) (+ 1))
+ (-> new-value (- min-val) (+ max-val) (+ step-val))
(and (num? min-val) (< new-value min-val))
min-val
@@ -124,15 +150,16 @@
(mf/use-callback
(mf/deps set-delta apply-value update-input)
(fn [event]
+ (mf/set-ref-val! dirty-ref true)
(let [up? (kbd/up-arrow? event)
down? (kbd/down-arrow? event)
enter? (kbd/enter? event)
- esc? (kbd/esc? event)]
+ esc? (kbd/esc? event)
+ input-node (mf/ref-val ref)]
(when (or up? down?)
(set-delta event up? down?))
(when enter?
- (let [new-value (parse-value)]
- (apply-value new-value)))
+ (dom/blur! input-node))
(when esc?
(update-input value-str)))))
@@ -144,12 +171,22 @@
handle-blur
(mf/use-callback
- (mf/deps parse-value apply-value update-input)
- (fn [_]
- (let [new-value (or (parse-value) default-val)]
- (if new-value
- (apply-value new-value)
- (update-input new-value)))))
+ (mf/deps parse-value apply-value update-input on-blur)
+ (fn [_]
+ (let [new-value (or (parse-value) default-val)]
+ (if new-value
+ (apply-value new-value)
+ (update-input new-value)))
+ (when on-blur (on-blur))))
+
+ on-click
+ (mf/use-callback
+ (fn [event]
+ (let [target (dom/get-target event)]
+ (when (some? ref)
+ (let [current (mf/ref-val ref)]
+ (when (and (some? current) (not (.contains current target)))
+ (dom/blur! current)))))))
props (-> props
(obj/without ["value" "onChange"])
@@ -166,8 +203,26 @@
(mf/deps value-str)
(fn []
(when-let [input-node (mf/ref-val ref)]
- (when-not (dom/active? input-node)
- (dom/set-value! input-node value-str)))))
+ (dom/set-value! input-node value-str))))
+
+ (mf/use-effect
+ (mf/deps handle-blur)
+ (fn []
+ (mf/set-ref-val! handle-blur-ref {:fn handle-blur})))
+
+ (mf/use-layout-effect
+ (fn []
+ #(when (mf/ref-val dirty-ref)
+ (let [handle-blur (:fn (mf/ref-val handle-blur-ref))]
+ (handle-blur)))))
+
+ (mf/use-layout-effect
+ (fn []
+ (let [keys [(events/listen globals/window EventType.POINTERDOWN on-click)
+ (events/listen globals/window EventType.MOUSEDOWN on-click)
+ (events/listen globals/window EventType.CLICK on-click)]]
+ #(doseq [key keys]
+ (events/unlistenByKey key)))))
[:> :input props]))
diff --git a/frontend/src/app/main/ui/cursors.clj b/frontend/src/app/main/ui/cursors.clj
index 0b63e5f579..98ee3ea519 100644
--- a/frontend/src/app/main/ui/cursors.clj
+++ b/frontend/src/app/main/ui/cursors.clj
@@ -25,7 +25,7 @@
;; Remove comments
(str/replace #"<\!\-\-(.*?(?=\-\->))\-\->" "")
- ;; Remofe end of line
+ ;; Remove end of line
(str/replace #"\r?\n|\r" " ")
;; Replace double quotes for single
diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs
index 500cb83548..33addf9f1e 100644
--- a/frontend/src/app/main/ui/dashboard.cljs
+++ b/frontend/src/app/main/ui/dashboard.cljs
@@ -8,6 +8,7 @@
(:require
[app.common.spec :as us]
[app.main.data.dashboard :as dd]
+ [app.main.data.dashboard.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.context :as ctx]
@@ -20,6 +21,7 @@
[app.main.ui.dashboard.search :refer [search-page]]
[app.main.ui.dashboard.sidebar :refer [sidebar]]
[app.main.ui.dashboard.team :refer [team-settings-page team-members-page]]
+ [app.main.ui.hooks :as hooks]
[rumext.alpha :as mf]))
(defn ^boolean uuid-str?
@@ -88,6 +90,8 @@
projects (mf/deref refs/dashboard-projects)
project (get projects project-id)]
+ (hooks/use-shortcuts ::dashboard sc/shortcuts)
+
(mf/use-effect
(mf/deps team-id)
(fn []
@@ -99,8 +103,8 @@
;; that the team is a implicit context variable that is
;; available using react context or accessing
;; the :current-team-id on the state. We set the key to the
- ;; team-id becase we want to completly refresh all the
- ;; components on team change. Many components assumess that the
+ ;; team-id because we want to completely refresh all the
+ ;; components on team change. Many components assumes that the
;; team is already set so don't put the team into mf/deps.
(when team
[:section.dashboard-layout {:key (:id team)}
diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs
index 68240a79ca..5a1425ba6b 100644
--- a/frontend/src/app/main/ui/dashboard/export.cljs
+++ b/frontend/src/app/main/ui/dashboard/export.cljs
@@ -7,7 +7,6 @@
(ns app.main.ui.dashboard.export
(:require
[app.common.data :as d]
- [app.main.data.events :as ev]
[app.main.data.modal :as modal]
[app.main.store :as st]
[app.main.ui.icons :as i]
@@ -15,7 +14,6 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[beicon.core :as rx]
- [potok.core :as ptk]
[rumext.alpha :as mf]))
(def ^:const options [:all :merge :detach])
@@ -60,10 +58,6 @@
start-export
(fn []
- (st/emit! (ptk/event ::ev/event {::ev/name "export-files"
- :num-files (count (:files @state))
- :option @selected-option}))
-
(swap! state assoc :status :exporting)
(->> (uw/ask-many!
{:cmd :export-file
@@ -100,7 +94,7 @@
(mf/use-effect
(fn []
(when-not has-libraries?
- ;; Start download automaticaly
+ ;; Start download automatically
(start-export))))
[:div.modal-overlay
diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs
index 4ac841f2fd..dba16d15b1 100644
--- a/frontend/src/app/main/ui/dashboard/file_menu.cljs
+++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs
@@ -75,11 +75,9 @@
on-new-tab
(fn [_]
(let [path-params {:project-id (:project-id file)
- :file-id (:id file)}
- query-params {:page-id (first (get-in file [:data :pages]))}]
+ :file-id (:id file)}]
(st/emit! (rt/nav-new-window* {:rname :workspace
- :path-params path-params
- :query-params query-params}))))
+ :path-params path-params}))))
on-duplicate
(fn [_]
@@ -164,6 +162,7 @@
(mf/deps files current-team-id)
(fn [_]
(st/emit! (ptk/event ::ev/event {::ev/name "export-files"
+ ::ev/origin "dashboard"
:num-files (count files)}))
(->> (rx/from files)
(rx/flat-map
diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs
index ef413c78ce..9d050206ab 100644
--- a/frontend/src/app/main/ui/dashboard/files.cljs
+++ b/frontend/src/app/main/ui/dashboard/files.cljs
@@ -16,6 +16,7 @@
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
+ [cuerdas.core :as str]
[rumext.alpha :as mf]))
(mf/defc header
@@ -63,9 +64,11 @@
(if (:edition @local)
[:& inline-edition {:content (:name project)
:on-end (fn [name]
- (st/emit! (-> (dd/rename-project (assoc project :name name))
- (with-meta {::ev/origin "project"})))
- (swap! local assoc :edition false))}]
+ (let [name (str/trim name)]
+ (when-not (str/empty? name)
+ (st/emit! (-> (dd/rename-project (assoc project :name name))
+ (with-meta {::ev/origin "project"}))))
+ (swap! local assoc :edition false)))}]
[:div.dashboard-title
[:h1 {:on-double-click on-edit}
(:name project)]]))
diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs
index dd35cd4464..0199f42130 100644
--- a/frontend/src/app/main/ui/dashboard/fonts.cljs
+++ b/frontend/src/app/main/ui/dashboard/fonts.cljs
@@ -103,6 +103,10 @@
(fn [error]
(js/console.log "error" error))))))
+ on-upload-all
+ (fn [items]
+ (run! on-upload items))
+
on-blur-name
(fn [id event]
(let [name (dom/get-target-val event)]
@@ -112,7 +116,11 @@
(mf/use-callback
(mf/deps team)
(fn [{:keys [id] :as item}]
- (swap! fonts dissoc id)))]
+ (swap! fonts dissoc id)))
+
+ on-dismiss-all
+ (fn [items]
+ (run! on-delete items))]
[:div.dashboard-fonts-upload
[:div.dashboard-fonts-hero
@@ -136,6 +144,17 @@
:on-selected on-selected}]]]
[:*
+ (when (some? (vals @fonts))
+ [:div.font-item.table-row
+ [:span (tr "dashboard.fonts.fonts-added" (i18n/c (count (vals @fonts))))]
+ [:div.table-field.options
+ [:div.btn-primary
+ {:on-click #(on-upload-all (vals @fonts))}
+ [:span (tr "dashboard.fonts.upload-all")]]
+ [:div.btn-secondary
+ {:on-click #(on-dismiss-all (vals @fonts))}
+ [:span (tr "dashboard.fonts.dismiss-all")]]]])
+
(for [item (sort-by :font-family (vals @fonts))]
(let [uploading? (contains? @uploading (:id item))]
[:div.font-item.table-row {:key (:id item)}
diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs
index dc181f6e4f..c6d84161eb 100644
--- a/frontend/src/app/main/ui/dashboard/grid.cljs
+++ b/frontend/src/app/main/ui/dashboard/grid.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.dashboard.grid
(:require
+ [app.common.logging :as log]
[app.common.math :as mth]
[app.main.data.dashboard :as dd]
[app.main.data.messages :as dm]
@@ -26,26 +27,89 @@
[app.util.timers :as ts]
[app.util.webapi :as wapi]
[beicon.core :as rx]
+ [cuerdas.core :as str]
+ [promesa.core :as p]
[rumext.alpha :as mf]))
+(log/set-level! :warn)
+
;; --- Grid Item Thumbnail
+(def ^:const CACHE-NAME "penpot")
+(def ^:const CACHE-URL "https://penpot.app/cache/")
+
+(defn use-thumbnail-cache
+ "Creates some hooks to handle the files thumbnails cache"
+ [file]
+
+ (let [cache-url (str CACHE-URL (:id file) "/" (:revn file) ".svg")
+
+ get-thumbnail
+ (mf/use-callback
+ (mf/deps cache-url)
+ (fn []
+ (p/let [response (.match js/caches cache-url)]
+ (when (some? response)
+ (p/let [blob (.blob response)
+ svg-content (.text blob)
+ headers (.-headers response)
+ fonts-header (or (.get headers "X-PENPOT-FONTS") "")
+ fonts (into #{}
+ (remove #(= "" %))
+ (str/split fonts-header ","))]
+ {:svg svg-content
+ :fonts fonts})))))
+
+ cache-thumbnail
+ (mf/use-callback
+ (mf/deps cache-url)
+ (fn [{:keys [svg fonts]}]
+ (p/let [cache (.open js/caches CACHE-NAME)
+ blob (js/Blob. #js [svg] #js {:type "image/svg"})
+ fonts (str/join "," fonts)
+ headers (js/Headers. #js {"X-PENPOT-FONTS" fonts})
+ response (js/Response. blob #js {:headers headers})]
+ (.put cache cache-url response))))]
+
+ (mf/use-callback
+ (mf/deps (:id file) (:revn file))
+ (fn []
+ (->> (rx/from (get-thumbnail))
+ (rx/merge-map
+ (fn [thumb-data]
+ (log/debug :msg "retrieve thumbnail" :file (:id file) :revn (:revn file)
+ :cache (if (some? thumb-data) :hit :miss))
+
+ (if (some? thumb-data)
+ (rx/of thumb-data)
+ (->> (wrk/ask! {:cmd :thumbnails/generate
+ :file-id (:id file)
+ :page-id (get-in file [:data :pages 0])})
+ (rx/tap cache-thumbnail)))))
+
+ ;; If we have a problem we delegate to the thumbnail generation
+ (rx/catch #(wrk/ask! {:cmd :thumbnails/generate
+ :file-id (:id file)
+ :page-id (get-in file [:data :pages 0])})))))))
+
(mf/defc grid-item-thumbnail
{::mf/wrap [mf/memo]}
[{:keys [file] :as props}]
- (let [container (mf/use-ref)]
+ (let [container (mf/use-ref)
+ generate (use-thumbnail-cache file)]
+
(mf/use-effect
- (mf/deps (:id file))
+ (mf/deps file)
(fn []
- (->> (wrk/ask! {:cmd :thumbnails/generate
- :file-id (:id file)
- :page-id (get-in file [:data :pages 0])})
+ (->> (generate)
(rx/subs (fn [{:keys [svg fonts]}]
(run! fonts/ensure-loaded! fonts)
(when-let [node (mf/ref-val container)]
- (set! (.-innerHTML ^js node) svg)))))))
+ (dom/set-html! node svg)))))))
+
[:div.grid-item-th {:style {:background-color (get-in file [:data :options :background])}
- :ref container}]))
+ :ref container}
+ i/loader-pencil]))
;; --- Grid Item
@@ -205,6 +269,7 @@
(mf/use-callback
(fn []
(st/emit! (dd/fetch-files {:project-id project-id})
+ (dd/fetch-shared-files)
(dd/clear-selected-files))))
import-files (use-import-file project-id on-finish-import)
diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs
index a076763648..6edfbb955b 100644
--- a/frontend/src/app/main/ui/dashboard/import.cljs
+++ b/frontend/src/app/main/ui/dashboard/import.cljs
@@ -98,7 +98,7 @@
(filter #(= :ready (:status %)))
(mapv #(assoc % :status :importing))))
-(defn update-status [files file-id status progress]
+(defn update-status [files file-id status progress errors]
(->> files
(mapv (fn [file]
(cond-> file
@@ -106,7 +106,10 @@
(assoc :status status)
(and (= file-id (:file-id file)) (= status :import-progress))
- (assoc :progress progress))))))
+ (assoc :progress progress)
+
+ (= file-id (:file-id file))
+ (assoc :errors errors))))))
(defn parse-progress-message
[message]
@@ -139,9 +142,10 @@
(let [loading? (or (= :analyzing (:status file))
(= :importing (:status file)))
- load-success? (= :import-success (:status file))
analyze-error? (= :analyze-error (:status file))
+ import-finish? (= :import-finish (:status file))
import-error? (= :import-error (:status file))
+ import-warn? (d/not-empty? (:errors file))
ready? (= :ready (:status file))
is-shared? (:shared file)
progress (:progress file)
@@ -177,7 +181,8 @@
[:div.file-entry
{:class (dom/classnames
:loading loading?
- :success load-success?
+ :success (and import-finish? (not import-warn?) (not import-error?))
+ :warning (and import-finish? import-warn? (not import-error?))
:error (or import-error? analyze-error?)
:editable (and ready? (not editing?)))}
@@ -185,8 +190,9 @@
[:div.file-icon
(cond loading? i/loader-pencil
ready? i/logo-icon
- load-success? i/tick
+ import-warn? i/msg-warning
import-error? i/close
+ import-finish? i/tick
analyze-error? i/close)]
(if editing?
@@ -212,7 +218,7 @@
[:div.error-message
(tr "dashboard.import.import-error")]
- (and (not load-success?) (some? progress))
+ (and (not import-finish?) (some? progress))
[:div.progress-message (parse-progress-message progress)])
[:div.linked-libraries
@@ -242,7 +248,7 @@
(rx/delay-emit emit-delay)
(rx/subs
(fn [{:keys [uri data error] :as msg}]
- (log/debug :msg msg)
+ (log/debug :uri uri :data data :error error)
(if (some? error)
(swap! state update :files set-analyze-error uri)
(swap! state update :files set-analyze-result uri data)))))))
@@ -258,9 +264,8 @@
:project-id project-id
:files files})
(rx/subs
- (fn [{:keys [file-id status message] :as msg}]
- (log/debug :msg msg)
- (swap! state update :files update-status file-id status message))))))
+ (fn [{:keys [file-id status message errors] :as msg}]
+ (swap! state update :files update-status file-id status message errors))))))
handle-cancel
(mf/use-callback
@@ -291,7 +296,8 @@
(st/emit! (modal/hide))
(when on-finish-import (on-finish-import))))
- success-files (->> @state :files (filter #(= (:status %) :import-success)) count)
+ warning-files (->> @state :files (filter #(and (= (:status %) :import-finish) (d/not-empty? (:errors %)))) count)
+ success-files (->> @state :files (filter #(and (= (:status %) :import-finish) (empty? (:errors %)))) count)
pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0)
pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)]
@@ -316,11 +322,15 @@
{:on-click handle-cancel} i/close]]
[:div.modal-content
- (when (and (= :importing (:status @state))
- (not pending-import?))
- [:div.feedback-banner
- [:div.icon i/checkbox-checked]
- [:div.message (tr "dashboard.import.import-message" success-files)]])
+ (when (and (= :importing (:status @state)) (not pending-import?))
+ (if (> warning-files 0)
+ [:div.feedback-banner.warning
+ [:div.icon i/msg-warning]
+ [:div.message (tr "dashboard.import.import-warning" warning-files success-files)]]
+
+ [:div.feedback-banner
+ [:div.icon i/checkbox-checked]
+ [:div.message (tr "dashboard.import.import-message" success-files)]]))
(for [file (->> (:files @state) (filterv (comp not :deleted?)))]
(let [editing? (and (some? (:file-id file))
diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs
index 98ff52fae2..618855c2c2 100644
--- a/frontend/src/app/main/ui/dashboard/libraries.cljs
+++ b/frontend/src/app/main/ui/dashboard/libraries.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.dashboard.libraries
(:require
+ [app.common.data :as d]
[app.main.data.dashboard :as dd]
[app.main.refs :as refs]
[app.main.store :as st]
@@ -17,6 +18,8 @@
(mf/defc libraries-page
[{:keys [team] :as props}]
(let [files-map (mf/deref refs/dashboard-shared-files)
+ projects (mf/deref refs/dashboard-projects)
+ default-project (->> projects vals (d/seek :is-default))
files (->> (vals files-map)
(sort-by :modified-at)
(reverse))]
@@ -38,5 +41,6 @@
[:div.dashboard-title
[:h1 (tr "dashboard.libraries-title")]]]
[:section.dashboard-container
- [:& grid {:files files}]]]))
+ [:& grid {:files files
+ :project default-project}]]]))
diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs
index e1820cc1a8..1df2385378 100644
--- a/frontend/src/app/main/ui/dashboard/project_menu.cljs
+++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.dashboard.project-menu
(:require
+ [app.common.spec :as us]
[app.main.data.dashboard :as dd]
[app.main.data.messages :as dm]
[app.main.data.modal :as modal]
@@ -17,14 +18,24 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
+ [cljs.spec.alpha :as s]
[rumext.alpha :as mf]))
+(s/def ::project some?)
+(s/def ::show? boolean?)
+(s/def ::on-edit fn?)
+(s/def ::on-menu-close fn?)
+(s/def ::top (s/nilable ::us/number))
+(s/def ::left (s/nilable ::us/number))
+(s/def ::on-import fn?)
+
+(s/def ::project-menu
+ (s/keys :req-un [::project ::show? ::on-edit ::on-menu-close]
+ :opt-un [::top ::left ::on-import]))
+
(mf/defc project-menu
[{:keys [project show? on-edit on-menu-close top left on-import] :as props}]
- (assert (some? project) "missing `project` prop")
- (assert (boolean? show?) "missing `show?` prop")
- (assert (fn? on-edit) "missing `on-edit` prop")
- (assert (fn? on-menu-close) "missing `on-menu-close` prop")
+ (us/verify ::project-menu props)
(let [top (or top 0)
left (or left 0)
@@ -83,7 +94,7 @@
on-finish-import
(mf/use-callback
(fn []
- (when (some? on-import) (on-import))))]
+ (when (fn? on-import) (on-import))))]
[:*
[:& udi/import-form {:ref file-input
diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs
index d25620b048..9a3ddc966b 100644
--- a/frontend/src/app/main/ui/dashboard/projects.cljs
+++ b/frontend/src/app/main/ui/dashboard/projects.cljs
@@ -18,6 +18,7 @@
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.time :as dt]
+ [cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
@@ -73,9 +74,11 @@
(mf/use-callback
(mf/deps project)
(fn [name]
- (st/emit! (-> (dd/rename-project (assoc project :name name))
- (with-meta {::ev/origin "dashboard"})))
- (swap! local assoc :edition? false)))
+ (let [name (str/trim name)]
+ (when-not (str/empty? name)
+ (st/emit! (-> (dd/rename-project (assoc project :name name))
+ (with-meta {::ev/origin "dashboard"}))))
+ (swap! local assoc :edition? false))))
on-file-created
(mf/use-callback
diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs
index a8984d2c6c..061b41f45b 100644
--- a/frontend/src/app/main/ui/dashboard/sidebar.cljs
+++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs
@@ -238,7 +238,7 @@
members (vals members-map)
options (into [{:value ""
- :label (tr "modals.leave-and-reassign.select-memeber-to-promote")}]
+ :label (tr "modals.leave-and-reassign.select-member-to-promote")}]
(map #(hash-map :label (:name %) :value (str (:id %))) members))
on-cancel (st/emitf (modal/hide))
diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs
index 1a936b26fb..d7088c0303 100644
--- a/frontend/src/app/main/ui/dashboard/team_form.cljs
+++ b/frontend/src/app/main/ui/dashboard/team_form.cljs
@@ -25,14 +25,14 @@
(defn- on-create-success
[_form response]
- (let [msg "Team created successfuly"]
+ (let [msg "Team created successfully"]
(st/emit! (dm/success msg)
(modal/hide)
(rt/nav :dashboard-projects {:team-id (:id response)}))))
(defn- on-update-success
[_form _response]
- (let [msg "Team created successfuly"]
+ (let [msg "Team created successfully"]
(st/emit! (dm/success msg)
(modal/hide))))
diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs
index 484974c1da..9280fc14a6 100644
--- a/frontend/src/app/main/ui/hooks.cljs
+++ b/frontend/src/app/main/ui/hooks.cljs
@@ -91,9 +91,9 @@
(fn []
(some-> (:subscr @state) rx/unsub!)
(swap! state (fn [state]
- (-> state
- (cancel-timer)
- (dissoc :over :subscr)))))
+ (-> state
+ (cancel-timer)
+ (dissoc :over :subscr)))))
subscribe-to-drag-end
(fn []
@@ -191,7 +191,7 @@
(defn use-stream
- "Wraps the subscription to a strem into a `use-effect` call"
+ "Wraps the subscription to a stream into a `use-effect` call"
([stream on-subscribe]
(use-stream stream (mf/deps) on-subscribe))
([stream deps on-subscribe]
diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs
index fce2cdc654..0230fa055a 100644
--- a/frontend/src/app/main/ui/icons.cljs
+++ b/frontend/src/app/main/ui/icons.cljs
@@ -17,6 +17,10 @@
(def align-middle (icon-xref :align-middle))
(def align-top (icon-xref :align-top))
(def alignment (icon-xref :alignment))
+(def animate-down (icon-xref :animate-down))
+(def animate-left (icon-xref :animate-left))
+(def animate-right (icon-xref :animate-right))
+(def animate-up (icon-xref :animate-up))
(def arrow-down (icon-xref :arrow-down))
(def arrow-end (icon-xref :arrow-end))
(def arrow-slide (icon-xref :arrow-slide))
@@ -25,11 +29,11 @@
(def auto-fix (icon-xref :auto-fix))
(def auto-height (icon-xref :auto-height))
(def auto-width (icon-xref :auto-width))
-(def boolean-difference (icon-xref :boolean-difference))
-(def boolean-exclude (icon-xref :boolean-exclude))
-(def boolean-flatten (icon-xref :boolean-flatten))
-(def boolean-intersection (icon-xref :boolean-intersection))
-(def boolean-union (icon-xref :boolean-union))
+(def bool-difference (icon-xref :boolean-difference))
+(def bool-exclude (icon-xref :boolean-exclude))
+(def bool-flatten (icon-xref :boolean-flatten))
+(def bool-intersection (icon-xref :boolean-intersection))
+(def bool-union (icon-xref :boolean-union))
(def box (icon-xref :box))
(def chain (icon-xref :chain))
(def chat (icon-xref :chat))
@@ -42,6 +46,11 @@
(def copy (icon-xref :copy))
(def curve (icon-xref :curve))
(def download (icon-xref :download))
+(def easing-linear (icon-xref :easing-linear))
+(def easing-ease (icon-xref :easing-ease))
+(def easing-ease-in (icon-xref :easing-ease-in))
+(def easing-ease-out (icon-xref :easing-ease-out))
+(def easing-ease-in-out (icon-xref :easing-ease-in-out))
(def exit (icon-xref :exit))
(def export (icon-xref :export))
(def eye (icon-xref :eye))
diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs
index e4dea1db10..03c7efc122 100644
--- a/frontend/src/app/main/ui/measurements.cljs
+++ b/frontend/src/app/main/ui/measurements.cljs
@@ -21,23 +21,23 @@
(def font-size 10)
(def selection-rect-width 1)
-(def select-color "#1FDEA7")
+(def select-color "var(--color-select)")
(def select-guide-width 1)
(def select-guide-dasharray 5)
-(def hover-color "#DB00FF")
-(def hover-color-text "#FFF")
+(def hover-color "var(--color-distance)")
+(def hover-color-text "var(--color-white)")
(def hover-guide-width 1)
-(def size-display-color "#FFF")
+(def size-display-color "var(--color-white)")
(def size-display-opacity 0.7)
-(def size-display-text-color "#000")
+(def size-display-text-color "var(--color-black)")
(def size-display-width-min 50)
(def size-display-width-max 75)
(def size-display-height 16)
-(def distance-color "#DB00FF")
-(def distance-text-color "#FFF")
+(def distance-color "var(--color-distance)")
+(def distance-text-color "var(--color-white)")
(def distance-border-radius 2)
(def distance-pill-width 40)
(def distance-pill-height 16)
diff --git a/frontend/src/app/main/ui/onboarding/templates.cljs b/frontend/src/app/main/ui/onboarding/templates.cljs
index 91a886d346..de4a5b381a 100644
--- a/frontend/src/app/main/ui/onboarding/templates.cljs
+++ b/frontend/src/app/main/ui/onboarding/templates.cljs
@@ -61,8 +61,9 @@
::mf/register-as :onboarding-templates}
;; NOTE: the project usually comes empty, it only comes fullfilled
;; when a user creates a new team just after signup.
- [{:keys [project-id] :as props}]
- (let [close-fn (mf/use-callback #(st/emit! (modal/hide)))
+ [props]
+ (let [project-id (unchecked-get props "project-id")
+ close-fn (mf/use-callback #(st/emit! (modal/hide)))
profile (mf/deref refs/profile)
project-id (or project-id (:default-project-id profile))]
[:div.modal-overlay
diff --git a/frontend/src/app/main/ui/releases.cljs b/frontend/src/app/main/ui/releases.cljs
index 1fee4c1d2c..85b6b499b1 100644
--- a/frontend/src/app/main/ui/releases.cljs
+++ b/frontend/src/app/main/ui/releases.cljs
@@ -11,6 +11,7 @@
[app.main.store :as st]
[app.main.ui.releases.common :as rc]
[app.main.ui.releases.v1-10]
+ [app.main.ui.releases.v1-11]
[app.main.ui.releases.v1-4]
[app.main.ui.releases.v1-5]
[app.main.ui.releases.v1-6]
@@ -81,4 +82,4 @@
(defmethod rc/render-release-notes "0.0"
[params]
- (rc/render-release-notes (assoc params :version "1.10")))
+ (rc/render-release-notes (assoc params :version "1.11")))
diff --git a/frontend/src/app/main/ui/releases/v1_11.cljs b/frontend/src/app/main/ui/releases/v1_11.cljs
new file mode 100644
index 0000000000..56e8dacef8
--- /dev/null
+++ b/frontend/src/app/main/ui/releases/v1_11.cljs
@@ -0,0 +1,89 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) UXBOX Labs SL
+
+(ns app.main.ui.releases.v1-11
+ (:require
+ [app.main.ui.releases.common :as c]
+ [rumext.alpha :as mf]))
+
+(defmethod c/render-release-notes "1.11"
+ [{:keys [slide klass next finish navigate version]}]
+ (mf/html
+ (case @slide
+ :start
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/login-on.jpg" :border "0" :alt "What's new Beta release 1.11"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "What's new?"]]
+ [:span.release "Beta version " version]
+ [:div.modal-content
+ [:p "Penpot continues growing with new features that improve performance, user experience and visual design."]
+ [:p "We are happy to show you a sneak peak of the most important stuff that the Beta 1.11 version brings."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]]]
+ [:img.deco {:src "images/deco-left.png" :border "0"}]
+ [:img.deco.right {:src "images/deco-right.png" :border "0"}]]]]
+
+ 0
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/1.11-animations.gif" :border "0" :alt "Animations"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Prototype animations"]]
+ [:div.modal-content
+ [:p "Bring your prototypes to life with animations! With animations now you can define the transition between artboards when an interaction is triggered."]
+ [:p "Use dissolve, slide and push animations to fade screens and imitate gestures like swipe."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 3}]]]]]]
+
+ 1
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/1.11-bg-export.gif" :border "0" :alt "Ignore background on export"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "Ignore artboard background on export"]]
+ [:div.modal-content
+ [:p "Sometimes you don’t need the artboards to be part of your designs, but only their support to work on them."]
+ [:p "Now you can decide to include their backgrounds on your exports or leave them out."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click next} "Continue"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 3}]]]]]]
+
+ 2
+ [:div.modal-overlay
+ [:div.animated {:class @klass}
+ [:div.modal-container.onboarding.feature
+ [:div.modal-left
+ [:img {:src "images/features/1.11-zoom-widget.gif" :border "0" :alt "New zoom widget"}]]
+ [:div.modal-right
+ [:div.modal-title
+ [:h2 "New zoom widget"]]
+ [:div.modal-content
+ [:p "We’ve redesigned zooming menus to improve their usability and the consistency between zooming in the design workspace and in the view mode."]
+ [:p "We’ve also added two new options to scale your designs at the view mode that might help you to make your presentations look better."]]
+ [:div.modal-navigation
+ [:button.btn-secondary {:on-click finish} "Start!"]
+ [:& c/navigation-bullets
+ {:slide @slide
+ :navigate navigate
+ :total 3}]]]]]])))
diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs
index f7107e7188..d5909a9ed5 100644
--- a/frontend/src/app/main/ui/render.cljs
+++ b/frontend/src/app/main/ui/render.cljs
@@ -13,12 +13,11 @@
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.data.fonts :as df]
- [app.main.exports :as exports]
+ [app.main.render :as render]
[app.main.repo :as repo]
[app.main.store :as st]
[app.main.ui.context :as muc]
[app.main.ui.shapes.embed :as embed]
- [app.main.ui.shapes.export :as ed]
[app.main.ui.shapes.filters :as filters]
[app.main.ui.shapes.shape :refer [shape-container]]
[app.util.dom :as dom]
@@ -28,15 +27,13 @@
(defn calc-bounds
[object objects]
-
- (let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
- padding (filters/calculate-padding object)
- obj-bounds
- (-> (filters/get-filters-bounds object)
- (update :x - padding)
- (update :y - padding)
- (update :width + (* 2 padding))
- (update :height + (* 2 padding)))]
+ (let [xf-get-bounds (comp (map #(get objects %)) (map #(calc-bounds % objects)))
+ padding (filters/calculate-padding object)
+ obj-bounds (-> (filters/get-filters-bounds object)
+ (update :x - padding)
+ (update :y - padding)
+ (update :width + (* 2 padding))
+ (update :height + (* 2 padding)))]
(cond
(and (= :group (:type object))
@@ -59,8 +56,6 @@
(:id object)
(:frame-id object))
- include-metadata? (mf/use-ctx ed/include-metadata-ctx)
-
modifier (-> (gpt/point (:x object) (:y object))
(gpt/negate)
(gmt/translate-matrix))
@@ -73,6 +68,10 @@
objects (reduce updt-fn objects mod-ids)
object (get objects object-id)
+ object (cond-> object
+ (:hide-fill-on-export object)
+ (assoc :fill-color nil :fill-opacity 0))
+
{:keys [x y width height] :as bs} (calc-bounds object objects)
[_ _ width height :as coords] (->> [x y width height] (map #(* % zoom)))
@@ -81,17 +80,17 @@
frame-wrapper
(mf/use-memo
(mf/deps objects)
- #(exports/frame-wrapper-factory objects))
+ #(render/frame-wrapper-factory objects))
group-wrapper
(mf/use-memo
(mf/deps objects)
- #(exports/group-wrapper-factory objects))
+ #(render/group-wrapper-factory objects))
shape-wrapper
(mf/use-memo
(mf/deps objects)
- #(exports/shape-wrapper-factory objects))
+ #(render/shape-wrapper-factory objects))
text-shapes
(->> objects
@@ -103,7 +102,7 @@
#(dom/set-page-style {:size (str (mth/ceil width) "px "
(mth/ceil height) "px")}))
- [:& (mf/provider embed/context) {:value true}
+ [:& (mf/provider embed/context) {:value false}
[:svg {:id "screenshot"
:view-box vbox
:width width
@@ -111,7 +110,6 @@
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
- :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns")
;; Fix Chromium bug about color of html texts
;; https://bugs.chromium.org/p/chromium/issues/detail?id=1244560#c5
:style {:-webkit-print-color-adjust :exact}}
@@ -133,7 +131,7 @@
:version "1.1"
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"}
- [:& shape-wrapper {:shape (-> object (assoc :x 0 :y 0))}]]]))]))
+ [:& shape-wrapper {:shape (assoc object :x 0 :y 0)}]]]))]))
(defn- adapt-root-frame
[objects object-id]
@@ -147,11 +145,6 @@
(assoc objects (:id object) object))
objects))
-
-;; NOTE: for now, it is ok download the entire file for render only
-;; single page but in a future we need consider to add a specific
-;; backend entry point for download only the data of single page.
-
(mf/defc render-object
[{:keys [file-id page-id object-id render-texts?] :as props}]
(let [objects (mf/use-state nil)]
@@ -160,7 +153,7 @@
(fn []
(->> (rx/zip
(repo/query! :font-variants {:file-id file-id})
- (repo/query! :file {:id file-id}))
+ (repo/query! :trimmed-file {:id file-id :page-id page-id :object-id object-id}))
(rx/subs
(fn [[fonts {:keys [data]}]]
(when (seq fonts)
@@ -190,7 +183,7 @@
(when @file
[:*
- [:& exports/components-sprite-svg {:data (:data @file) :embed true}
+ [:& render/components-sprite-svg {:data (:data @file) :embed true}
(when (some? component-id)
[:use {:x 0 :y 0
diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs
index f0b6a32230..12d43561e6 100644
--- a/frontend/src/app/main/ui/settings/password.cljs
+++ b/frontend/src/app/main/ui/settings/password.cljs
@@ -19,16 +19,16 @@
(defn- on-error
[form error]
(case (:code error)
- :app.services.mutations.profile/old-password-not-match
+ :old-password-not-match
(swap! form assoc-in [:errors :password-old]
{:message (tr "errors.wrong-old-password")})
- :else
(let [msg (tr "generic.error")]
(st/emit! (dm/error msg)))))
(defn- on-success
- [_]
+ [form]
+ (reset! form nil)
(let [msg (tr "dashboard.notifications.password-saved")]
(st/emit! (dm/success msg))))
diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs
index 9cf576dede..ae5967d1ba 100644
--- a/frontend/src/app/main/ui/shapes/attrs.cljs
+++ b/frontend/src/app/main/ui/shapes/attrs.cljs
@@ -18,7 +18,8 @@
[width style]
(let [values (case style
:mixed [5 5 1 5]
- :dotted [5 5]
+ ;; We want 0 so they are circles
+ :dotted [(- width) 5]
:dashed [10 10]
nil)]
@@ -132,9 +133,13 @@
;; for inner or outer strokes.
(and (spec/stroke-caps-line (:stroke-cap-start shape))
(= (:stroke-cap-start shape) (:stroke-cap-end shape))
- (not (#{:inner :outer} (:stroke-alignment shape))))
+ (not (#{:inner :outer} (:stroke-alignment shape)))
+ (not= :dotted stroke-style))
(assoc :strokeLinecap (:stroke-cap-start shape))
+ (= :dotted stroke-style)
+ (assoc :strokeLinecap "round")
+
;; For other cap types we use markers.
(and (or (spec/stroke-caps-marker (:stroke-cap-start shape))
(and (spec/stroke-caps-line (:stroke-cap-start shape))
@@ -174,25 +179,28 @@
[attrs styles]))
(defn add-style-attrs
- [props shape]
- (let [render-id (mf/use-ctx muc/render-ctx)
- svg-defs (:svg-defs shape {})
- svg-attrs (:svg-attrs shape {})
+ ([props shape]
+ (let [render-id (mf/use-ctx muc/render-ctx)]
+ (add-style-attrs props shape render-id)))
- [svg-attrs svg-styles] (mf/use-memo
- (mf/deps render-id svg-defs svg-attrs)
- #(extract-svg-attrs render-id svg-defs svg-attrs))
+ ([props shape render-id]
+ (let [svg-defs (:svg-defs shape {})
+ svg-attrs (:svg-attrs shape {})
- styles (-> (obj/get props "style" (obj/new))
- (obj/merge! svg-styles)
- (add-fill shape render-id)
- (add-stroke shape render-id)
- (add-layer-props shape))]
+ [svg-attrs svg-styles] (mf/use-memo
+ (mf/deps render-id svg-defs svg-attrs)
+ #(extract-svg-attrs render-id svg-defs svg-attrs))
- (-> props
- (obj/merge! svg-attrs)
- (add-border-radius shape)
- (obj/set! "style" styles))))
+ styles (-> (obj/get props "style" (obj/new))
+ (obj/merge! svg-styles)
+ (add-fill shape render-id)
+ (add-stroke shape render-id)
+ (add-layer-props shape))]
+
+ (-> props
+ (obj/merge! svg-attrs)
+ (add-border-radius shape)
+ (obj/set! "style" styles)))))
(defn extract-style-attrs
[shape]
diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs
index fd9b58f94d..9d3d418512 100644
--- a/frontend/src/app/main/ui/shapes/bool.cljs
+++ b/frontend/src/app/main/ui/shapes/bool.cljs
@@ -8,107 +8,41 @@
(:require
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
- [app.common.geom.shapes.path :as gsp]
- [app.common.path.bool :as pb]
- [app.common.path.shapes-to-path :as stp]
[app.main.ui.hooks :refer [use-equal-memo]]
[app.main.ui.shapes.export :as use]
[app.main.ui.shapes.path :refer [path-shape]]
[app.util.object :as obj]
[rumext.alpha :as mf]))
-(mf/defc debug-bool
- {::mf/wrap-props false}
- [props]
-
- (let [frame (obj/get props "frame")
- shape (obj/get props "shape")
- childs (obj/get props "childs")
-
- [content-a content-b]
- (mf/use-memo
- (mf/deps shape childs)
- (fn []
- (let [childs (d/mapm #(-> %2 (gsh/translate-to-frame frame) gsh/transform-shape) childs)
- [content-a content-b]
- (->> (:shapes shape)
- (map #(get childs %))
- (filter #(not (:hidden %)))
- (map #(stp/convert-to-path % childs))
- (map :content)
- (map pb/close-paths)
- (map pb/add-previous))]
- (pb/content-intersect-split content-a content-b))))]
- [:g.debug-bool
- [:g.shape-a
- [:& path-shape {:shape (-> shape
- (assoc :type :path)
- (assoc :stroke-color "blue")
- (assoc :stroke-opacity 1)
- (assoc :stroke-width 1)
- (assoc :stroke-style :solid)
- (dissoc :fill-color :fill-opacity)
- (assoc :content content-b))
- :frame frame}]
- (for [{:keys [x y]} (gsp/content->points (pb/close-paths content-b))]
- [:circle {:cx x
- :cy y
- :r 2.5
- :style {:fill "blue"}}])]
-
- [:g.shape-b
- [:& path-shape {:shape (-> shape
- (assoc :type :path)
- (assoc :stroke-color "red")
- (assoc :stroke-opacity 1)
- (assoc :stroke-width 0.5)
- (assoc :stroke-style :solid)
- (dissoc :fill-color :fill-opacity)
- (assoc :content content-a))
- :frame frame}]
- (for [{:keys [x y]} (gsp/content->points (pb/close-paths content-a))]
- [:circle {:cx x
- :cy y
- :r 1.25
- :style {:fill "red"}}])]])
- )
-
-
(defn bool-shape
[shape-wrapper]
(mf/fnc bool-shape
{::mf/wrap-props false}
[props]
- (let [frame (obj/get props "frame")
- shape (obj/get props "shape")
+ (let [shape (obj/get props "shape")
childs (obj/get props "childs")
-
childs (use-equal-memo childs)
-
include-metadata? (mf/use-ctx use/include-metadata-ctx)
bool-content
(mf/use-memo
(mf/deps shape childs)
(fn []
- (let [childs (d/mapm #(-> %2 gsh/transform-shape (gsh/translate-to-frame frame)) childs)]
- (->> (:shapes shape)
- (map #(get childs %))
- (filter #(not (:hidden %)))
- (map #(stp/convert-to-path % childs))
- (mapv :content)
- (pb/content-bool (:bool-type shape))))))]
+ (cond
+ (some? (:bool-content shape))
+ (:bool-content shape)
+
+ (some? childs)
+ (->> childs
+ (d/mapm #(gsh/transform-shape %2))
+ (gsh/calc-bool-content shape)))))]
[:*
- [:& path-shape {:shape (assoc shape :content bool-content)}]
+ (when (some? bool-content)
+ [:& path-shape {:shape (assoc shape :content bool-content)}])
(when include-metadata?
[:> "penpot:bool" {}
(for [item (->> (:shapes shape) (mapv #(get childs %)))]
- [:& shape-wrapper {:frame frame
- :shape item
- :key (:id item)}])])
-
- #_[:& debug-bool {:frame frame
- :shape shape
- :childs childs}]])))
+ [:& shape-wrapper {:shape item
+ :key (:id item)}])])])))
diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs
index bf627751b8..b88e72722c 100644
--- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs
+++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs
@@ -181,7 +181,7 @@
[:& cap-markers {:shape shape
:render-id render-id}])))
-;; Outer alingmnent: display the shape in two layers. One
+;; Outer alignment: display the shape in two layers. One
;; without stroke (only fill), and another one only with stroke
;; at double width (transparent fill) and passed through a mask
;; that shows the whole shape, but hides the original shape
diff --git a/frontend/src/app/main/ui/shapes/embed.cljs b/frontend/src/app/main/ui/shapes/embed.cljs
index 1f68376cb8..0c4fbd2599 100644
--- a/frontend/src/app/main/ui/shapes/embed.cljs
+++ b/frontend/src/app/main/ui/shapes/embed.cljs
@@ -25,7 +25,12 @@
(let [;; When not active the embedding we return the URI
url-mapping (fn [obs]
(if embed?
- (rx/merge-map http/fetch-data-uri obs)
+ (->> obs
+ (rx/merge-map
+ (fn [uri]
+ (->> (http/fetch-data-uri uri true)
+ ;; If fetching give an error we store the URI as its `data-uri`
+ (rx/catch #(rx/of (hash-map uri uri)))))))
(rx/map identity obs)))
sub (->> (rx/from urls)
@@ -38,6 +43,6 @@
#(when sub
(rx/dispose! sub)))))
- ;; Use ref so if the urls are cached will return inmediately instead of the
+ ;; Use ref so if the urls are cached will return immediately instead of the
;; next render
(mf/ref-val uri-data)))
diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs
index 45f4b48bd7..1ea58e5ed5 100644
--- a/frontend/src/app/main/ui/shapes/export.cljs
+++ b/frontend/src/app/main/ui/shapes/export.cljs
@@ -5,14 +5,17 @@
;; Copyright (c) UXBOX Labs SL
(ns app.main.ui.shapes.export
- (:require
- [app.common.data :as d]
- [app.common.geom.shapes :as gsh]
- [app.util.json :as json]
- [app.util.object :as obj]
- [app.util.svg :as usvg]
- [cuerdas.core :as str]
- [rumext.alpha :as mf]))
+ "Components that generates penpot specific svg nodes with
+ exportation data. This xml nodes serves mainly to enable
+ importation."
+ (:require
+ [app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
+ [app.util.json :as json]
+ [app.util.object :as obj]
+ [app.util.svg :as usvg]
+ [cuerdas.core :as str]
+ [rumext.alpha :as mf]))
(def include-metadata-ctx (mf/create-context false))
@@ -73,6 +76,7 @@
(add! :type)
(add! :stroke-style)
(add! :stroke-alignment)
+ (add! :hide-fill-on-export)
(add! :transform)
(add! :transform-inverse)
(add! :flip-x)
@@ -128,21 +132,20 @@
[(str "penpot:" (d/name k)) v])]
(into {} (map prefix-entry) m)))
-
-(mf/defc export-grid-data
- [{:keys [grids]}]
- [:> "penpot:grids" #js {}
- (for [{:keys [type display params]} grids]
- (let [props (->> (d/without-keys params [:color])
- (prefix-keys)
- (clj->js))]
- [:> "penpot:grid"
- (-> props
- (obj/set! "penpot:color" (get-in params [:color :color]))
- (obj/set! "penpot:opacity" (get-in params [:color :opacity]))
- (obj/set! "penpot:type" (d/name type))
- (cond-> (some? display)
- (obj/set! "penpot:display" (str display))))]))])
+(defn- export-grid-data [{:keys [grids]}]
+ (mf/html
+ [:> "penpot:grids" #js {}
+ (for [{:keys [type display params]} grids]
+ (let [props (->> (dissoc params :color)
+ (prefix-keys)
+ (clj->js))]
+ [:> "penpot:grid"
+ (-> props
+ (obj/set! "penpot:color" (get-in params [:color :color]))
+ (obj/set! "penpot:opacity" (get-in params [:color :opacity]))
+ (obj/set! "penpot:type" (d/name type))
+ (cond-> (some? display)
+ (obj/set! "penpot:display" (str display))))]))]))
(mf/defc export-flows
[{:keys [flows]}]
@@ -167,92 +170,117 @@
(when (seq flows)
[:& export-flows {:flows flows}])]))))
-(mf/defc export-shadow-data
- [{:keys [shadow]}]
- (for [{:keys [style hidden color offset-x offset-y blur spread]} shadow]
- [:> "penpot:shadow"
- #js {:penpot:shadow-type (d/name style)
- :penpot:hidden (str hidden)
- :penpot:color (str (:color color))
- :penpot:opacity (str (:opacity color))
- :penpot:offset-x (str offset-x)
- :penpot:offset-y (str offset-y)
- :penpot:blur (str blur)
- :penpot:spread (str spread)}]))
+(defn- export-shadow-data [{:keys [shadow]}]
+ (mf/html
+ (for [{:keys [style hidden color offset-x offset-y blur spread]} shadow]
+ [:> "penpot:shadow"
+ #js {:penpot:shadow-type (d/name style)
+ :penpot:hidden (str hidden)
+ :penpot:color (str (:color color))
+ :penpot:opacity (str (:opacity color))
+ :penpot:offset-x (str offset-x)
+ :penpot:offset-y (str offset-y)
+ :penpot:blur (str blur)
+ :penpot:spread (str spread)}])))
-(mf/defc export-blur-data [{:keys [blur]}]
- (when (some? blur)
- (let [{:keys [type hidden value]} blur]
- [:> "penpot:blur"
- #js {:penpot:blur-type (d/name type)
- :penpot:hidden (str hidden)
- :penpot:value (str value)}])))
+(defn- export-blur-data [{:keys [blur]}]
+ (when-let [{:keys [type hidden value]} blur]
+ (mf/html
+ [:> "penpot:blur"
+ #js {:penpot:blur-type (d/name type)
+ :penpot:hidden (str hidden)
+ :penpot:value (str value)}])))
-(mf/defc export-exports-data [{:keys [exports]}]
- (for [{:keys [scale suffix type]} exports]
- [:> "penpot:export"
- #js {:penpot:type (d/name type)
- :penpot:suffix suffix
- :penpot:scale (str scale)}]))
+(defn export-exports-data [{:keys [exports]}]
+ (mf/html
+ (for [{:keys [scale suffix type]} exports]
+ [:> "penpot:export"
+ #js {:penpot:type (d/name type)
+ :penpot:suffix suffix
+ :penpot:scale (str scale)}])))
-(mf/defc export-svg-data [shape]
- [:*
- (when (contains? shape :svg-attrs)
- (let [svg-transform (get shape :svg-transform)
- svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") )
- svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))]
- [:> "penpot:svg-import"
- #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs)
- :penpot:svg-defs (when-not (empty? svg-defs) svg-defs)
- :penpot:svg-transform (when svg-transform (str svg-transform))
- :penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x])
- :penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y])
- :penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width])
- :penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])}
- (for [[def-id def-xml] (:svg-defs shape)]
- [:> "penpot:svg-def" #js {:def-id def-id}
- [:& render-xml {:xml def-xml}]])]))
+(defn str->style
+ [style-str]
+ (if (string? style-str)
+ (->> (str/split style-str ";")
+ (map str/trim)
+ (map #(str/split % ":"))
+ (group-by first)
+ (map (fn [[key val]]
+ (vector (keyword key) (second (first val)))))
+ (into {}))
+ style-str))
- (when (= (:type shape) :svg-raw)
- (let [props
- (-> (obj/new)
- (obj/set! "penpot:x" (:x shape))
- (obj/set! "penpot:y" (:y shape))
- (obj/set! "penpot:width" (:width shape))
- (obj/set! "penpot:height" (:height shape))
- (obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name))
- (obj/merge! (-> (get-in shape [:content :attrs])
- (clj->js))))]
- [:> "penpot:svg-content" props
- (for [leaf (->> shape :content :content (filter string?))]
- [:> "penpot:svg-child" {} leaf])]))])
+(defn style->str
+ [style]
+ (->> style
+ (map (fn [[key val]] (str (d/name key) ":" val)))
+ (str/join "; ")))
-(mf/defc export-interactions-data
- [{:keys [interactions]}]
- (when-not (empty? interactions)
- [:> "penpot:interactions" #js {}
- (for [interaction interactions]
- [:> "penpot:interaction"
- #js {:penpot:event-type (d/name (:event-type interaction))
- :penpot:action-type (d/name (:action-type interaction))
- :penpot:delay ((d/nilf str) (:delay interaction))
- :penpot:destination ((d/nilf str) (:destination interaction))
- :penpot:overlay-pos-type ((d/nilf d/name) (:overlay-pos-type interaction))
- :penpot:overlay-position-x ((d/nilf get-in) interaction [:overlay-position :x])
- :penpot:overlay-position-y ((d/nilf get-in) interaction [:overlay-position :y])
- :penpot:url (:url interaction)
- :penpot:close-click-outside ((d/nilf str) (:close-click-outside interaction))
- :penpot:background-overlay ((d/nilf str) (:background-overlay interaction))
- :penpot:preserve-scroll ((d/nilf str) (:preserve-scroll interaction))}])]))
+(defn- export-svg-data [shape]
+ (mf/html
+ [:*
+ (when (contains? shape :svg-attrs)
+ (let [svg-transform (get shape :svg-transform)
+ svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") )
+ svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))]
+ [:> "penpot:svg-import"
+ #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs)
+ ;; Style and filter are special properties so we need to save it otherwise will be indistingishible from
+ ;; standard properties
+ :penpot:svg-style (when (contains? (:svg-attrs shape) :style) (style->str (get-in shape [:svg-attrs :style])))
+ :penpot:svg-filter (when (contains? (:svg-attrs shape) :filter) (get-in shape [:svg-attrs :filter]))
+ :penpot:svg-defs (when-not (empty? svg-defs) svg-defs)
+ :penpot:svg-transform (when svg-transform (str svg-transform))
+ :penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x])
+ :penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y])
+ :penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width])
+ :penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])}
+ (for [[def-id def-xml] (:svg-defs shape)]
+ [:> "penpot:svg-def" #js {:def-id def-id}
+ [:& render-xml {:xml def-xml}]])]))
+
+ (when (= (:type shape) :svg-raw)
+ (let [shape (-> shape (d/update-in-when [:content :attrs :style] str->style))
+ props
+ (-> (obj/new)
+ (obj/set! "penpot:x" (:x shape))
+ (obj/set! "penpot:y" (:y shape))
+ (obj/set! "penpot:width" (:width shape))
+ (obj/set! "penpot:height" (:height shape))
+ (obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name))
+ (obj/merge! (-> (get-in shape [:content :attrs])
+ (clj->js))))]
+ [:> "penpot:svg-content" props
+ (for [leaf (->> shape :content :content (filter string?))]
+ [:> "penpot:svg-child" {} leaf])]))]))
+
+(defn- export-interactions-data [{:keys [interactions]}]
+ (when-let [interactions (seq interactions)]
+ (mf/html
+ [:> "penpot:interactions" #js {}
+ (for [interaction interactions]
+ [:> "penpot:interaction"
+ #js {:penpot:event-type (d/name (:event-type interaction))
+ :penpot:action-type (d/name (:action-type interaction))
+ :penpot:delay ((d/nilf str) (:delay interaction))
+ :penpot:destination ((d/nilf str) (:destination interaction))
+ :penpot:overlay-pos-type ((d/nilf d/name) (:overlay-pos-type interaction))
+ :penpot:overlay-position-x ((d/nilf get-in) interaction [:overlay-position :x])
+ :penpot:overlay-position-y ((d/nilf get-in) interaction [:overlay-position :y])
+ :penpot:url (:url interaction)
+ :penpot:close-click-outside ((d/nilf str) (:close-click-outside interaction))
+ :penpot:background-overlay ((d/nilf str) (:background-overlay interaction))
+ :penpot:preserve-scroll ((d/nilf str) (:preserve-scroll interaction))}])])))
(mf/defc export-data
[{:keys [shape]}]
(let [props (-> (obj/new) (add-data shape) (add-library-refs shape))]
[:> "penpot:shape" props
- [:& export-shadow-data shape]
- [:& export-blur-data shape]
- [:& export-exports-data shape]
- [:& export-svg-data shape]
- [:& export-interactions-data shape]
- [:& export-grid-data shape]]))
+ (export-shadow-data shape)
+ (export-blur-data shape)
+ (export-exports-data shape)
+ (export-svg-data shape)
+ (export-interactions-data shape)
+ (export-grid-data shape)]))
diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs
index 1fb272c69b..1bf804cce1 100644
--- a/frontend/src/app/main/ui/shapes/filters.cljs
+++ b/frontend/src/app/main/ui/shapes/filters.cljs
@@ -113,7 +113,7 @@
filter-x (min x (+ x offset-x (- spread) (- blur) -5))
filter-y (min y (+ y offset-y (- spread) (- blur) -5))
filter-width (+ width (mth/abs offset-x) (* spread 2) (* blur 2) 10)
- filter-height (+ height (mth/abs offset-x) (* spread 2) (* blur 2) 10)]
+ filter-height (+ height (mth/abs offset-y) (* spread 2) (* blur 2) 10)]
{:x1 filter-x
:y1 filter-y
:x2 (+ filter-x filter-width)
@@ -208,26 +208,31 @@
margin (gsh/shape-stroke-margin shape stroke-width)]
(+ stroke-width margin)))
+(defn change-filter-in
+ "Adds the previous filter as `filter-in` parameter"
+ [filters]
+ (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters))))
+
(mf/defc filters
[{:keys [filter-id shape]}]
- (let [filters (shape->filters shape)
-
- ;; Adds the previous filter as `filter-in` parameter
- filters (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters)))
- bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
- padding (calculate-padding shape)]
-
+ (let [filters (-> shape shape->filters change-filter-in)
+ bounds (get-filters-bounds shape filters (or (-> shape :blur :value) 0))
+ padding (calculate-padding shape)
+ selrect (:selrect shape)
+ filter-x (/ (- (:x bounds) (:x selrect) padding) (:width selrect))
+ filter-y (/ (- (:y bounds) (:y selrect) padding) (:height selrect))
+ filter-width (/ (+ (:width bounds) (* 2 padding)) (:width selrect))
+ filter-height (/ (+ (:height bounds) (* 2 padding)) (:height selrect))]
[:*
(when (> (count filters) 2)
- [:filter {:id filter-id
- :x (- (:x bounds) padding)
- :y (- (:y bounds) padding)
- :width (+ (:width bounds) (* 2 padding))
- :height (+ (:height bounds) (* 2 padding))
- :filterUnits "userSpaceOnUse"
+ [:filter {:id filter-id
+ :x filter-x
+ :y filter-y
+ :width filter-width
+ :height filter-height
+ :filterUnits "objectBoundingBox"
:color-interpolation-filters "sRGB"}
-
(for [entry filters]
[:& filter-entry {:entry entry}])])]))
diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs
index d9e95c1351..cdb4aea31c 100644
--- a/frontend/src/app/main/ui/shapes/frame.cljs
+++ b/frontend/src/app/main/ui/shapes/frame.cljs
@@ -8,8 +8,40 @@
(:require
[app.main.ui.shapes.attrs :as attrs]
[app.util.object :as obj]
+ [debug :refer [debug?]]
[rumext.alpha :as mf]))
+(defn frame-clip-id
+ [shape render-id]
+ (str "frame-clip-" (:id shape) "-" render-id))
+
+(defn frame-clip-url
+ [shape render-id]
+ (when (= :frame (:type shape))
+ (str "url(#" (frame-clip-id shape render-id) ")")))
+
+(mf/defc frame-clip-def
+ [{:keys [shape render-id]}]
+ (when (= :frame (:type shape))
+ (let [{:keys [x y width height]} shape]
+ [:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"}
+ [:rect {:x x :y y :width width :height height}]])))
+
+(mf/defc frame-thumbnail
+ {::mf/wrap-props false}
+ [props]
+ (let [shape (obj/get props "shape")]
+ (when (:thumbnail shape)
+ [:image.frame-thumbnail
+ {:id (str "thumbnail-" (:id shape))
+ :xlinkHref (:thumbnail shape)
+ :x (:x shape)
+ :y (:y shape)
+ :width (:width shape)
+ :height (:height shape)
+ ;; DEBUG
+ :style {:filter (when (debug? :thumbnails) "sepia(1)")}}])))
+
(defn frame-shape
[shape-wrapper]
(mf/fnc frame-shape
@@ -17,7 +49,7 @@
[props]
(let [childs (unchecked-get props "childs")
shape (unchecked-get props "shape")
- {:keys [width height]} shape
+ {:keys [x y width height]} shape
has-background? (or (some? (:fill-color shape))
(some? (:fill-color-gradient shape)))
@@ -25,8 +57,8 @@
props (-> (attrs/extract-style-attrs shape)
(obj/merge!
- #js {:x 0
- :y 0
+ #js {:x x
+ :y y
:width width
:height height
:className "frame-background"}))]
@@ -34,7 +66,6 @@
(when (or has-background? has-stroke?)
[:> :rect props])
(for [item childs]
- [:& shape-wrapper {:frame shape
- :shape item
+ [:& shape-wrapper {:shape item
:key (:id item)}])])))
diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs
index 49a6283508..69bc4ee583 100644
--- a/frontend/src/app/main/ui/shapes/group.cljs
+++ b/frontend/src/app/main/ui/shapes/group.cljs
@@ -17,8 +17,7 @@
(mf/fnc group-shape
{::mf/wrap-props false}
[props]
- (let [frame (unchecked-get props "frame")
- shape (unchecked-get props "shape")
+ (let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
render-id (mf/use-ctx muc/render-ctx)
masked-group? (:masked-group? shape)
@@ -46,11 +45,10 @@
[:> clip-wrapper clip-props
[:> mask-wrapper mask-props
(when masked-group?
- [:> render-mask #js {:frame frame :mask mask}])
+ [:> render-mask #js {:mask mask}])
(for [item childs]
- [:& shape-wrapper {:frame frame
- :shape item
+ [:& shape-wrapper {:shape item
:key (:id item)}])]]))))
diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs
index 87fa6790b1..30a5ed70f6 100644
--- a/frontend/src/app/main/ui/shapes/mask.cljs
+++ b/frontend/src/app/main/ui/shapes/mask.cljs
@@ -34,13 +34,9 @@
(mf/fnc mask-shape
{::mf/wrap-props false}
[props]
- (let [frame (unchecked-get props "frame")
- mask (unchecked-get props "mask")
+ (let [mask (unchecked-get props "mask")
render-id (mf/use-ctx muc/render-ctx)
-
- mask' (-> mask
- (gsh/transform-shape)
- (gsh/translate-to-frame frame))]
+ mask' (gsh/transform-shape mask)]
[:defs
[:filter {:id (filter-id render-id mask)}
[:feFlood {:flood-color "white"
@@ -49,16 +45,16 @@
:in2 "SourceGraphic"
:operator "in"
:result "comp"}]]
- ;; Clip path is necesary so the elements inside the mask won't affect
+ ;; Clip path is necessary so the elements inside the mask won't affect
;; the events outside. Clip hides the elements but mask doesn't (like display vs visibility)
;; we cannot use clips instead of mask because clips can only be simple shapes
- [:clipPath {:id (clip-id render-id mask)}
+ [:clipPath {:class "mask-clip-path"
+ :id (clip-id render-id mask)}
[:polyline {:points (->> (:points mask')
(map #(str (:x %) "," (:y %)))
(str/join " "))}]]
- [:mask {:id (mask-id render-id mask)}
+ [:mask {:class "mask-shape"
+ :id (mask-id render-id mask)}
[:g {:filter (filter-url render-id mask)}
- [:& shape-wrapper {:frame frame
- :shape (-> mask
- (dissoc :shadow :blur))}]]]])))
+ [:& shape-wrapper {:shape (dissoc mask :shadow :blur)}]]]])))
diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs
index e62fb46e86..36155fc7e8 100644
--- a/frontend/src/app/main/ui/shapes/path.cljs
+++ b/frontend/src/app/main/ui/shapes/path.cljs
@@ -20,21 +20,18 @@
[props]
(let [shape (unchecked-get props "shape")
content (:content shape)
- pdata (mf/use-memo
- (mf/deps content)
- (fn []
- (try
- (upf/format-path content)
- (catch :default e
- (log/error :hint "unexpected error on formating path"
- :shape-name (:name shape)
- :shape-id (:id shape)
- :cause e)
- ""))))
+ pdata (mf/with-memo [content]
+ (try
+ (upf/format-path content)
+ (catch :default e
+ (log/error :hint "unexpected error on formating path"
+ :shape-name (:name shape)
+ :shape-id (:id shape)
+ :cause e)
+ "")))
props (-> (attrs/extract-style-attrs shape)
- (obj/merge!
- #js {:d pdata}))]
+ (obj/set! "d" pdata))]
[:& shape-custom-stroke {:shape shape}
[:> :path props]]))
diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs
index 5b68b7ee91..cfb73aad9d 100644
--- a/frontend/src/app/main/ui/shapes/shape.cljs
+++ b/frontend/src/app/main/ui/shapes/shape.cljs
@@ -14,6 +14,7 @@
[app.main.ui.shapes.export :as ed]
[app.main.ui.shapes.fill-image :as fim]
[app.main.ui.shapes.filters :as filters]
+ [app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.gradients :as grad]
[app.main.ui.shapes.svg-defs :as defs]
[app.util.object :as obj]
@@ -26,6 +27,8 @@
(let [shape (obj/get props "shape")
children (obj/get props "children")
pointer-events (obj/get props "pointer-events")
+
+ type (:type shape)
render-id (mf/use-memo #(str (uuid/next)))
filter-id (str "filter_" render-id)
styles (-> (obj/new)
@@ -34,10 +37,6 @@
(cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal))
(obj/set! "mixBlendMode" (d/name (:blend-mode shape)))))
- {:keys [x y width height type]} shape
- frame? (= :frame type)
- group? (= :group type)
-
include-metadata? (mf/use-ctx ed/include-metadata-ctx)
wrapper-props
@@ -50,26 +49,14 @@
wrapper-props
(cond-> wrapper-props
- frame?
- (-> (obj/set! "x" x)
- (obj/set! "y" y)
- (obj/set! "width" width)
- (obj/set! "height" height)
- (obj/set! "xmlns" "http://www.w3.org/2000/svg")
- (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink")
- (cond->
- include-metadata?
- (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))
+ (= :frame type)
+ (obj/set! "clipPath" (frame/frame-clip-url shape render-id))
- wrapper-props
- (cond-> wrapper-props
- group?
- (attrs/add-style-attrs shape))
-
- wrapper-tag (if frame? "svg" "g")]
+ (= :group type)
+ (attrs/add-style-attrs shape render-id))]
[:& (mf/provider muc/render-ctx) {:value render-id}
- [:> wrapper-tag wrapper-props
+ [:> :g wrapper-props
(when include-metadata?
[:& ed/export-data {:shape shape}])
@@ -79,5 +66,6 @@
[:& grad/gradient {:shape shape :attr :fill-color-gradient}]
[:& grad/gradient {:shape shape :attr :stroke-color-gradient}]
[:& fim/fill-image-pattern {:shape shape :render-id render-id}]
- [:& cs/stroke-defs {:shape shape :render-id render-id}]]
+ [:& cs/stroke-defs {:shape shape :render-id render-id}]
+ [:& frame/frame-clip-def {:shape shape :render-id render-id}]]
children]]))
diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs
index 407f5096d0..42b797be8b 100644
--- a/frontend/src/app/main/ui/shapes/svg_raw.cljs
+++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs
@@ -88,8 +88,7 @@
{::mf/wrap-props false}
[props]
- (let [frame (unchecked-get props "frame")
- shape (unchecked-get props "shape")
+ (let [shape (unchecked-get props "shape")
childs (unchecked-get props "childs")
{:keys [content]} shape
@@ -103,12 +102,12 @@
svg-root?
[:& svg-root {:shape shape}
(for [item childs]
- [:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
+ [:& shape-wrapper {:shape item :key (:id item)}])]
svg-tag?
[:& svg-element {:shape shape}
(for [item childs]
- [:& shape-wrapper {:frame frame :shape item :key (:id item)}])]
+ [:& shape-wrapper {:shape item :key (:id item)}])]
svg-leaf?
content
diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs
index 88e48e13b7..77ddea023b 100644
--- a/frontend/src/app/main/ui/shapes/text.cljs
+++ b/frontend/src/app/main/ui/shapes/text.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.shapes.text
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.common.geom.shapes :as geom]
[app.main.ui.context :as muc]
@@ -135,7 +136,7 @@
(filter some?))
colors (->> color-data
- (into #{"#000000"}
+ (into #{clr/black}
(comp (filter #(= :solid (:type %)))
(map :hex))))
@@ -208,7 +209,7 @@
:height (if (#{:auto-height :auto-width} grow-type) 100000 height)
:style (-> (obj/new) (attrs/add-layer-props shape))
:ref ref}
- ;; We use a class here because react has a bug that won't use the appropiate selector for
+ ;; We use a class here because react has a bug that won't use the appropriate selector for
;; `background-clip`
[:style ".text-node { background-clip: text;
-webkit-background-clip: text;" ]
diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs
index 263424b68a..fe7efceefb 100644
--- a/frontend/src/app/main/ui/shapes/text/styles.cljs
+++ b/frontend/src/app/main/ui/shapes/text/styles.cljs
@@ -14,11 +14,10 @@
[cuerdas.core :as str]))
(defn generate-root-styles
- [shape node]
+ [_shape node]
(let [valign (:vertical-align node "top")
- width (some-> (:width shape) (+ 1))
- base #js {:height (or (:height shape) "100%")
- :width (or width "100%")
+ base #js {:height "100%"
+ :width "100%"
:fontFamily "sourcesanspro"}]
(cond-> base
(= valign "top") (obj/set! "justifyContent" "flex-start")
diff --git a/frontend/src/app/main/ui/share_link.cljs b/frontend/src/app/main/ui/share_link.cljs
index 1bb520416a..52438b3de7 100644
--- a/frontend/src/app/main/ui/share_link.cljs
+++ b/frontend/src/app/main/ui/share_link.cljs
@@ -155,7 +155,7 @@
;; [:div.input-checkbox.check-primary
;; [:input.check-primary.input-checkbox {:type "checkbox"}]
- ;; [:label "Handsoff" ]]
+ ;; [:label "Handoff" ]]
]])
(let [mode (:pages-mode @opts)]
diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs
index 18d2485be1..432f4ec050 100644
--- a/frontend/src/app/main/ui/viewer.cljs
+++ b/frontend/src/app/main/ui/viewer.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.viewer
(:require
+ [app.common.data :as d]
[app.common.exceptions :as ex]
[app.common.geom.point :as gpt]
[app.main.data.comments :as dcm]
@@ -25,15 +26,29 @@
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
+ [app.util.webapi :as wapi]
[goog.events :as events]
[rumext.alpha :as mf]))
(defn- calculate-size
[frame zoom]
(let [{:keys [_ _ width height]} (filters/get-filters-bounds frame)]
- {:width (* width zoom)
- :height (* height zoom)
- :vbox (str "0 0 " width " " height)}))
+ {:base-width width
+ :base-height height
+ :width (* width zoom)
+ :height (* height zoom)
+ :vbox (str "0 0 " width " " height)}))
+
+(defn- calculate-wrapper
+ [size1 size2 zoom]
+ (cond
+ (nil? size1) size2
+ (nil? size2) size1
+ :else (let [width (max (:base-width size1) (:base-width size2))
+ height (max (:base-height size1) (:base-height size2))]
+ {:width (* width zoom)
+ :height (* height zoom)
+ :vbox (str "0 0 " width " " height)})))
(mf/defc viewer
[{:keys [params data]}]
@@ -41,24 +56,41 @@
(let [{:keys [page-id section index]} params
{:keys [file users project permissions]} data
- local (mf/deref refs/viewer-local)
+ local (mf/deref refs/viewer-local)
nav-scroll (:nav-scroll local)
+ orig-viewport-ref (mf/use-ref nil)
+ current-viewport-ref (mf/use-ref nil)
+ current-animation (:current-animation local)
page-id (or page-id (-> file :data :pages first))
- page (mf/use-memo
- (mf/deps data page-id)
- (fn []
- (get-in data [:pages page-id])))
+ page (mf/use-memo
+ (mf/deps data page-id)
+ (fn []
+ (get-in data [:pages page-id])))
- zoom (:zoom local)
- frames (:frames page)
- frame (get frames index)
+ zoom (:zoom local)
+ frames (:frames page)
+ frame (get frames index)
+ fullscreen? (mf/deref refs/fullscreen?)
+ overlays (:overlays local)
- size (mf/use-memo
- (mf/deps frame zoom)
- (fn [] (calculate-size frame zoom)))
+ orig-frame
+ (when (:orig-frame-id current-animation)
+ (d/seek #(= (:id %) (:orig-frame-id current-animation)) frames))
+
+ size (mf/use-memo
+ (mf/deps frame zoom)
+ (fn [] (calculate-size frame zoom)))
+
+ orig-size (mf/use-memo
+ (mf/deps orig-frame zoom)
+ (fn [] (when orig-frame (calculate-size orig-frame zoom))))
+
+ wrapper-size (mf/use-memo
+ (mf/deps size orig-size zoom)
+ (fn [] (calculate-wrapper size orig-size zoom)))
interactions-mode
(:interactions-mode local)
@@ -72,8 +104,15 @@
close-overlay
(mf/use-callback
- (fn [frame]
- (st/emit! (dv/close-overlay (:id frame)))))]
+ (fn [frame]
+ (st/emit! (dv/close-overlay (:id frame)))))
+
+ set-up-new-size
+ (mf/use-callback
+ (fn [_]
+ (let [viewer-section (dom/get-element "viewer-section")
+ size (dom/get-client-size viewer-section)]
+ (st/emit! (dv/set-viewport-size {:size size})))))]
(hooks/use-shortcuts ::viewer sc/shortcuts)
@@ -94,17 +133,92 @@
(events/unlistenByKey key1)))))
(mf/use-layout-effect
- (mf/deps nav-scroll)
- (fn []
- (when (number? nav-scroll)
- (let [viewer-section (dom/get-element "viewer-section")]
- (st/emit! (dv/reset-nav-scroll))
- (dom/set-scroll-pos! viewer-section nav-scroll)))))
+ (fn []
+ (set-up-new-size)
+ (.addEventListener js/window "resize" set-up-new-size)
+ (fn []
+ (.removeEventListener js/window "resize" set-up-new-size))))
- [:div {:class (dom/classnames
- :force-visible (:show-thumbnails local)
- :viewer-layout (not= section :handoff)
- :handoff-layout (= section :handoff))}
+ (mf/use-layout-effect
+ (mf/deps nav-scroll)
+ (fn []
+ ;; Set scroll position after navigate
+ (when (number? nav-scroll)
+ (let [viewer-section (dom/get-element "viewer-section")]
+ (st/emit! (dv/reset-nav-scroll))
+ (dom/set-scroll-pos! viewer-section nav-scroll)))))
+
+ (mf/use-layout-effect
+ (mf/deps fullscreen?)
+ (fn []
+ ;; Trigger dom fullscreen depending on our state
+ (let [wrapper (dom/get-element "viewer-layout")
+ fullscreen-dom? (dom/fullscreen?)]
+ (when (not= fullscreen? fullscreen-dom?)
+ (if fullscreen?
+ (wapi/request-fullscreen wrapper)
+ (wapi/exit-fullscreen))))))
+
+ (mf/use-layout-effect
+ (mf/deps index)
+ (fn []
+ ;; Navigate animation needs to be started after navigation
+ ;; is complete, and we have the next page index.
+ (when (and current-animation
+ (= (:kind current-animation) :go-to-frame))
+ (let [orig-viewport (mf/ref-val orig-viewport-ref)
+ current-viewport (mf/ref-val current-viewport-ref)]
+ (interactions/animate-go-to-frame
+ (:animation current-animation)
+ current-viewport
+ orig-viewport
+ size
+ orig-size
+ wrapper-size)))))
+
+ (mf/use-layout-effect
+ (mf/deps current-animation)
+ (fn []
+ ;; Overlay animations may be started when needed.
+ (when current-animation
+ (case (:kind current-animation)
+
+ :open-overlay
+ (let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
+ overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
+ overlays)
+ overlay-size (calculate-size (:frame overlay) zoom)
+ overlay-position {:x (* (:x (:position overlay)) zoom)
+ :y (* (:y (:position overlay)) zoom)}]
+ (interactions/animate-open-overlay
+ (:animation current-animation)
+ overlay-viewport
+ wrapper-size
+ overlay-size
+ overlay-position))
+
+ :close-overlay
+ (let [overlay-viewport (dom/get-element (str "overlay-" (str (:overlay-id current-animation))))
+ overlay (d/seek #(= (:id (:frame %)) (:overlay-id current-animation))
+ overlays)
+ overlay-size (calculate-size (:frame overlay) zoom)
+ overlay-position {:x (* (:x (:position overlay)) zoom)
+ :y (* (:y (:position overlay)) zoom)}]
+ (interactions/animate-close-overlay
+ (:animation current-animation)
+ overlay-viewport
+ wrapper-size
+ overlay-size
+ overlay-position
+ (:id (:frame overlay))))
+
+ nil))))
+
+ [:div#viewer-layout {:class (dom/classnames
+ :force-visible (:show-thumbnails local)
+ :viewer-layout (not= section :handoff)
+ :handoff-layout (= section :handoff)
+ :fullscreen fullscreen?)}
[:& header {:project project
:index index
@@ -120,7 +234,8 @@
:show? (:show-thumbnails local false)
:page page
:index index}]
- [:section.viewer-section {:id "viewer-section"}
+ [:section.viewer-section {:id "viewer-section"
+ :class (if fullscreen? "fullscreen" "")}
(cond
(empty? frames)
[:section.empty-state
@@ -128,7 +243,8 @@
(nil? frame)
[:section.empty-state
- [:span (tr "viewer.frame-not-found")]]
+ (when (some? index)
+ [:span (tr "viewer.frame-not-found")])]
(some? frame)
(if (= :handoff section)
@@ -139,57 +255,83 @@
:section section
:local local}]
- [:div.viewport-container
- {:style {:width (:width size)
- :height (:height size)
- :position "relative"}}
+ [:*
+ [:div.viewer-wrapper
+ {:style {:width (:width wrapper-size)
+ :height (:height wrapper-size)}}
- (when (= section :comments)
- [:& comments-layer {:file file
- :users users
- :frame frame
- :page page
- :zoom zoom}])
+ [:div.viewer-clipper
+ [:*
+ (when orig-frame
+ [:div.viewport-container
+ {:ref orig-viewport-ref
+ :style {:width (:width orig-size)
+ :height (:height orig-size)
+ :position "relative"}}
- [:& interactions/viewport
- {:frame frame
- :base-frame frame
- :frame-offset (gpt/point 0 0)
- :size size
- :page page
- :file file
- :users users
- :interactions-mode interactions-mode}]
-
- (for [overlay (:overlays local)]
- (let [size-over (calculate-size (:frame overlay) zoom)]
- [:*
- (when (or (:close-click-outside overlay)
- (:background-overlay overlay))
- [:div.viewer-overlay-background
- {:class (dom/classnames
- :visible (:background-overlay overlay))
- :style {:width (:width frame)
- :height (:height frame)
- :position "absolute"
- :left 0
- :top 0}
- :on-click #(when (:close-click-outside overlay)
- (close-overlay (:frame overlay)))}])
- [:div.viewport-container.viewer-overlay
- {:style {:width (:width size-over)
- :height (:height size-over)
- :left (* (:x (:position overlay)) zoom)
- :top (* (:y (:position overlay)) zoom)}}
[:& interactions/viewport
- {:frame (:frame overlay)
- :base-frame frame
- :frame-offset (:position overlay)
- :size size-over
+ {:frame orig-frame
+ :base-frame orig-frame
+ :frame-offset (gpt/point 0 0)
+ :size orig-size
:page page
:file file
:users users
- :interactions-mode interactions-mode}]]]))]))]]]))
+ :interactions-mode :hide}]])
+
+ [:div.viewport-container
+ {:ref current-viewport-ref
+ :style {:width (:width size)
+ :height (:height size)
+ :position "relative"}}
+
+ [:& interactions/viewport
+ {:frame frame
+ :base-frame frame
+ :frame-offset (gpt/point 0 0)
+ :size size
+ :page page
+ :file file
+ :users users
+ :interactions-mode interactions-mode}]
+
+ (for [overlay overlays]
+ (let [size-over (calculate-size (:frame overlay) zoom)]
+ [:*
+ (when (or (:close-click-outside overlay)
+ (:background-overlay overlay))
+ [:div.viewer-overlay-background
+ {:class (dom/classnames
+ :visible (:background-overlay overlay))
+ :style {:width (:width wrapper-size)
+ :height (:height wrapper-size)
+ :position "absolute"
+ :left 0
+ :top 0}
+ :on-click #(when (:close-click-outside overlay)
+ (close-overlay (:frame overlay)))}])
+ [:div.viewport-container.viewer-overlay
+ {:id (str "overlay-" (str (:id (:frame overlay))))
+ :style {:width (:width size-over)
+ :height (:height size-over)
+ :left (* (:x (:position overlay)) zoom)
+ :top (* (:y (:position overlay)) zoom)}}
+ [:& interactions/viewport
+ {:frame (:frame overlay)
+ :base-frame frame
+ :frame-offset (:position overlay)
+ :size size-over
+ :page page
+ :file file
+ :users users
+ :interactions-mode interactions-mode}]]]))]]
+
+ (when (= section :comments)
+ [:& comments-layer {:file file
+ :users users
+ :frame frame
+ :page page
+ :zoom zoom}])]]]))]]]))
;; --- Component: Viewer Page
diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
index e760b668f4..2cfdda7bd6 100644
--- a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs
@@ -166,8 +166,8 @@
(mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text))))]
(for [[idx [full-style text]] (map-indexed vector style-text-blocks)]
- (let [previus-style (first (nth style-text-blocks (dec idx) nil))
- style (remove-equal-values full-style previus-style)
+ (let [previous-style (first (nth style-text-blocks (dec idx) nil))
+ style (remove-equal-values full-style previous-style)
;; If the color is set we need to add opacity otherwise the display will not work
style (cond-> style
diff --git a/frontend/src/app/main/ui/viewer/handoff/exports.cljs b/frontend/src/app/main/ui/viewer/handoff/exports.cljs
index bbdc65cecd..930281d301 100644
--- a/frontend/src/app/main/ui/viewer/handoff/exports.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/exports.cljs
@@ -7,36 +7,17 @@
(ns app.main.ui.viewer.handoff.exports
(:require
[app.common.data :as d]
- [app.main.data.messages :as dm]
- [app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.menus.exports :as we]
[app.util.dom :as dom]
[app.util.i18n :refer [tr]]
- [beicon.core :as rx]
[rumext.alpha :as mf]))
(mf/defc exports
[{:keys [shape page-id file-id] :as props}]
(let [exports (mf/use-state (:exports shape []))
- loading? (mf/use-state false)
- on-download
- (mf/use-callback
- (mf/deps shape @exports)
- (fn [event]
- (dom/prevent-default event)
- (swap! loading? not)
- (->> (we/request-export (assoc shape :page-id page-id :file-id file-id) @exports)
- (rx/subs
- (fn [{:keys [status body] :as response}]
- (js/console.log status body)
- (if (= status 200)
- (dom/trigger-download (:name shape) body)
- (st/emit! (dm/error (tr "errors.unexpected-error")))))
- (constantly nil)
- (fn []
- (swap! loading? not))))))
+ [on-download loading?] (we/use-download-export shape page-id file-id @exports)
add-export
(mf/use-callback
@@ -118,10 +99,10 @@
i/minus]])
[:div.btn-icon-dark.download-button
- {:on-click (when-not @loading? on-download)
- :class (dom/classnames :btn-disabled @loading?)
- :disabled @loading?}
- (if @loading?
+ {:on-click (when-not loading? on-download)
+ :class (dom/classnames :btn-disabled loading?)
+ :disabled loading?}
+ (if loading?
(tr "workspace.options.exporting-object")
(tr "workspace.options.export-object"))]])]))
diff --git a/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
index 48aa1a5aae..6dd87c2a76 100644
--- a/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/left_sidebar.cljs
@@ -59,7 +59,7 @@
(mf/deps selected)
(fn []
(when (and (= (count selected) 1) selected?)
- (.scrollIntoView (mf/ref-val item-ref) false))))
+ (dom/scroll-into-view-if-needed! (mf/ref-val item-ref) true))))
[:li {:ref item-ref
:class (dom/classnames
diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
index 2169bab30b..0cbf82a799 100644
--- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs
@@ -22,7 +22,16 @@
section (mf/use-state :info #_:code)
shapes (resolve-shapes (:objects page) selected)
- selected-type (or (-> shapes first :type) :not-found)]
+ first-shape (first shapes)
+
+ selected-type (or (:type first-shape) :not-found)
+ selected-type (if (= selected-type :group)
+ (if (some? (:component-id first-shape))
+ :component
+ (if (:masked-group? first-shape)
+ :mask
+ :group))
+ selected-type)]
[:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")}
[:div.settings-bar-inside
@@ -35,7 +44,7 @@
[:span.tool-window-bar-title (tr "handoff.tabs.code.selected.multiple" (count shapes))]]
[:*
[:span.tool-window-bar-icon
- [:& element-icon {:shape (-> shapes first)}]]
+ [:& element-icon {:shape first-shape}]]
[:span.tool-window-bar-title (->> selected-type d/name (str "handoff.tabs.code.selected.") (tr))]])
]
[:div.tool-window-content
diff --git a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
index 51fb68ce4e..a93fbbdeae 100644
--- a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
+++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs
@@ -14,7 +14,7 @@
;; CONSTANTS
;; ------------------------------------------------
-(def select-color "#1FDEA7")
+(def select-color "var(--color-select)")
(def selection-rect-width 1)
(def select-guide-width 1)
(def select-guide-dasharray 5)
@@ -57,8 +57,7 @@
(let [{:keys [hover selected zoom]} local
hover-shape (-> (or (first (resolve-shapes objects [hover])) frame)
(gsh/translate-to-frame frame))
- selected-shapes (->> (resolve-shapes objects selected)
- (map #(gsh/translate-to-frame % frame)))
+ selected-shapes (->> (resolve-shapes objects selected))
selrect (gsh/selection-rect selected-shapes)
bounds (frame->bounds frame)]
diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs
index 13b3d03537..8a3e078a06 100644
--- a/frontend/src/app/main/ui/viewer/header.cljs
+++ b/frontend/src/app/main/ui/viewer/header.cljs
@@ -6,28 +6,65 @@
(ns app.main.ui.viewer.header
(:require
+ [app.common.math :as mth]
[app.main.data.modal :as modal]
[app.main.data.viewer :as dv]
+ [app.main.data.viewer.shortcuts :as sc]
+ [app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
- [app.main.ui.components.fullscreen :as fs]
[app.main.ui.icons :as i]
[app.main.ui.viewer.comments :refer [comments-menu]]
[app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]]
- [app.main.ui.workspace.header :refer [zoom-widget]]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
+(mf/defc zoom-widget
+ {::mf/wrap [mf/memo]}
+ [{:keys [zoom
+ on-increase
+ on-decrease
+ on-zoom-reset
+ on-fullscreen
+ on-zoom-fit
+ on-zoom-fill]
+ :as props}]
+ (let [show-dropdown? (mf/use-state false)]
+ [:div.zoom-widget {:on-click #(reset! show-dropdown? true)}
+ [:span.label {} (str (mth/round (* 100 zoom)) "%")]
+ [:span.icon i/arrow-down]
+ [:& dropdown {:show @show-dropdown?
+ :on-close #(reset! show-dropdown? false)}
+ [:ul.dropdown
+ [:li.basic-zoom-bar
+ [:span.zoom-btns
+ [:button {:on-click (fn [event]
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (on-decrease))} "-"]
+ [:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")]
+ [:button {:on-click (fn [event]
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (on-increase))} "+"]]
+ [:button.reset-btn {:on-click on-zoom-reset} (tr "workspace.header.reset-zoom")]]
+ [:li.separator]
+ [:li {:on-click on-zoom-fit}
+ (tr "workspace.header.zoom-fit") [:span (sc/get-tooltip :toggle-zoom-style)]]
+ [:li {:on-click on-zoom-fill}
+ (tr "workspace.header.zoom-fill") [:span (sc/get-tooltip :toggle-zoom-style)]]
+ [:li {:on-click on-fullscreen}
+ (tr "workspace.header.zoom-full-screen") [:span (sc/get-tooltip :toogle-fullscreen)]]]]]))
+
+
(mf/defc header-options
[{:keys [section zoom page file index permissions]}]
- (let [fullscreen (mf/use-ctx fs/fullscreen-context)
+ (let [fullscreen? (mf/deref refs/fullscreen?)
toggle-fullscreen
(mf/use-callback
- (mf/deps fullscreen)
- (fn []
- (if @fullscreen (fullscreen false) (fullscreen true))))
+ (fn [] (st/emit! dv/toggle-fullscreen)))
go-to-workspace
(mf/use-callback
@@ -55,15 +92,15 @@
{:zoom zoom
:on-increase (st/emitf dv/increase-zoom)
:on-decrease (st/emitf dv/decrease-zoom)
- :on-zoom-to-50 (st/emitf dv/zoom-to-50)
- :on-zoom-to-100 (st/emitf dv/reset-zoom)
- :on-zoom-to-200 (st/emitf dv/zoom-to-200)
+ :on-zoom-reset (st/emitf dv/reset-zoom)
+ :on-zoom-fill (st/emitf dv/zoom-to-fill)
+ :on-zoom-fit (st/emitf dv/zoom-to-fit)
:on-fullscreen toggle-fullscreen}]
[:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left
{:alt (tr "viewer.header.fullscreen")
:on-click toggle-fullscreen}
- (if @fullscreen
+ (if fullscreen?
i/full-screen-off
i/full-screen)]
@@ -103,31 +140,31 @@
(st/emit! (dv/go-to-page page-id))
(reset! show-dropdown? false)))]
- [:div.sitemap-zone {:alt (tr "viewer.header.sitemap")}
- [:div.breadcrumb
- {:on-click open-dropdown}
- [:span.project-name project-name]
- [:span "/"]
- [:span.file-name file-name]
- [:span "/"]
+ [:div.sitemap-zone {:alt (tr "viewer.header.sitemap")}
+ [:div.breadcrumb
+ {:on-click open-dropdown}
+ [:span.project-name project-name]
+ [:span "/"]
+ [:span.file-name file-name]
+ [:span "/"]
- [:span.page-name page-name]
- [:span.icon i/arrow-down]
+ [:span.page-name page-name]
+ [:span.icon i/arrow-down]
- [:& dropdown {:show @show-dropdown?
- :on-close close-dropdown}
- [:ul.dropdown
- (for [id (get-in file [:data :pages])]
- [:li {:id (str id)
- :on-click (partial navigate-to id)}
- (get-in file [:data :pages-index id :name])])]]]
+ [:& dropdown {:show @show-dropdown?
+ :on-close close-dropdown}
+ [:ul.dropdown
+ (for [id (get-in file [:data :pages])]
+ [:li {:id (str id)
+ :on-click (partial navigate-to id)}
+ (get-in file [:data :pages-index id :name])])]]]
- [:div.current-frame
- {:on-click toggle-thumbnails}
- [:span.label "/"]
- [:span.label frame-name]
- [:span.icon i/arrow-down]
- [:span.counters (str (inc index) " / " total)]]]))
+ [:div.current-frame
+ {:on-click toggle-thumbnails}
+ [:span.label "/"]
+ [:span.label frame-name]
+ [:span.icon i/arrow-down]
+ [:span.counters (str (inc index) " / " total)]]]))
(mf/defc header
@@ -140,25 +177,26 @@
(st/emit! (dv/go-to-section section)))]
[:header.viewer-header
- [:div.main-icon
- [:a {:on-click go-to-dashboard
- ;; If the user doesn't have permission we disable the link
- :style {:pointer-events (when-not permissions "none")}} i/logo-icon]]
+ [:div.nav-zone
+ [:div.main-icon
+ [:a {:on-click go-to-dashboard
+ ;; If the user doesn't have permission we disable the link
+ :style {:pointer-events (when-not permissions "none")}} i/logo-icon]]
- [:& header-sitemap {:project project :file file :page page :frame frame :index index}]
+ [:& header-sitemap {:project project :file file :page page :frame frame :index index}]]
[:div.mode-zone
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :interactions)
:class (dom/classnames :active (= section :interactions))
- :alt (tr "viewer.header.interactions-section")}
+ :alt (tr "viewer.header.interactions-section" (sc/get-tooltip :open-interactions))}
i/play]
(when (:can-edit permissions)
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :comments)
:class (dom/classnames :active (= section :comments))
- :alt (tr "viewer.header.comments-section")}
+ :alt (tr "viewer.header.comments-section" (sc/get-tooltip :open-comments))}
i/chat])
(when (or (= (:type permissions) :membership)
@@ -167,7 +205,7 @@
[:button.mode-zone-button.tooltip.tooltip-bottom
{:on-click #(navigate :handoff)
:class (dom/classnames :active (= section :handoff))
- :alt (tr "viewer.header.handsoff-section")}
+ :alt (tr "viewer.header.handoff-section" (sc/get-tooltip :open-handoff))}
i/code])]
[:& header-options {:section section
@@ -176,4 +214,3 @@
:file file
:index index
:zoom zoom}]]))
-
diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs
index f0534aa951..1258e49f24 100644
--- a/frontend/src/app/main/ui/viewer/interactions.cljs
+++ b/frontend/src/app/main/ui/viewer/interactions.cljs
@@ -169,3 +169,338 @@
[:span.icon i/tick]
[:span.label (tr "viewer.header.show-interactions-on-click")]]]]]))
+
+(defn animate-go-to-frame
+ [animation current-viewport orig-viewport current-size orig-size wrapper-size]
+ (case (:animation-type animation)
+
+ :dissolve
+ (do (dom/animate! orig-viewport
+ [#js {:opacity "100"}
+ #js {:opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (dom/animate! current-viewport
+ [#js {:opacity "0"}
+ #js {:opacity "100"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}))
+
+ :slide
+ (case (:way animation)
+
+ :in
+ (case (:direction animation)
+
+ :right
+ (let [offset (+ (:width current-size)
+ (/ (- (:width wrapper-size) (:width current-size)) 2))]
+ (dom/animate! current-viewport
+ [#js {:left (str "-" offset "px")}
+ #js {:left "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! orig-viewport
+ [#js {:left "0"
+ :opacity "100%"}
+ #js {:left (str (* offset 0.2) "px")
+ :opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :left
+ (let [offset (+ (:width current-size)
+ (/ (- (:width wrapper-size) (:width current-size)) 2))]
+ (dom/animate! current-viewport
+ [#js {:right (str "-" offset "px")}
+ #js {:right "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! orig-viewport
+ [#js {:right "0"
+ :opacity "100%"}
+ #js {:right (str (* offset 0.2) "px")
+ :opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :up
+ (let [offset (+ (:height current-size)
+ (/ (- (:height wrapper-size) (:height current-size)) 2))]
+ (dom/animate! current-viewport
+ [#js {:bottom (str "-" offset "px")}
+ #js {:bottom "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! orig-viewport
+ [#js {:bottom "0"
+ :opacity "100%"}
+ #js {:bottom (str (* offset 0.2) "px")
+ :opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :down
+ (let [offset (+ (:height current-size)
+ (/ (- (:height wrapper-size) (:height current-size)) 2))]
+ (dom/animate! current-viewport
+ [#js {:top (str "-" offset "px")}
+ #js {:top "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! orig-viewport
+ [#js {:top "0"
+ :opacity "100%"}
+ #js {:top (str (* offset 0.2) "px")
+ :opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}))))
+
+ :out
+ (case (:direction animation)
+
+ :right
+ (let [offset (+ (:width orig-size)
+ (/ (- (:width wrapper-size) (:width orig-size)) 2))]
+ (dom/set-css-property! orig-viewport "z-index" 10000)
+ (dom/animate! orig-viewport
+ [#js {:right "0"}
+ #js {:right (str "-" offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! current-viewport
+ [#js {:right (str (* offset 0.2) "px")
+ :opacity "0"}
+ #js {:right "0"
+ :opacity "100%"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :left
+ (let [offset (+ (:width orig-size)
+ (/ (- (:width wrapper-size) (:width orig-size)) 2))]
+ (dom/set-css-property! orig-viewport "z-index" 10000)
+ (dom/animate! orig-viewport
+ [#js {:left "0"}
+ #js {:left (str "-" offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! current-viewport
+ [#js {:left (str (* offset 0.2) "px")
+ :opacity "0"}
+ #js {:left "0"
+ :opacity "100%"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :up
+ (let [offset (+ (:height orig-size)
+ (/ (- (:height wrapper-size) (:height orig-size)) 2))]
+ (dom/set-css-property! orig-viewport "z-index" 10000)
+ (dom/animate! orig-viewport
+ [#js {:top "0"}
+ #js {:top (str "-" offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! current-viewport
+ [#js {:top (str (* offset 0.2) "px")
+ :opacity "0"}
+ #js {:top "0"
+ :opacity "100%"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))
+
+ :down
+ (let [offset (+ (:height orig-size)
+ (/ (- (:height wrapper-size) (:height orig-size)) 2))]
+ (dom/set-css-property! orig-viewport "z-index" 10000)
+ (dom/animate! orig-viewport
+ [#js {:bottom "0"}
+ #js {:bottom (str "-" offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (when (:offset-effect animation)
+ (dom/animate! current-viewport
+ [#js {:bottom (str (* offset 0.2) "px")
+ :opacity "0"}
+ #js {:bottom "0"
+ :opacity "100%"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))))
+
+ :push
+ (case (:direction animation)
+
+ :right
+ (let [offset (:width wrapper-size)]
+ (dom/animate! current-viewport
+ [#js {:left (str "-" offset "px")}
+ #js {:left "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (dom/animate! orig-viewport
+ [#js {:left "0"}
+ #js {:left (str offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}))
+
+ :left
+ (let [offset (:width wrapper-size)]
+ (dom/animate! current-viewport
+ [#js {:right (str "-" offset "px")}
+ #js {:right "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (dom/animate! orig-viewport
+ [#js {:right "0"}
+ #js {:right (str offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}))
+
+ :up
+ (let [offset (:height wrapper-size)]
+ (dom/animate! current-viewport
+ [#js {:bottom (str "-" offset "px")}
+ #js {:bottom "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (dom/animate! orig-viewport
+ [#js {:bottom "0"}
+ #js {:bottom (str offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}))
+
+ :down
+ (let [offset (:height wrapper-size)]
+ (dom/animate! current-viewport
+ [#js {:top (str "-" offset "px")}
+ #js {:top "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+ (dom/animate! orig-viewport
+ [#js {:top "0"}
+ #js {:top (str offset "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))})))))
+
+(defn animate-open-overlay
+ [animation overlay-viewport
+ wrapper-size overlay-size overlay-position]
+ (case (:animation-type animation)
+
+ :dissolve
+ (dom/animate! overlay-viewport
+ [#js {:opacity "0"}
+ #js {:opacity "100"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+
+ :slide
+ (case (:direction animation) ;; way and offset-effect are ignored
+
+ :right
+ (dom/animate! overlay-viewport
+ [#js {:left (str "-" (:width overlay-size) "px")}
+ #js {:left (str (:x overlay-position) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+
+ :left
+ (dom/animate! overlay-viewport
+ [#js {:left (str (:width wrapper-size) "px")}
+ #js {:left (str (:x overlay-position) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+
+ :up
+ (dom/animate! overlay-viewport
+ [#js {:top (str (:height wrapper-size) "px")}
+ #js {:top (str (:y overlay-position) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)))
+
+ :down
+ (dom/animate! overlay-viewport
+ [#js {:top (str "-" (:height overlay-size) "px")}
+ #js {:top (str (:y overlay-position) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation))))))
+
+(defn animate-close-overlay
+ [animation overlay-viewport
+ wrapper-size overlay-size overlay-position overlay-id]
+ (case (:animation-type animation)
+
+ :dissolve
+ (dom/animate! overlay-viewport
+ [#js {:opacity "100"}
+ #js {:opacity "0"}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)
+ (dv/close-overlay overlay-id)))
+
+ :slide
+ (case (:direction animation) ;; way and offset-effect are ignored
+
+ :right
+ (dom/animate! overlay-viewport
+ [#js {:left (str (:x overlay-position) "px")}
+ #js {:left (str (:width wrapper-size) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)
+ (dv/close-overlay overlay-id)))
+
+ :left
+ (dom/animate! overlay-viewport
+ [#js {:left (str (:x overlay-position) "px")}
+ #js {:left (str "-" (:width overlay-size) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)
+ (dv/close-overlay overlay-id)))
+
+ :up
+ (dom/animate! overlay-viewport
+ [#js {:top (str (:y overlay-position) "px")}
+ #js {:top (str "-" (:height overlay-size) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)
+ (dv/close-overlay overlay-id)))
+
+ :down
+ (dom/animate! overlay-viewport
+ [#js {:top (str (:y overlay-position) "px")}
+ #js {:top (str (:height wrapper-size) "px")}]
+ #js {:duration (:duration animation)
+ :easing (name (:easing animation))}
+ #(st/emit! (dv/complete-animation)
+ (dv/close-overlay overlay-id))))))
+
diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs
index cff1ce87a2..38c628c4f4 100644
--- a/frontend/src/app/main/ui/viewer/shapes.cljs
+++ b/frontend/src/app/main/ui/viewer/shapes.cljs
@@ -38,7 +38,7 @@
(def viewer-interactions-show?
(l/derived :interactions-show? refs/viewer-local))
-(defn activate-interaction
+(defn- activate-interaction
[interaction shape base-frame frame-offset objects]
(case (:action-type interaction)
:navigate
@@ -48,7 +48,7 @@
(dom/get-scroll-pos viewer-section)
0)]
(st/emit! (dv/set-nav-scroll scroll)
- (dv/go-to-frame frame-id))))
+ (dv/go-to-frame frame-id (:animation interaction)))))
:open-overlay
(let [dest-frame-id (:destination interaction)
@@ -64,7 +64,8 @@
(st/emit! (dv/open-overlay dest-frame-id
position
close-click-outside
- background-overlay))))
+ background-overlay
+ (:animation interaction)))))
:toggle-overlay
(let [frame-id (:destination interaction)
@@ -75,14 +76,15 @@
(st/emit! (dv/toggle-overlay frame-id
position
close-click-outside
- background-overlay))))
+ background-overlay
+ (:animation interaction)))))
:close-overlay
(let [frame-id (or (:destination interaction)
(if (= (:type shape) :frame)
(:id shape)
(:frame-id shape)))]
- (st/emit! (dv/close-overlay frame-id)))
+ (st/emit! (dv/close-overlay frame-id (:animation interaction))))
:prev-screen
(st/emit! (rt/nav-back-local))
@@ -93,7 +95,7 @@
nil))
;; Perform the opposite action of an interaction, if possible
-(defn deactivate-interaction
+(defn- deactivate-interaction
[interaction shape base-frame frame-offset objects]
(case (:action-type interaction)
:open-overlay
@@ -112,7 +114,8 @@
(st/emit! (dv/toggle-overlay frame-id
position
close-click-outside
- background-overlay))))
+ background-overlay
+ (:animation interaction)))))
:close-overlay
(let [dest-frame-id (:destination interaction)
@@ -128,10 +131,11 @@
(st/emit! (dv/open-overlay dest-frame-id
position
close-click-outside
- background-overlay))))
+ background-overlay
+ (:animation interaction)))))
nil))
-(defn on-mouse-down
+(defn- on-mouse-down
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(or (= (:event-type %) :click)
@@ -141,7 +145,7 @@
(doseq [interaction interactions]
(activate-interaction interaction shape base-frame frame-offset objects)))))
-(defn on-mouse-up
+(defn- on-mouse-up
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :mouse-press)))]
@@ -150,7 +154,7 @@
(doseq [interaction interactions]
(deactivate-interaction interaction shape base-frame frame-offset objects)))))
-(defn on-mouse-enter
+(defn- on-mouse-enter
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(or (= (:event-type %) :mouse-enter)
@@ -160,7 +164,7 @@
(doseq [interaction interactions]
(activate-interaction interaction shape base-frame frame-offset objects)))))
-(defn on-mouse-leave
+(defn- on-mouse-leave
[event shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :mouse-leave)))
@@ -173,7 +177,7 @@
(doseq [interaction interactions-inv]
(deactivate-interaction interaction shape base-frame frame-offset objects)))))
-(defn on-load
+(defn- on-load
[shape base-frame frame-offset objects]
(let [interactions (->> (:interactions shape)
(filter #(= (:event-type %) :after-delay)))]
@@ -195,8 +199,8 @@
:y (- y 1)
:width (+ width 2)
:height (+ height 2)
- :fill "#31EFB8"
- :stroke "#31EFB8"
+ :fill "var(--color-primary)"
+ :stroke "var(--color-primary)"
:stroke-width (if interactions-show? 1 0)
:fill-opacity (if interactions-show? 0.2 0)
:style {:pointer-events (when frame? "none")}
diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs
index 52082e5163..c383149a03 100644
--- a/frontend/src/app/main/ui/viewer/thumbnails.cljs
+++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs
@@ -8,7 +8,7 @@
(:require
[app.common.data :as d]
[app.main.data.viewer :as dv]
- [app.main.exports :as exports]
+ [app.main.render :as render]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
@@ -77,7 +77,7 @@
[:div.thumbnail-item {:on-click #(on-click % index)}
[:div.thumbnail-preview
{:class (dom/classnames :selected selected?)}
- [:& exports/frame-svg {:frame frame :objects objects}]]
+ [:& render/frame-svg {:frame frame :objects objects}]]
[:div.thumbnail-info
[:span.name {:title (:name frame)} (:name frame)]]])
diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs
index 1ccc80d5b6..7181f147fa 100644
--- a/frontend/src/app/main/ui/workspace.cljs
+++ b/frontend/src/app/main/ui/workspace.cljs
@@ -87,7 +87,8 @@
(mf/defc workspace-page
[{:keys [file layout page-id] :as props}]
- (mf/use-layout-effect
+
+ (mf/use-layout-effect
(mf/deps page-id)
(fn []
(if (nil? page-id)
diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs
index e4bbf6d37a..006fdaaa5c 100644
--- a/frontend/src/app/main/ui/workspace/colorpalette.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs
@@ -8,7 +8,6 @@
(:require
[app.common.math :as mth]
[app.main.data.workspace.colors :as mdc]
- [app.main.data.workspace.state-helpers :as wsh]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.color-bullet :as cb]
@@ -40,12 +39,12 @@
;; --- Components
(mf/defc palette-item
[{:keys [color size]}]
- (let [select-color
+ (let [ids-with-children (map :id (mf/deref refs/selected-shapes-with-children))
+ select-color
(fn [event]
- (let [ids (wsh/lookup-selected @st/state)]
- (if (kbd/shift? event)
- (st/emit! (mdc/change-stroke ids (merge uc/empty-color color)))
- (st/emit! (mdc/change-fill ids (merge uc/empty-color color))))))]
+ (if (kbd/alt? event)
+ (st/emit! (mdc/change-stroke ids-with-children (merge uc/empty-color color)))
+ (st/emit! (mdc/change-fill ids-with-children (merge uc/empty-color color)))))]
[:div.color-cell {:class (str "cell-"(name size))
:on-click select-color}
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 6d4d306421..2b34513072 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.colorpicker
(:require
+ [app.common.colors :as clr]
[app.main.data.modal :as modal]
[app.main.data.workspace.colors :as dc]
[app.main.data.workspace.libraries :as dwl]
@@ -49,7 +50,7 @@
;; --- Color Picker Modal
(defn color->components [value opacity]
- (let [value (if (uc/hex? value) value "#000000")
+ (let [value (if (uc/hex? value) value clr/black)
[r g b] (uc/hex->rgb value)
[h s v] (uc/hex->hsv value)]
@@ -134,8 +135,15 @@
(fn [changes]
(let [editing-stop (:editing-stop @state)]
(swap! state #(cond-> %
- true (update :current-color merge changes)
- editing-stop (update-in [:stops editing-stop] merge changes)))
+ :always
+ (update :current-color merge changes)
+
+ (not editing-stop)
+ (-> (assoc :type :color)
+ (dissoc :gradient-data :stops :editing-stops))
+
+ editing-stop
+ (update-in [:stops editing-stop] merge changes)))
(reset! dirty? true)))
handle-click-picker
@@ -161,9 +169,12 @@
is-gradient? (some? (:gradient color))]
(if (and (some? editing-stop) (not is-gradient?))
(handle-change-color (color->components (:color color) (:opacity color)))
- (do (reset! state (data->state color))
+ (do (reset! dirty? false)
+ (reset! state (-> (data->state color)
+ (assoc :editing-stop nil)))
(on-change color)))))
+
on-add-library-color
(fn [_]
(st/emit! (dwl/add-color (state->data @state))))
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
index ca17d1ede0..69247a2d09 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs
@@ -38,8 +38,8 @@
(.stroke ctx)))
(let [grd (.createRadialGradient ctx cx cy 0 cx cx radius)]
- (.addColorStop grd 0 "white")
- (.addColorStop grd 1 "rgba(255, 255, 255, 0")
+ (.addColorStop grd 0 "rgba(255, 255, 255, 1)")
+ (.addColorStop grd 1 "rgba(255, 255, 255, 0)")
(obj/set! ctx "fillStyle" grd)
(.beginPath ctx)
diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs
index a305e8fed4..7023b52a95 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs
@@ -81,11 +81,11 @@
[:div.selected-colors
(when (= selected-library :file)
- [:div.color-bullet.button.plus-button {:style {:background-color "white"}
+ [:div.color-bullet.button.plus-button {:style {:background-color "var(--color-white)"}
:on-click on-add-library-color}
i/plus])
- [:div.color-bullet.button {:style {:background-color "white"}
+ [:div.color-bullet.button {:style {:background-color "var(--color-white)"}
:on-click #(st/emit! (dc/show-palette selected-library))}
i/palette]
diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs
index ea92fc79d2..322aa473c5 100644
--- a/frontend/src/app/main/ui/workspace/comments.cljs
+++ b/frontend/src/app/main/ui/workspace/comments.cljs
@@ -79,7 +79,9 @@
(st/emit! (dw/go-to-page (:page-id thread))))
(tm/schedule
(fn []
- (st/emit! (dwcm/center-to-comment-thread thread)
+ (st/emit! (when (not= page-id (:page-id thread))
+ (dw/select-for-drawing :comments))
+ (dwcm/center-to-comment-thread thread)
(-> (dcm/open-thread thread)
(with-meta {::ev/origin "workspace"})))))))]
diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs
index abe4ed6062..58556d94ff 100644
--- a/frontend/src/app/main/ui/workspace/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/context_menu.cljs
@@ -7,13 +7,13 @@
(ns app.main.ui.workspace.context-menu
"A workspace specific context menu (mouse right click)."
(:require
+ [app.common.data :as d]
[app.common.types.page-options :as cto]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.interactions :as dwi]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.shortcuts :as sc]
- [app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
@@ -85,90 +85,12 @@
[]
[:li.separator])
-(mf/defc shape-context-menu
- [{:keys [mdata] :as props}]
- (let [{:keys [shape selected disable-booleans? disable-flatten?]} mdata
- {:keys [id type]} shape
-
- single? (= (count selected) 1)
- multiple? (> (count selected) 1)
- editable-shape? (#{:group :text :path} type)
-
- is-group? (and (some? shape) (= :group type))
- is-bool? (and (some? shape) (= :bool type))
-
- options (mf/deref refs/workspace-page-options)
- flows (:flows options)
-
- options-mode (mf/deref refs/options-mode)
-
- set-bool
- (fn [bool-type]
- #(cond
- (> (count selected) 1)
- (st/emit! (dw/create-bool bool-type))
-
- (and (= (count selected) 1) is-group?)
- (st/emit! (dw/group-to-bool (:id shape) bool-type))
-
- (and (= (count selected) 1) is-bool?)
- (st/emit! (dw/change-bool-type (:id shape) bool-type))))
-
- current-file-id (mf/use-ctx ctx/current-file-id)
-
- do-duplicate (st/emitf (dw/duplicate-selected false))
- do-delete (st/emitf dw/delete-selected)
- do-copy (st/emitf (dw/copy-selected))
- do-cut (st/emitf (dw/copy-selected) dw/delete-selected)
- do-paste (st/emitf dw/paste)
- do-bring-forward (st/emitf (dw/vertical-order-selected :up))
- do-bring-to-front (st/emitf (dw/vertical-order-selected :top))
- do-send-backward (st/emitf (dw/vertical-order-selected :down))
- do-send-to-back (st/emitf (dw/vertical-order-selected :bottom))
- do-show-shape (st/emitf (dw/update-shape-flags id {:hidden false}))
- do-hide-shape (st/emitf (dw/update-shape-flags id {:hidden true}))
- do-lock-shape (st/emitf (dw/update-shape-flags id {:blocked true}))
- do-unlock-shape (st/emitf (dw/update-shape-flags id {:blocked false}))
- do-add-flow (st/emitf (dwi/add-flow-selected-frame))
- do-remove-flow #(st/emitf (dwi/remove-flow (:id %)))
- do-create-group (st/emitf dw/group-selected)
- do-remove-group (st/emitf dw/ungroup-selected)
- do-mask-group (st/emitf dw/mask-group)
- do-unmask-group (st/emitf dw/unmask-group)
- do-flip-vertical (st/emitf (dw/flip-vertical-selected))
- do-flip-horizontal (st/emitf (dw/flip-horizontal-selected))
- do-add-component (st/emitf (dwl/add-component))
- do-detach-component (st/emitf (dwl/detach-component id))
- do-reset-component (st/emitf (dwl/reset-component id))
- do-start-editing (fn []
- ;; We defer the execution so the mouse event won't close the editor
- (timers/schedule #(st/emit! (dw/start-editing-selected))))
- do-update-component (st/emitf
- (dwu/start-undo-transaction)
- (dwl/update-component id)
- (dwl/sync-file current-file-id (:component-file shape))
- (dwu/commit-undo-transaction))
- confirm-update-remote-component (st/emitf
- (dwl/update-component id)
- (dwl/sync-file current-file-id
- (:component-file shape))
- (dwl/sync-file (:component-file shape)
- (:component-file shape)))
- do-update-remote-component (st/emitf (modal/show
- {:type :confirm
- :message ""
- :title (tr "modals.update-remote-component.message")
- :hint (tr "modals.update-remote-component.hint")
- :cancel-label (tr "modals.update-remote-component.cancel")
- :accept-label (tr "modals.update-remote-component.accept")
- :accept-style :primary
- :on-accept confirm-update-remote-component}))
- do-show-component (st/emitf (dw/go-to-layout :assets))
- do-navigate-component-file (st/emitf (dwl/nav-to-component-file
- (:component-file shape)))
-
- do-transform-to-path (st/emitf (dw/convert-selected-to-path))
- do-flatten (st/emitf (dw/convert-selected-to-path))]
+(mf/defc context-menu-edit
+ []
+ (let [do-copy (st/emitf (dw/copy-selected))
+ do-cut (st/emitf (dw/copy-selected) dw/delete-selected)
+ do-paste (st/emitf dw/paste)
+ do-duplicate (st/emitf (dw/duplicate-selected false))]
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.copy")
:shortcut (sc/get-tooltip :copy)
@@ -182,7 +104,16 @@
[:& menu-entry {:title (tr "workspace.shape.menu.duplicate")
:shortcut (sc/get-tooltip :duplicate)
:on-click do-duplicate}]
- [:& menu-separator]
+
+ [:& menu-separator]]))
+
+(mf/defc context-menu-layer-position
+ []
+ (let [do-bring-forward (st/emitf (dw/vertical-order-selected :up))
+ do-bring-to-front (st/emitf (dw/vertical-order-selected :top))
+ do-send-backward (st/emitf (dw/vertical-order-selected :down))
+ do-send-to-back (st/emitf (dw/vertical-order-selected :bottom))]
+ [:*
[:& menu-entry {:title (tr "workspace.shape.menu.forward")
:shortcut (sc/get-tooltip :bring-forward)
:on-click do-bring-forward}]
@@ -195,47 +126,105 @@
[:& menu-entry {:title (tr "workspace.shape.menu.back")
:shortcut (sc/get-tooltip :bring-back)
:on-click do-send-to-back}]
- [:& menu-separator]
- (when multiple?
+ [:& menu-separator]]))
+
+(mf/defc context-menu-flip
+ []
+ (let [do-flip-vertical (st/emitf (dw/flip-vertical-selected))
+ do-flip-horizontal (st/emitf (dw/flip-horizontal-selected))]
+ [:*
+ [:& menu-entry {:title (tr "workspace.shape.menu.flip-vertical")
+ :shortcut (sc/get-tooltip :flip-vertical)
+ :on-click do-flip-vertical}]
+
+ [:& menu-entry {:title (tr "workspace.shape.menu.flip-horizontal")
+ :shortcut (sc/get-tooltip :flip-horizontal)
+ :on-click do-flip-horizontal}]
+ [:& menu-separator]]))
+
+(mf/defc context-menu-group
+ [{:keys [shapes]}]
+
+ (let [multiple? (> (count shapes) 1)
+ single? (= (count shapes) 1)
+ do-create-artboard-from-selection (st/emitf (dw/create-artboard-from-selection))
+
+ has-group? (->> shapes (d/seek #(= :group (:type %))))
+ has-bool? (->> shapes (d/seek #(= :bool (:type %))))
+ has-mask? (->> shapes (d/seek :masked-group?))
+ has-frame? (->> shapes (d/seek #(= :frame (:type %))))
+
+ is-group? (and single? has-group?)
+ is-bool? (and single? has-bool?)
+
+ do-create-group (st/emitf dw/group-selected)
+ do-mask-group (st/emitf dw/mask-group)
+ do-remove-group (st/emitf dw/ungroup-selected)
+ do-unmask-group (st/emitf dw/unmask-group)]
+
+ [:*
+ (when (or has-bool? has-group? has-mask?)
+ [:& menu-entry {:title (tr "workspace.shape.menu.ungroup")
+ :shortcut (sc/get-tooltip :ungroup)
+ :on-click do-remove-group}])
+
+ (when (not has-frame?)
+ [:& menu-entry {:title (tr "workspace.shape.menu.group")
+ :shortcut (sc/get-tooltip :group)
+ :on-click do-create-group}])
+
+ (when (or multiple? (and is-group? (not has-mask?)) is-bool?)
+ [:& menu-entry {:title (tr "workspace.shape.menu.mask")
+ :shortcut (sc/get-tooltip :mask)
+ :on-click do-mask-group}])
+
+ (when has-mask?
+ [:& menu-entry {:title (tr "workspace.shape.menu.unmask")
+ :shortcut (sc/get-tooltip :unmask)
+ :on-click do-unmask-group}])
+
+ (when (not has-frame?)
[:*
- [:& menu-entry {:title (tr "workspace.shape.menu.group")
- :shortcut (sc/get-tooltip :group)
- :on-click do-create-group}]
- [:& menu-entry {:title (tr "workspace.shape.menu.mask")
- :shortcut (sc/get-tooltip :mask)
- :on-click do-mask-group}]
- [:& menu-separator]])
+ [:& menu-entry {:title (tr "workspace.shape.menu.create-artboard-from-selection")
+ :shortcut (sc/get-tooltip :create-artboard-from-selection)
+ :on-click do-create-artboard-from-selection}]
+ [:& menu-separator]])]))
- (when (or single? multiple?)
- [:*
- [:& menu-entry {:title (tr "workspace.shape.menu.flip-vertical")
- :shortcut (sc/get-tooltip :flip-vertical)
- :on-click do-flip-vertical}]
- [:& menu-entry {:title (tr "workspace.shape.menu.flip-horizontal")
- :shortcut (sc/get-tooltip :flip-horizontal)
- :on-click do-flip-horizontal}]
- [:& menu-separator]])
+(mf/defc context-menu-path
+ [{:keys [shapes disable-flatten? disable-booleans?]}]
+ (let [multiple? (> (count shapes) 1)
+ single? (= (count shapes) 1)
- (when (and single? (or is-bool? is-group?))
- [:*
- [:& menu-entry {:title (tr "workspace.shape.menu.ungroup")
- :shortcut (sc/get-tooltip :ungroup)
- :on-click do-remove-group}]
- (if (:masked-group? shape)
- [:& menu-entry {:title (tr "workspace.shape.menu.unmask")
- :shortcut (sc/get-tooltip :unmask)
- :on-click do-unmask-group}]
- [:& menu-entry {:title (tr "workspace.shape.menu.mask")
- :shortcut (sc/get-tooltip :group)
- :on-click do-mask-group}])])
+ has-group? (->> shapes (d/seek #(= :group (:type %))))
+ has-bool? (->> shapes (d/seek #(= :bool (:type %))))
+ has-frame? (->> shapes (d/seek #(= :frame (:type %))))
- (when (and single? editable-shape?)
+ is-group? (and single? has-group?)
+ is-bool? (and single? has-bool?)
+ is-frame? (and single? has-frame?)
+
+ do-start-editing #(timers/schedule (st/emitf (dw/start-editing-selected)))
+ do-transform-to-path (st/emitf (dw/convert-selected-to-path))
+
+ make-do-bool
+ (fn [bool-type]
+ #(cond
+ multiple?
+ (st/emit! (dw/create-bool bool-type))
+
+ is-group?
+ (st/emit! (dw/group-to-bool (-> shapes first :id) bool-type))
+
+ is-bool?
+ (st/emit! (dw/change-bool-type (-> shapes first :id) bool-type))))]
+ [:*
+ (when (and single? (not is-frame?))
[:& menu-entry {:title (tr "workspace.shape.menu.edit")
:shortcut (sc/get-tooltip :start-editing)
:on-click do-start-editing}])
- (when-not disable-flatten?
+ (when-not (or disable-flatten? has-frame?)
[:& menu-entry {:title (tr "workspace.shape.menu.transform-to-path")
:on-click do-transform-to-path}])
@@ -243,85 +232,165 @@
(or multiple? (and single? (or is-group? is-bool?))))
[:& menu-entry {:title (tr "workspace.shape.menu.path")}
[:& menu-entry {:title (tr "workspace.shape.menu.union")
- :shortcut (sc/get-tooltip :boolean-union)
- :on-click (set-bool :union)}]
+ :shortcut (sc/get-tooltip :bool-union)
+ :on-click (make-do-bool :union)}]
[:& menu-entry {:title (tr "workspace.shape.menu.difference")
- :shortcut (sc/get-tooltip :boolean-difference)
- :on-click (set-bool :difference)}]
+ :shortcut (sc/get-tooltip :bool-difference)
+ :on-click (make-do-bool :difference)}]
[:& menu-entry {:title (tr "workspace.shape.menu.intersection")
- :shortcut (sc/get-tooltip :boolean-intersection)
- :on-click (set-bool :intersection)}]
+ :shortcut (sc/get-tooltip :bool-intersection)
+ :on-click (make-do-bool :intersection)}]
[:& menu-entry {:title (tr "workspace.shape.menu.exclude")
- :shortcut (sc/get-tooltip :boolean-exclude)
- :on-click (set-bool :exclude)}]
+ :shortcut (sc/get-tooltip :bool-exclude)
+ :on-click (make-do-bool :exclude)}]
(when (and single? is-bool? (not disable-flatten?))
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.flatten")
- :on-click do-flatten}]])])
+ :on-click do-transform-to-path}]])])]))
- (if (:hidden shape)
+(mf/defc context-menu-layer-options
+ [{:keys [shapes]}]
+ (let [ids (mapv :id shapes)
+ do-show-shape (st/emitf (dw/update-shape-flags ids {:hidden false}))
+ do-hide-shape (st/emitf (dw/update-shape-flags ids {:hidden true}))
+ do-lock-shape (st/emitf (dw/update-shape-flags ids {:blocked true}))
+ do-unlock-shape (st/emitf (dw/update-shape-flags ids {:blocked false}))]
+ [:*
+ (if (every? :hidden shapes)
[:& menu-entry {:title (tr "workspace.shape.menu.show")
:on-click do-show-shape}]
[:& menu-entry {:title (tr "workspace.shape.menu.hide")
:on-click do-hide-shape}])
- (if (:blocked shape)
+ (if (every? :blocked shapes)
[:& menu-entry {:title (tr "workspace.shape.menu.unlock")
:on-click do-unlock-shape}]
[:& menu-entry {:title (tr "workspace.shape.menu.lock")
- :on-click do-lock-shape}])
+ :on-click do-lock-shape}])]))
- (when (and (= options-mode :prototype) (= (:type shape) :frame))
- (let [flow (cto/get-frame-flow flows (:id shape))]
- (if (nil? flow)
- [:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
- :on-click do-add-flow}]
- [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start")
- :on-click (do-remove-flow flow)}])))
+(mf/defc context-menu-prototype
+ [{:keys [shapes]}]
+ (let [options (mf/deref refs/workspace-page-options)
+ options-mode (mf/deref refs/options-mode)
+ do-add-flow (st/emitf (dwi/add-flow-selected-frame))
+ do-remove-flow #(st/emitf (dwi/remove-flow (:id %)))
+ flows (:flows options)
- (when (and (not= (:type shape) :frame)
- (or multiple? (nil? (:component-id shape))))
+ prototype? (= options-mode :prototype)
+ single? (= (count shapes) 1)
+ has-frame? (->> shapes (d/seek #(= :frame (:type %))))
+ is-frame? (and single? has-frame?)]
+
+ (when (and prototype? is-frame?)
+ (let [flow (cto/get-frame-flow flows (-> shapes first :id))]
+ (if (some? flow)
+ [:& menu-entry {:title (tr "workspace.shape.menu.delete-flow-start")
+ :on-click (do-remove-flow flow)}]
+
+ [:& menu-entry {:title (tr "workspace.shape.menu.flow-start")
+ :on-click do-add-flow}])))))
+
+(mf/defc context-menu-component
+ [{:keys [shapes]}]
+ (let [single? (= (count shapes) 1)
+
+ has-frame? (->> shapes (d/seek #(= :frame (:type %))))
+ has-component? (some true? (map #(contains? % :component-id) shapes))
+ is-component? (and single? (-> shapes first :component-id some?))
+
+ shape-id (->> shapes first :id)
+ component-id (->> shapes first :component-id)
+ component-file (-> shapes first :component-file)
+
+ current-file-id (mf/use-ctx ctx/current-file-id)
+ local-component? (= component-file current-file-id)
+
+ do-add-component (st/emitf (dwl/add-component))
+ do-detach-component (st/emitf (dwl/detach-component shape-id))
+ do-detach-component-in-bulk (st/emitf dwl/detach-selected-components)
+ do-reset-component (st/emitf (dwl/reset-component shape-id))
+ do-show-component (st/emitf (dw/go-to-component component-id))
+ do-navigate-component-file (st/emitf (dwl/nav-to-component-file component-file))
+ do-update-component (st/emitf (dwl/update-component-sync shape-id component-file))
+
+ do-update-remote-component
+ (st/emitf (modal/show
+ {:type :confirm
+ :message ""
+ :title (tr "modals.update-remote-component.message")
+ :hint (tr "modals.update-remote-component.hint")
+ :cancel-label (tr "modals.update-remote-component.cancel")
+ :accept-label (tr "modals.update-remote-component.accept")
+ :accept-style :primary
+ :on-accept do-update-component}))]
+ [:*
+ (when (and (not has-frame?) (not is-component?))
[:*
[:& menu-separator]
[:& menu-entry {:title (tr "workspace.shape.menu.create-component")
:shortcut (sc/get-tooltip :create-component)
- :on-click do-add-component}]])
+ :on-click do-add-component}]
+ (when has-component?
+ [:& menu-entry {:title (tr "workspace.shape.menu.detach-instances-in-bulk")
+ :shortcut (sc/get-tooltip :detach-component)
+ :on-click do-detach-component-in-bulk}])])
- (when (and (:component-id shape)
- (= (count selected) 1))
+ (when is-component?
;; WARNING: this menu is the same as the context menu at the sidebar.
;; If you change it, you must change equally the file
- ;; app/main/ui/workspace/sidebar/options/component.cljs
- (if (= (:component-file shape) current-file-id)
- [:*
- [:& menu-separator]
- [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
- :shortcut (sc/get-tooltip :detach-component)
- :on-click do-detach-component}]
- [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
- :on-click do-reset-component}]
- [:& menu-entry {:title (tr "workspace.shape.menu.update-main")
- :on-click do-update-component}]
- [:& menu-entry {:title (tr "workspace.shape.menu.show-main")
- :on-click do-show-component}]]
- [:*
- [:& menu-separator]
- [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
- :shortcut (sc/get-tooltip :detach-component)
- :on-click do-detach-component}]
- [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
- :on-click do-reset-component}]
- [:& menu-entry {:title (tr "workspace.shape.menu.go-main")
- :on-click do-navigate-component-file}]
- [:& menu-entry {:title (tr "workspace.shape.menu.update-main")
- :on-click do-update-remote-component}]]))
+ ;; app/main/ui/workspace/sidebar/options/menus/component.cljs
- [:& menu-separator]
- [:& menu-entry {:title (tr "workspace.shape.menu.delete")
- :shortcut (sc/get-tooltip :delete)
- :on-click do-delete}]]))
+ [:*
+ [:& menu-separator]
+ [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance")
+ :shortcut (sc/get-tooltip :detach-component)
+ :on-click do-detach-component}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides")
+ :on-click do-reset-component}]
+
+
+ (if local-component?
+ [:*
+ [:& menu-entry {:title (tr "workspace.shape.menu.update-main")
+ :on-click do-update-component}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.show-main")
+ :on-click do-show-component}]]
+
+ [:*
+ [:& menu-entry {:title (tr "workspace.shape.menu.go-main")
+ :on-click do-navigate-component-file}]
+ [:& menu-entry {:title (tr "workspace.shape.menu.update-main")
+ :on-click do-update-remote-component}]])])
+ [:& menu-separator]]))
+
+(mf/defc context-menu-delete
+ []
+ (let [do-delete (st/emitf dw/delete-selected)]
+ [:& menu-entry {:title (tr "workspace.shape.menu.delete")
+ :shortcut (sc/get-tooltip :delete)
+ :on-click do-delete}]))
+
+(mf/defc shape-context-menu
+ [{:keys [mdata] :as props}]
+ (let [{:keys [disable-booleans? disable-flatten?]} mdata
+ shapes (mf/deref refs/selected-objects)
+
+ props #js {:shapes shapes
+ :disable-booleans? disable-booleans?
+ :disable-flatten? disable-flatten?}]
+ (when-not (empty? shapes)
+ [:*
+ [:> context-menu-edit props]
+ [:> context-menu-layer-position props]
+ [:> context-menu-flip props]
+ [:> context-menu-group props]
+ [:> context-menu-path props]
+ [:> context-menu-layer-options props]
+ [:> context-menu-prototype props]
+ [:> context-menu-component props]
+ [:> context-menu-delete props]])))
(mf/defc viewport-context-menu
[]
@@ -357,7 +426,7 @@
:style {:top top :left left}
:on-context-menu prevent-default}
- (if (:shape mdata)
+ (if (contains? mdata :selected)
[:& shape-context-menu {:mdata mdata}]
[:& viewport-context-menu {:mdata mdata}])]]))
diff --git a/frontend/src/app/main/ui/workspace/effects.cljs b/frontend/src/app/main/ui/workspace/effects.cljs
index 12515dc0da..667bb64b62 100644
--- a/frontend/src/app/main/ui/workspace/effects.cljs
+++ b/frontend/src/app/main/ui/workspace/effects.cljs
@@ -28,16 +28,6 @@
(fn []
(st/emit! (dws/change-hover-state id false)))))
-(defn use-context-menu
- [shape]
- (mf/use-callback
- (mf/deps shape)
- (fn [event]
- (dom/prevent-default event)
- (dom/stop-propagation event)
- (let [position (dom/get-client-position event)]
- (st/emit! (dw/show-shape-context-menu {:position position :shape shape}))))))
-
(defn use-mouse-down
[{:keys [id type blocked]}]
(mf/use-callback
diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs
index 828a60913d..ca418b63fa 100644
--- a/frontend/src/app/main/ui/workspace/header.cljs
+++ b/frontend/src/app/main/ui/workspace/header.cljs
@@ -9,6 +9,7 @@
[app.common.data :as d]
[app.common.math :as mth]
[app.config :as cf]
+ [app.main.data.events :as ev]
[app.main.data.messages :as dm]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
@@ -25,6 +26,7 @@
[app.util.router :as rt]
[beicon.core :as rx]
[okulary.core :as l]
+ [potok.core :as ptk]
[rumext.alpha :as mf]))
;; --- Zoom Widget
@@ -57,16 +59,14 @@
[:span.icon i/msg-warning]
[:span.label (tr "workspace.header.save-error")]])]))
-
-(mf/defc zoom-widget
+(mf/defc zoom-widget-workspace
{::mf/wrap [mf/memo]}
[{:keys [zoom
on-increase
on-decrease
on-zoom-reset
on-zoom-fit
- on-zoom-selected
- on-fullscreen]
+ on-zoom-selected]
:as props}]
(let [show-dropdown? (mf/use-state false)]
[:div.zoom-widget {:on-click #(reset! show-dropdown? true)}
@@ -75,19 +75,24 @@
[:& dropdown {:show @show-dropdown?
:on-close #(reset! show-dropdown? false)}
[:ul.dropdown
- [:li {:on-click on-increase}
- "Zoom in" [:span (sc/get-tooltip :increase-zoom)]]
- [:li {:on-click on-decrease}
- "Zoom out" [:span (sc/get-tooltip :decrease-zoom)]]
- [:li {:on-click on-zoom-reset}
- "Zoom to 100%" [:span (sc/get-tooltip :reset-zoom)]]
+ [:li.basic-zoom-bar
+ [:span.zoom-btns
+ [:button {:on-click (fn [event]
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (on-decrease))} "-"]
+ [:p.zoom-size {} (str (mth/round (* 100 zoom)) "%")]
+ [:button {:on-click (fn [event]
+ (dom/stop-propagation event)
+ (dom/prevent-default event)
+ (on-increase))} "+"]]
+ [:button.reset-btn {:on-click on-zoom-reset} (tr "workspace.header.reset-zoom")]]
+ [:li.separator]
[:li {:on-click on-zoom-fit}
- "Zoom to fit all" [:span (sc/get-tooltip :fit-all)]]
+ (tr "workspace.header.zoom-fit-all") [:span (sc/get-tooltip :fit-all)]]
[:li {:on-click on-zoom-selected}
- "Zoom to selected" [:span (sc/get-tooltip :zoom-selected)]]
- (when on-fullscreen
- [:li {:on-click on-fullscreen}
- "Full screen"])]]]))
+ (tr "workspace.header.zoom-selected") [:span (sc/get-tooltip :zoom-selected)]]]]]))
+
;; --- Header Users
@@ -95,9 +100,9 @@
(mf/defc menu
[{:keys [layout project file team-id page-id] :as props}]
(let [show-menu? (mf/use-state false)
- editing? (mf/use-state false)
+ editing? (mf/use-state false)
- frames (mf/deref refs/workspace-frames)
+ frames (mf/deref refs/workspace-frames)
edit-input-ref (mf/use-ref nil)
@@ -149,6 +154,10 @@
(mf/use-callback
(mf/deps file team-id)
(fn [_]
+ (st/emit! (ptk/event ::ev/event {::ev/name "export-files"
+ ::ev/origin "workspace"
+ :num-files 1}))
+
(->> (rx/of file)
(rx/flat-map
(fn [file]
@@ -166,23 +175,24 @@
on-export-frames
(mf/use-callback
- (mf/deps file)
- (fn [_]
- (let [filename (str (:name file) ".pdf")
- frame-ids (mapv :id frames)]
- (st/emit! (dm/info (tr "workspace.options.exporting-object")
- {:timeout nil}))
- (->> (rp/query! :export-frames
- {:name (:name file)
- :file-id (:id file)
- :page-id page-id
- :frame-ids frame-ids})
- (rx/subs
+ (mf/deps file frames)
+ (fn [_]
+ (when (seq frames)
+ (let [filename (str (:name file) ".pdf")
+ frame-ids (mapv :id frames)]
+ (st/emit! (dm/info (tr "workspace.options.exporting-object")
+ {:timeout nil}))
+ (->> (rp/query! :export-frames
+ {:name (:name file)
+ :file-id (:id file)
+ :page-id page-id
+ :frame-ids frame-ids})
+ (rx/subs
(fn [body]
(dom/trigger-download filename body))
(fn [_error]
(st/emit! (dm/error (tr "errors.unexpected-error"))))
- (st/emitf dm/hide))))))]
+ (st/emitf dm/hide)))))))]
(mf/use-effect
(mf/deps @editing?)
@@ -248,6 +258,12 @@
(tr "workspace.header.menu.show-palette"))]
[:span.shortcut (sc/get-tooltip :toggle-palette)]]
+ [:li {:on-click #(st/emit! (dw/toggle-layout-flags :display-artboard-names))}
+ [:span
+ (if (contains? layout :display-artboard-names)
+ (tr "workspace.header.menu.hide-artboard-names")
+ (tr "workspace.header.menu.show-artboard-names"))]]
+
[:li {:on-click #(st/emit! (dw/toggle-layout-flags :assets))}
[:span
(if (contains? layout :assets)
@@ -282,14 +298,13 @@
[:li.export-file {:on-click on-export-file}
[:span (tr "dashboard.export-single")]]
- [:li.export-file {:on-click on-export-frames}
- [:span (tr "dashboard.export-frames")]]
+ (when (seq frames)
+ [:li.export-file {:on-click on-export-frames}
+ [:span (tr "dashboard.export-frames")]])
(when (contains? @cf/flags :user-feedback)
[:li.feedback {:on-click (st/emitf (rt/nav :settings-feedback))}
- [:span (tr "labels.give-feedback")]])
-
- ]]]))
+ [:span (tr "labels.give-feedback")]])]]]))
;; --- Header Component
@@ -297,7 +312,7 @@
[{:keys [file layout project page-id] :as props}]
(let [team-id (:team-id project)
zoom (mf/deref refs/selected-zoom)
- params {:page-id page-id :file-id (:id file)}
+ params {:page-id page-id :file-id (:id file) :section "interactions"}
go-back
(mf/use-callback
@@ -325,7 +340,7 @@
[:div.options-section
[:& persistence-state-widget]
- [:& zoom-widget
+ [:& zoom-widget-workspace
{:zoom zoom
:on-increase #(st/emit! (dw/increase-zoom nil))
:on-decrease #(st/emit! (dw/decrease-zoom nil))
diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs
index f7ed31957b..94c48e702c 100644
--- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs
+++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs
@@ -67,7 +67,7 @@
[:div.left-toolbar-inside
[:ul.left-toolbar-options
[:li.tooltip.tooltip-right
- {:alt (tr "workspace.toolbar.move")
+ {:alt (tr "workspace.toolbar.move" (sc/get-tooltip :move))
:class (when (and (nil? selected-drawtool)
(not edition)) "selected")
:on-click (st/emitf :interrupt)}
diff --git a/frontend/src/app/main/ui/workspace/rules.cljs b/frontend/src/app/main/ui/workspace/rules.cljs
index d455cf9705..a9d5b34b2e 100644
--- a/frontend/src/app/main/ui/workspace/rules.cljs
+++ b/frontend/src/app/main/ui/workspace/rules.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.rules
(:require
+ [app.common.colors :as colors]
[app.common.math :as mth]
[app.util.object :as obj]
[rumext.alpha :as mf]))
@@ -47,8 +48,8 @@
(.translate dctx 0 txfm))
(obj/set! dctx "font" "12px worksans")
- (obj/set! dctx "fillStyle" "#7B7D85")
- (obj/set! dctx "strokeStyle" "#7B7D85")
+ (obj/set! dctx "fillStyle" colors/gray-30)
+ (obj/set! dctx "strokeStyle" colors/gray-30)
(obj/set! dctx "textAlign" "center")
(loop [i minv]
diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs
index 70efd1f49c..315d697a9b 100644
--- a/frontend/src/app/main/ui/workspace/shapes.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes.cljs
@@ -12,7 +12,6 @@
others are defined using a generic wrapper implemented in
common."
(:require
- [app.common.geom.shapes :as geom]
[app.common.pages :as cp]
[app.common.uuid :as uuid]
[app.main.refs :as refs]
@@ -28,8 +27,8 @@
[app.main.ui.workspace.shapes.path :as path]
[app.main.ui.workspace.shapes.svg-raw :as svg-raw]
[app.main.ui.workspace.shapes.text :as text]
- [app.util.debug :refer [debug?]]
[app.util.object :as obj]
+ [debug :refer [debug?]]
[okulary.core :as l]
[rumext.alpha :as mf]))
@@ -77,20 +76,16 @@
:key (:id item)}]))]))
(mf/defc shape-wrapper
- {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
(let [shape (obj/get props "shape")
- frame (obj/get props "frame")
- shape (-> (geom/transform-shape shape {:round-coords? false})
- (geom/translate-to-frame frame))
- opts #js {:shape shape
- :frame frame}
+ opts #js {:shape shape}
svg-element? (and (= (:type shape) :svg-raw)
(not= :svg (get-in shape [:content :tag])))]
- (when (and shape (not (:hidden shape)))
+ (when (and (some? shape) (not (:hidden shape)))
[:*
(if-not svg-element?
(case (:type shape)
@@ -104,7 +99,7 @@
:bool [:> bool-wrapper opts]
;; Only used when drawing a new frame.
- :frame [:> frame-wrapper {:shape shape}]
+ :frame [:> frame-wrapper opts]
nil)
@@ -112,7 +107,7 @@
[:> svg-raw-wrapper opts])
(when (debug? :bounding-boxes)
- [:& bounding-box {:shape shape :frame frame}])])))
+ [:> bounding-box opts])])))
(def group-wrapper (group/group-wrapper-factory shape-wrapper))
(def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper))
diff --git a/frontend/src/app/main/ui/workspace/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/shapes/bool.cljs
index 2cc8f6e006..96234a0dd8 100644
--- a/frontend/src/app/main/ui/workspace/shapes/bool.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/bool.cljs
@@ -27,21 +27,26 @@
[shape-wrapper]
(let [shape-component (bool/bool-shape shape-wrapper)]
(mf/fnc bool-wrapper
- {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
- (let [shape (unchecked-get props "shape")
- frame (unchecked-get props "frame")
+ (let [shape (unchecked-get props "shape")
+ child-sel-ref (mf/use-memo
+ (mf/deps (:id shape))
+ #(refs/is-child-selected? (:id shape)))
- childs-ref (mf/use-memo
- (mf/deps (:id shape))
- #(refs/select-children (:id shape)))
+ childs-ref (mf/use-memo
+ (mf/deps (:id shape))
+ #(refs/select-bool-children (:id shape)))
- childs (mf/deref childs-ref)]
+ child-sel? (mf/deref child-sel-ref)
+ childs (mf/deref childs-ref)
+
+ shape (cond-> shape
+ child-sel?
+ (dissoc :bool-content))]
[:> shape-container {:shape shape}
- [:& shape-component
- {:frame frame
- :shape shape
- :childs childs}]]))))
+ [:& shape-component {:shape shape
+ :childs childs}]]))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs
index c7d599a2bb..7e05a78811 100644
--- a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs
@@ -65,7 +65,7 @@
:y (- (:y bounding-box) 5)
:font-size 10
:fill line-color
- :stroke "white"
+ :stroke "var(--color-white)"
:stroke-width 0.1}
(str/format "%s - (%s, %s)" (str/slice (str (:id shape)) 0 8) (fixed (:x bounding-box)) (fixed (:y bounding-box)))]
diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs
index 6f7504222a..fe79dd29e0 100644
--- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs
@@ -6,8 +6,9 @@
(ns app.main.ui.workspace.shapes.frame
(:require
- [app.common.geom.shapes :as gsh]
+ [app.common.data :as d]
[app.common.pages :as cp]
+ [app.main.ui.hooks :as hooks]
[app.main.ui.shapes.frame :as frame]
[app.main.ui.shapes.shape :refer [shape-container]]
[app.main.ui.shapes.text.fontfaces :as ff]
@@ -34,72 +35,88 @@
(= new-thumbnail? old-thumbnail?)
(= new-children old-children))))
-(mf/defc thumbnail
+(mf/defc frame-placeholder
{::mf/wrap-props false}
[props]
- (let [shape (obj/get props "shape")]
- (when (:thumbnail shape)
- [:image.frame-thumbnail
- {:id (str "thumbnail-" (:id shape))
- :xlinkHref (:thumbnail shape)
- :x (:x shape)
- :y (:y shape)
- :width (:width shape)
- :height (:height shape)
- ;; DEBUG
- ;; :style {:filter "sepia(1)"}
- }])))
+ (let [{:keys [x y width height fill-color] :as shape} (obj/get props "shape")]
+ (if (some? (:thumbnail shape))
+ [:& frame/frame-thumbnail {:shape shape}]
+ [:rect {:x x :y y :width width :height height :style {:fill (or fill-color "var(--color-white)")}}])))
-;; This custom deffered don't deffer rendering when ghost rendering is
-;; used.
(defn custom-deferred
[component]
(mf/fnc deferred
{::mf/wrap-props false}
[props]
- (let [tmp (mf/useState false)
+ (let [shape (-> (obj/get props "shape")
+ (select-keys [:x :y :width :height])
+ (hooks/use-equal-memo))
+
+ tmp (mf/useState false)
^boolean render? (aget tmp 0)
- ^js set-render (aget tmp 1)]
- (mf/use-layout-effect
+ ^js set-render (aget tmp 1)
+ prev-shape-ref (mf/use-ref shape)]
+
+ (mf/use-effect
+ (mf/deps shape)
(fn []
- (let [sem (ts/schedule-on-idle #(set-render true))]
- #(rx/dispose! sem))))
- (when render? (mf/create-element component props)))))
+ (mf/set-ref-val! prev-shape-ref shape)
+ (set-render false)))
+
+ (mf/use-effect
+ (mf/deps render? shape)
+ (fn []
+ (when-not render?
+ (let [sem (ts/schedule-on-idle #(set-render true))]
+ #(rx/dispose! sem)))))
+
+ (if (and render? (= shape (mf/ref-val prev-shape-ref)))
+ (mf/create-element component props)
+ (mf/create-element frame-placeholder props)))))
+
+;; Draw the frame proper as a deferred component
+(defn deferred-frame-shape-factory
+ [shape-wrapper]
+ (let [frame-shape (frame/frame-shape shape-wrapper)]
+ (mf/fnc defered-frame-wrapper
+ {::mf/wrap-props false
+ ::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "childs"]))
+ custom-deferred]}
+ [props]
+ (let [shape (unchecked-get props "shape")
+ childs (unchecked-get props "childs")]
+ [:& frame-shape {:shape shape
+ :childs childs}]))))
(defn frame-wrapper-factory
[shape-wrapper]
- (let [frame-shape (frame/frame-shape shape-wrapper)]
+ (let [deferred-frame-shape (deferred-frame-shape-factory shape-wrapper)]
(mf/fnc frame-wrapper
- {::mf/wrap [#(mf/memo' % check-frame-props) custom-deferred]
+ {::mf/wrap [#(mf/memo' % check-frame-props)]
::mf/wrap-props false}
[props]
- (let [shape (unchecked-get props "shape")
- objects (unchecked-get props "objects")
- thumbnail? (unchecked-get props "thumbnail?")
- shape (gsh/transform-shape shape)
- children (mapv #(get objects %) (:shapes shape))
+ (when-let [shape (unchecked-get props "shape")]
+ (let [objects (unchecked-get props "objects")
+ thumbnail? (unchecked-get props "thumbnail?")
- all-children (cp/get-children-objects (:id shape) objects)
+ children
+ (-> (mapv (d/getf objects) (:shapes shape))
+ (hooks/use-equal-memo))
- rendered? (mf/use-state false)
+ all-children
+ (-> (cp/get-children-objects (:id shape) objects)
+ (hooks/use-equal-memo))
- show-thumbnail? (and thumbnail? (some? (:thumbnail shape)))
+ show-thumbnail?
+ (and thumbnail? (some? (:thumbnail shape)))]
- on-dom
- (mf/use-callback
- (fn [node]
- (ts/schedule-on-idle #(reset! rendered? (some? node)))))]
-
- (when (some? shape)
[:g.frame-wrapper {:display (when (:hidden shape) "none")}
-
- (when-not show-thumbnail?
- [:> shape-container {:shape shape :ref on-dom}
- [:& ff/fontfaces-style {:shapes all-children}]
- [:& frame-shape {:shape shape
- :childs children}]])
-
- (when (or (not @rendered?) show-thumbnail?)
- [:& thumbnail {:shape shape}])])))))
+ [:> shape-container {:shape shape}
+ [:& ff/fontfaces-style {:shapes all-children}]
+ (if show-thumbnail?
+ [:& frame/frame-thumbnail {:shape shape}]
+ [:& deferred-frame-shape
+ {:shape shape
+ :childs children}])]])))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs
index 745523a1bf..18c1237aec 100644
--- a/frontend/src/app/main/ui/workspace/shapes/group.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs
@@ -27,18 +27,15 @@
[shape-wrapper]
(let [group-shape (group/group-shape shape-wrapper)]
(mf/fnc group-wrapper
- {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
- frame (unchecked-get props "frame")
-
- childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape) {:with-modifiers? true}))
+ childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
childs (mf/deref childs-ref)]
[:> shape-container {:shape shape}
[:& group-shape
- {:frame frame
- :shape shape
+ {:shape shape
:childs childs}]]))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs
index df228663ca..1f5ec07ca0 100644
--- a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs
@@ -10,11 +10,11 @@
[okulary.core :as l]
[rumext.alpha :as mf]))
-(def primary-color "#1FDEA7")
-(def secondary-color "#DB00FF")
-(def black-color "#000000")
-(def white-color "#FFFFFF")
-(def gray-color "#B1B2B5")
+(def primary-color "var(--color-select)")
+(def secondary-color "var(--color-distance)")
+(def black-color "var(--color-black)")
+(def white-color "var(--color-white)")
+(def gray-color "var(--color-gray-20)")
(def current-edit-path-ref
(let [selfn (fn [local]
diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs
index dda2e33b3f..22919126a5 100644
--- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs
@@ -15,12 +15,10 @@
[shape-wrapper]
(let [svg-raw-shape (svg-raw/svg-raw-shape shape-wrapper)]
(mf/fnc svg-raw-wrapper
- {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))]
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))]
::mf/wrap-props false}
[props]
(let [shape (unchecked-get props "shape")
- frame (unchecked-get props "frame")
-
childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape)))
childs (mf/deref childs-ref)]
@@ -28,11 +26,9 @@
(if (or (= (get-in shape [:content :tag]) :svg)
(and (contains? shape :svg-attrs) (map? (:content shape))))
[:> shape-container {:shape shape}
- [:& svg-raw-shape {:frame frame
- :shape shape
+ [:& svg-raw-shape {:shape shape
:childs childs}]]
- [:& svg-raw-shape {:frame frame
- :shape shape
+ [:& svg-raw-shape {:shape shape
:childs childs}])))))
diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs
index 704920fba6..09fc366988 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs
@@ -110,7 +110,7 @@
[:> shape-container {:shape shape}
;; We keep hidden the shape when we're editing so it keeps track of the size
- ;; and updates the selrect acordingly
+ ;; and updates the selrect accordingly
[:g.text-shape {:opacity (when edition? 0)
:pointer-events "none"}
diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
index d00ea37bd6..fad6579384 100644
--- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
+++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs
@@ -7,7 +7,6 @@
(ns app.main.ui.workspace.shapes.text.editor
(:require
["draft-js" :as draft]
- [app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.text :as txt]
[app.main.data.workspace :as dw]
@@ -72,20 +71,18 @@
(def empty-editor-state
(ted/create-editor-state nil default-decorator))
-(defn get-content-changes
- [old-state state]
- (let [old-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js old-state)))
- :keywordize-keys false)
- new-blocks (js->clj (.toJS (.getBlockMap (.getCurrentContent ^js state)))
+(defn get-blocks-to-setup [block-changes]
+ (->> block-changes
+ (filter (fn [[_ v]]
+ (nil? (:old v))))
+ (mapv first)))
- :keywordize-keys false)]
- (->> old-blocks
- (d/mapm
- (fn [bkey bstate]
- {:old (get bstate "text")
- :new (get-in new-blocks [bkey "text"])}))
- (filter #(contains? new-blocks (first %)))
- (into {}))))
+(defn get-blocks-to-add-styles
+ [block-changes]
+ (->> block-changes
+ (filter (fn [[_ v]]
+ (and (not= (:old v) (:new v)) (= (:old v) ""))))
+ (mapv first)))
(mf/defc text-shape-edit-html
{::mf/wrap [mf/memo]
@@ -98,7 +95,7 @@
state (get state-map id empty-editor-state)
self-ref (mf/use-ref)
- blured (mf/use-var false)
+ blurred (mf/use-var false)
on-key-up
(fn [event]
@@ -123,13 +120,13 @@
(fn [event]
(dom/stop-propagation event)
(dom/prevent-default event)
- (reset! blured true)))
+ (reset! blurred true)))
on-focus
(mf/use-callback
(mf/deps shape state)
(fn [_]
- (reset! blured false)))
+ (reset! blurred false)))
prev-value (mf/use-ref state)
@@ -143,25 +140,35 @@
(fn [state]
(let [old-state (mf/ref-val prev-value)]
(if (and (some? state) (some? old-state))
- (let [block-states (get-content-changes old-state state)
-
- block-to-add-styles
- (->> block-states
- (filter
- (fn [[_ v]]
- (and (not= (:old v) (:new v))
- (= (:old v) ""))))
- (mapv first))]
- (ted/apply-block-styles-to-content state block-to-add-styles))
+ (let [block-changes (ted/get-content-changes old-state state)
+ prev-data (ted/get-editor-current-inline-styles old-state)
+ block-to-setup (get-blocks-to-setup block-changes)
+ block-to-add-styles (get-blocks-to-add-styles block-changes)]
+ (-> state
+ (ted/setup-block-styles block-to-setup prev-data)
+ (ted/apply-block-styles-to-content block-to-add-styles)))
state))))
on-change
(mf/use-callback
(fn [val]
- (let [val (handle-change val)
- val (if (true? @blured)
- (ted/add-editor-blur-selection val)
- (ted/remove-editor-blur-selection val))]
+ (let [prev-val (mf/ref-val prev-value)
+ styleOverride (ted/get-style-override prev-val)
+
+ ;; If the content and the selection are the same we keep the style override
+ keep-style? (and (some? styleOverride)
+ (ted/content-equals prev-val val)
+ (ted/selection-equals prev-val val))
+
+ val (cond-> (handle-change val)
+ @blurred
+ (ted/add-editor-blur-selection)
+
+ (not @blurred)
+ (ted/remove-editor-blur-selection)
+
+ keep-style?
+ (ted/set-style-override styleOverride))]
(st/emit! (dwt/update-editor-state shape val)))))
on-editor
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index 16889f7590..13b6506961 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -20,8 +20,8 @@
[app.main.data.workspace.state-helpers :as wsh]
[app.main.data.workspace.texts :as dwt]
[app.main.data.workspace.undo :as dwu]
- [app.main.exports :as exports]
[app.main.refs :as refs]
+ [app.main.render :refer [component-svg]]
[app.main.store :as st]
[app.main.ui.components.color-bullet :as bc]
[app.main.ui.components.context-menu :refer [context-menu]]
@@ -273,12 +273,13 @@
:selected (contains? selected-components (:id component))
:grid-cell @listing-thumbs?
:enum-item (not @listing-thumbs?))
+ :id (str "component-shape-id-" (:id component))
:draggable true
:on-click #(on-asset-click % (:id component) nil)
:on-context-menu (on-context-menu (:id component))
:on-drag-start (partial on-drag-start component)}
- [:& exports/component-svg {:group (get-in component [:objects (:id component)])
- :objects (:objects component)}]
+ [:& component-svg {:group (get-in component [:objects (:id component)])
+ :objects (:objects component)}]
(let [renaming? (= renaming (:id component))]
[:& editable-label
{:class-name (dom/classnames
@@ -796,7 +797,7 @@
apply-color
(fn [_ event]
(let [ids (wsh/lookup-selected @st/state)]
- (if (kbd/shift? event)
+ (if (kbd/alt? event)
(st/emit! (dc/change-stroke ids color))
(st/emit! (dc/change-fill ids color)))))
@@ -1087,7 +1088,7 @@
(mf/defc typographies-group
[{:keys [file-id prefix groups open-groups file local? selected-typographies local
- editting-id on-asset-click handle-change apply-typography
+ editing-id on-asset-click handle-change apply-typography
on-rename-group on-ungroup on-context-menu]}]
(let [group-open? (get open-groups prefix true)]
@@ -1113,7 +1114,7 @@
:selected? (contains? selected-typographies (:id typography))
:on-click #(on-asset-click % (:id typography)
(partial apply-typography typography))
- :editting? (= editting-id (:id typography))
+ :editing? (= editing-id (:id typography))
:focus-name? (= (:rename-typography local) (:id typography))}])])
(for [[path-item content] groups]
@@ -1125,7 +1126,7 @@
:file file
:local? local?
:selected-typographies selected-typographies
- :editting-id editting-id
+ :editing-id editing-id
:local local
:on-asset-click on-asset-click
:handle-change handle-change
@@ -1172,7 +1173,7 @@
attrs (merge
{:typography-ref-file file-id
:typography-ref-id (:id typography)}
- (d/without-keys typography [:id :name]))]
+ (dissoc typography :id :name))]
(run! #(st/emit! (dwt/update-text-attrs {:id % :editor (get-in local [:editors %]) :attrs attrs}))
ids)))
@@ -1272,7 +1273,7 @@
(dwl/sync-file file-id file-id)
(dwu/commit-undo-transaction)))))
- editting-id (or (:rename-typography local) (:edit-typography local))]
+ editing-id (or (:rename-typography local) (:edit-typography local))]
(mf/use-effect
(mf/deps local)
@@ -1301,7 +1302,7 @@
:file file
:local? local?
:selected-typographies selected-typographies
- :editting-id editting-id
+ :editing-id editing-id
:local local
:on-asset-click (partial on-asset-click groups)
:handle-change handle-change
@@ -1402,15 +1403,12 @@
reverse-sort? (mf/use-state false)
listing-thumbs? (mf/use-state true)
- selected-assets (mf/use-state {:components #{}
- :graphics #{}
- :colors #{}
- :typographies #{}})
+ selected-assets (mf/deref refs/selected-assets)
- selected-count (+ (count (:components @selected-assets))
- (count (:graphics @selected-assets))
- (count (:colors @selected-assets))
- (count (:typographies @selected-assets)))
+ selected-count (+ (count (:components selected-assets))
+ (count (:graphics selected-assets))
+ (count (:colors selected-assets))
+ (count (:typographies selected-assets)))
toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?)))
@@ -1441,92 +1439,81 @@
(fn [_]
(swap! listing-thumbs? not)))
- toggle-selected-asset
- (mf/use-callback
- (mf/deps @selected-assets)
- (fn [asset-type asset-id]
- (swap! selected-assets update asset-type
- (fn [selected]
- (if (contains? selected asset-id)
- (disj selected asset-id)
- (conj selected asset-id))))))
-
extend-selected-assets
(mf/use-callback
- (mf/deps @selected-assets)
- (fn [asset-type asset-groups asset-id]
- (letfn [(flatten-groups
- [groups]
- (concat
- (get groups "" [])
- (reduce concat
- []
- (->> (filter #(seq (first %)) groups)
- (map second)
- (map flatten-groups)))))]
- (swap! selected-assets update asset-type
- (fn [selected]
- (let [all-assets (flatten-groups asset-groups)
- clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id))
- selected-idx (->> selected
- (map (fn [id]
- (d/index-of-pred all-assets
- #(= (:id %) id)))))
- min-idx (apply min (conj selected-idx clicked-idx))
- max-idx (apply max (conj selected-idx clicked-idx))]
+ (mf/deps selected-assets)
+ (fn [asset-type asset-groups asset-id]
+ (letfn [(flatten-groups
+ [groups]
+ (concat
+ (get groups "" [])
+ (reduce concat
+ (into []
+ (->> (filter #(seq (first %)) groups)
+ (map second)
+ (mapcat flatten-groups))))))]
+ (let [selected-assets-type (get selected-assets asset-type)
+ count-assets (count selected-assets-type)]
+ (if (<= count-assets 0)
+ (st/emit! (dw/select-single-asset asset-id asset-type))
+ (let [all-assets (flatten-groups asset-groups)
+ clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id))
+ components (get selected-assets asset-type)
- (->> all-assets
- d/enumerate
- (filter #(<= min-idx (first %) max-idx))
- (map #(-> % second :id))
- set)))))))
+ first-idx (first (sort (map (fn [asset] (d/index-of-pred all-assets #(= (:id %) asset))) components)))
+ selected-idx (vector first-idx clicked-idx)
+ min-idx (apply min (conj selected-idx clicked-idx))
+ max-idx (apply max (conj selected-idx clicked-idx))
+ values (->> all-assets
+ d/enumerate
+ (filter #(<= min-idx (first %) max-idx))
+ (map #(-> % second :id))
+ set)]
+
+ (st/emit! (dw/select-assets values asset-type))))))))
unselect-all
(mf/use-callback
- (fn []
- (swap! selected-assets {:components #{}
- :graphics #{}
- :colors #{}
- :typographies #{}})))
+ (fn []
+ (st/emit! (dw/unselect-all-assets))))
on-asset-click
(mf/use-callback
- (mf/deps toggle-selected-asset extend-selected-assets)
- (fn [asset-type asset-groups event asset-id default-click]
- (cond
- (kbd/ctrl? event)
- (do
- (dom/stop-propagation event)
- (toggle-selected-asset asset-type asset-id))
+ (mf/deps extend-selected-assets selected-assets)
+ (fn [asset-type asset-groups event asset-id default-click]
+ (cond
+ (kbd/ctrl? event)
+ (do
+ (dom/stop-propagation event)
+ (st/emit! (dw/toggle-selected-assets asset-id asset-type)))
- (kbd/shift? event)
- (do
- (dom/stop-propagation event)
- (extend-selected-assets asset-type asset-groups asset-id))
+ (kbd/shift? event)
+ (do
+ (dom/stop-propagation event)
+ (extend-selected-assets asset-type asset-groups asset-id))
- :else
- (when default-click
- (default-click event)))))
+ :else
+ (when default-click
+ (default-click event)))))
on-assets-delete
(mf/use-callback
- (mf/deps @selected-assets)
- (fn []
- (let [selected-assets @selected-assets]
- (st/emit! (dwu/start-undo-transaction))
- (apply st/emit! (map #(dwl/delete-component {:id %})
- (:components selected-assets)))
- (apply st/emit! (map #(dwl/delete-media {:id %})
- (:graphics selected-assets)))
- (apply st/emit! (map #(dwl/delete-color {:id %})
- (:colors selected-assets)))
- (apply st/emit! (map #(dwl/delete-typography %)
- (:typographies selected-assets)))
- (when (or (d/not-empty? (:components selected-assets))
- (d/not-empty? (:colors selected-assets))
- (d/not-empty? (:typographies selected-assets)))
- (st/emit! (dwl/sync-file (:id file) (:id file))))
- (st/emit! (dwu/commit-undo-transaction)))))]
+ (mf/deps selected-assets)
+ (fn []
+ (st/emit! (dwu/start-undo-transaction))
+ (apply st/emit! (map #(dwl/delete-component {:id %})
+ (:components selected-assets)))
+ (apply st/emit! (map #(dwl/delete-media {:id %})
+ (:graphics selected-assets)))
+ (apply st/emit! (map #(dwl/delete-color {:id %})
+ (:colors selected-assets)))
+ (apply st/emit! (map #(dwl/delete-typography %)
+ (:typographies selected-assets)))
+ (when (or (d/not-empty? (:components selected-assets))
+ (d/not-empty? (:colors selected-assets))
+ (d/not-empty? (:typographies selected-assets)))
+ (st/emit! (dwl/sync-file (:id file) (:id file))))
+ (st/emit! (dwu/commit-undo-transaction))))]
[:div.tool-window {:on-context-menu #(dom/prevent-default %)
:on-click unselect-all}
@@ -1588,7 +1575,7 @@
:open? (open-box? :components)
:open-groups (open-groups :components)
:reverse-sort? @reverse-sort?
- :selected-assets @selected-assets
+ :selected-assets selected-assets
:on-asset-click (partial on-asset-click :components)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
@@ -1601,7 +1588,7 @@
:open? (open-box? :graphics)
:open-groups (open-groups :graphics)
:reverse-sort? @reverse-sort?
- :selected-assets @selected-assets
+ :selected-assets selected-assets
:on-asset-click (partial on-asset-click :graphics)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
@@ -1612,7 +1599,7 @@
:open? (open-box? :colors)
:open-groups (open-groups :colors)
:reverse-sort? @reverse-sort?
- :selected-assets @selected-assets
+ :selected-assets selected-assets
:on-asset-click (partial on-asset-click :colors)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
@@ -1625,7 +1612,7 @@
:open? (open-box? :typographies)
:open-groups (open-groups :typographies)
:reverse-sort? @reverse-sort?
- :selected-assets @selected-assets
+ :selected-assets selected-assets
:on-asset-click (partial on-asset-click :typographies)
:on-assets-delete on-assets-delete
:on-clear-selection unselect-all}])
diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs
index 84afab2d1b..d3db889a0f 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs
@@ -21,7 +21,7 @@
(l/derived :workspace-undo st/state))
(defn get-object
- "Searchs for a shape inside the objects list or inside the undo history"
+ "Searches for a shape inside the objects list or inside the undo history"
[id entries objects]
(let [search-deleted-shape
(fn [id entries]
@@ -144,7 +144,7 @@
maybe-keyword))
(defn select-entry
- "Selects the entry the user will see inside a list of posible entries.
+ "Selects the entry the user will see inside a list of possible entries.
Sometimes the result will be a combination."
[candidates]
(let [;; Group by id and type
diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
index 928a3e9d8d..768ea5f35f 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs
@@ -19,6 +19,7 @@
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.timers :as ts]
+ [beicon.core :as rx]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
@@ -41,10 +42,10 @@
i/mask
i/folder))
:bool (case (:bool-type shape)
- :difference i/boolean-difference
- :exclude i/boolean-exclude
- :intersection i/boolean-intersection
- #_:default i/boolean-union)
+ :difference i/bool-difference
+ :exclude i/bool-exclude
+ :intersection i/bool-intersection
+ #_:default i/bool-union)
:svg-raw i/file-svg
nil))
@@ -69,8 +70,8 @@
(on-stop-edit)
(swap! local assoc :edition false)
(st/emit! (dw/end-rename-shape)
- (when-not (str/empty? name)
- (dw/update-shape (:id shape) {:name name})))))
+ (when-not (str/empty? (str/trim name))
+ (dw/update-shape (:id shape) {:name (str/trim name)})))))
cancel-edit (fn []
(on-stop-edit)
@@ -138,16 +139,16 @@
(fn [event]
(dom/stop-propagation event)
(if (:blocked item)
- (st/emit! (dw/update-shape-flags id {:blocked false}))
- (st/emit! (dw/update-shape-flags id {:blocked true})
+ (st/emit! (dw/update-shape-flags [id] {:blocked false}))
+ (st/emit! (dw/update-shape-flags [id] {:blocked true})
(dw/deselect-shape id))))
toggle-visibility
(fn [event]
(dom/stop-propagation event)
(if (:hidden item)
- (st/emit! (dw/update-shape-flags id {:hidden false}))
- (st/emit! (dw/update-shape-flags id {:hidden true}))))
+ (st/emit! (dw/update-shape-flags [id] {:hidden false}))
+ (st/emit! (dw/update-shape-flags [id] {:hidden true}))))
select-shape
(fn [event]
@@ -205,8 +206,12 @@
(mf/use-effect
(mf/deps selected)
(fn []
- (when (and (= (count selected) 1) selected?)
- (.scrollIntoView (mf/ref-val dref) #js {:block "nearest", :behavior "smooth"}))))
+ (let [subid
+ (when (and (= (count selected) 1) selected?)
+ (ts/schedule-on-idle
+ #(.scrollIntoView (mf/ref-val dref) #js {:block "nearest", :behavior "smooth"})))]
+ #(when (some? subid)
+ (rx/dispose! subid)))))
[:li {:on-context-menu on-context-menu
:ref dref
@@ -270,7 +275,7 @@
[:ul.element-list
[:& hooks/sortable-container {}
(for [[index id] (reverse (d/enumerate (:shapes root)))]
- (let [obj (get objects id)]
+ (when-let [obj (get objects id)]
(if (= (:type obj) :frame)
[:& frame-wrapper
{:item obj
@@ -303,6 +308,7 @@
:bool-type]))
(defn- strip-objects
+ "Remove unnecesary data from objects map"
[objects]
(persistent!
(->> objects
@@ -315,8 +321,11 @@
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/throttle % 200)]}
[props]
- (let [objects (obj/get props "objects")
- objects (strip-objects objects)]
+ (let [objects (-> (obj/get props "objects")
+ (hooks/use-equal-memo))
+ objects (mf/use-memo
+ (mf/deps objects)
+ #(strip-objects objects))]
[:& layers-tree {:objects objects}]))
;; --- Layers Toolbox
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
index e0a62cbff6..0d9f452646 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs
@@ -12,7 +12,7 @@
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
[app.main.ui.context :as ctx]
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
- [app.main.ui.workspace.sidebar.options.menus.booleans :refer [booleans-options]]
+ [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
[app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]]
[app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]]
[app.main.ui.workspace.sidebar.options.page :as page]
@@ -63,7 +63,7 @@
:title (tr "workspace.options.design")}
[:div.element-options
[:& align-options]
- [:& booleans-options]
+ [:& bool-options]
(case (count selected)
0 [:& page/options]
1 [:& shape-options {:shape (first shapes)
@@ -92,7 +92,7 @@
page-id (mf/use-ctx ctx/current-page-id)
file-id (mf/use-ctx ctx/current-file-id)
shapes (mf/deref refs/selected-objects)
- shapes-with-children (mf/deref refs/selected-objects-with-children)]
+ shapes-with-children (mf/deref refs/selected-shapes-with-children)]
[:& options-content {:shapes shapes
:selected selected
:shapes-with-children shapes-with-children
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
index 7b32c969a8..6581b32635 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/align.cljs
@@ -6,8 +6,8 @@
(ns app.main.ui.workspace.sidebar.options.menus.align
(:require
- [app.common.uuid :as uuid]
[app.main.data.workspace :as dw]
+ [app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
@@ -21,71 +21,58 @@
;; don't need to watch objects, only read the value
objects (deref refs/workspace-page-objects)
- disabled (cond
- (empty? selected) true
- (> (count selected) 1) false
- :else
- (= uuid/zero (:frame-id (get objects (first selected)))))
+ disabled (not (dw/can-align? selected objects))
- disabled-distribute (cond
- (empty? selected) true
- (< (count selected) 2) true
- :else false)
-
- on-align-button-clicked
- (fn [axis] (when-not disabled (st/emit! (dw/align-objects axis))))
-
- on-distribute-button-clicked
- (fn [axis] (when-not disabled-distribute (st/emit! (dw/distribute-objects axis))))]
+ disabled-distribute (not(dw/can-distribute? selected))]
[:div.align-options
[:div.align-group
[:div.align-button.tooltip.tooltip-bottom
- {:alt (tr "workspace.align.hleft")
+ {:alt (tr "workspace.align.hleft" (sc/get-tooltip :align-left))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :hleft)}
+ :on-click #(st/emit! (dw/align-objects :hleft))}
i/shape-halign-left]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (tr "workspace.align.hcenter")
+ {:alt (tr "workspace.align.hcenter" (sc/get-tooltip :align-hcenter))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :hcenter)}
+ :on-click #(st/emit! (dw/align-objects :hcenter))}
i/shape-halign-center]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (tr "workspace.align.hright")
+ {:alt (tr "workspace.align.hright" (sc/get-tooltip :align-right))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :hright)}
+ :on-click #(st/emit! (dw/align-objects :hright))}
i/shape-halign-right]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (tr "workspace.align.hdistribute")
+ {:alt (tr "workspace.align.hdistribute" (sc/get-tooltip :h-distribute))
:class (when disabled-distribute "disabled")
- :on-click #(on-distribute-button-clicked :horizontal)}
+ :on-click #(st/emit! (dw/distribute-objects :horizontal))}
i/shape-hdistribute]]
[:div.align-group
- [:div.align-button.tooltip.tooltip-bottom
- {:alt (tr "workspace.align.vtop")
+ [:div.align-button.tooltip.tooltip-bottom-left
+ {:alt (tr "workspace.align.vtop" (sc/get-tooltip :align-top))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :vtop)}
+ :on-click #(st/emit! (dw/align-objects :vtop))}
i/shape-valign-top]
[:div.align-button.tooltip.tooltip-bottom-left
- {:alt (tr "workspace.align.vcenter")
+ {:alt (tr "workspace.align.vcenter" (sc/get-tooltip :align-vcenter))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :vcenter)}
+ :on-click #(st/emit! (dw/align-objects :vcenter))}
i/shape-valign-center]
[:div.align-button.tooltip.tooltip-bottom-left
- {:alt (tr "workspace.align.vbottom")
+ {:alt (tr "workspace.align.vbottom" (sc/get-tooltip :align-bottom))
:class (when disabled "disabled")
- :on-click #(on-align-button-clicked :vbottom)}
+ :on-click #(st/emit! (dw/align-objects :vbottom))}
i/shape-valign-bottom]
[:div.align-button.tooltip.tooltip-bottom-left
- {:alt (tr "workspace.align.vdistribute")
+ {:alt (tr "workspace.align.vdistribute" (sc/get-tooltip :v-distribute))
:class (when disabled-distribute "disabled")
- :on-click #(on-distribute-button-clicked :vertical)}
+ :on-click #(st/emit! (dw/distribute-objects :vertical))}
i/shape-vdistribute]]]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
similarity index 88%
rename from frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
rename to frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
index 8476844471..3b76be0c28 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/booleans.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/bool.cljs
@@ -4,7 +4,7 @@
;;
;; Copyright (c) UXBOX Labs SL
-(ns app.main.ui.workspace.sidebar.options.menus.booleans
+(ns app.main.ui.workspace.sidebar.options.menus.bool
(:require
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
@@ -15,10 +15,10 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
-(mf/defc booleans-options
+(mf/defc bool-options
[]
(let [selected (mf/deref refs/selected-objects)
- selected-with-children (mf/deref refs/selected-objects-with-children)
+ selected-with-children (mf/deref refs/selected-shapes-with-children)
has-invalid-shapes? (->> selected-with-children
(some (comp #{:frame :text} :type)))
@@ -52,37 +52,37 @@
[:div.align-options
[:div.align-group
[:div.align-button.tooltip.tooltip-bottom
- {:alt (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :boolean-union) ")")
+ {:alt (str (tr "workspace.shape.menu.union") " (" (sc/get-tooltip :bool-union) ")")
:class (dom/classnames :disabled disabled-bool-btns
:selected (= head-bool-type :union))
:on-click (set-bool :union)}
- i/boolean-union]
+ i/bool-union]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :boolean-difference) ")")
+ {:alt (str (tr "workspace.shape.menu.difference") " (" (sc/get-tooltip :bool-difference) ")")
:class (dom/classnames :disabled disabled-bool-btns
:selected (= head-bool-type :difference))
:on-click (set-bool :difference)}
- i/boolean-difference]
+ i/bool-difference]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :boolean-intersection) ")")
+ {:alt (str (tr "workspace.shape.menu.intersection") " (" (sc/get-tooltip :bool-intersection) ")")
:class (dom/classnames :disabled disabled-bool-btns
:selected (= head-bool-type :intersection))
:on-click (set-bool :intersection)}
- i/boolean-intersection]
+ i/bool-intersection]
[:div.align-button.tooltip.tooltip-bottom
- {:alt (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :boolean-exclude) ")")
+ {:alt (str (tr "workspace.shape.menu.exclude") " (" (sc/get-tooltip :bool-exclude) ")")
:class (dom/classnames :disabled disabled-bool-btns
:selected (= head-bool-type :exclude))
:on-click (set-bool :exclude)}
- i/boolean-exclude]]
+ i/bool-exclude]]
[:div.align-group
[:div.align-button.tooltip.tooltip-bottom
{:alt (tr "workspace.shape.menu.flatten")
:class (dom/classnames :disabled disabled-flatten)
:on-click (st/emitf (dw/convert-selected-to-path))}
- i/boolean-flatten]]]))
+ i/bool-flatten]]]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
index 024c391d10..8710ff7d52 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs
@@ -10,7 +10,6 @@
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.libraries :as dwl]
- [app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.context-menu :refer [context-menu]]
@@ -33,10 +32,9 @@
show? (some? (:component-id values))
local-library (mf/deref refs/workspace-local-library)
libraries (mf/deref refs/workspace-libraries)
- component (cp/get-component (:component-id values)
- (:component-file values)
- local-library
- libraries)
+ {:keys [component-id component-file]} values
+
+ component (cp/get-component component-id component-file local-library libraries)
on-menu-click (mf/use-callback
(fn [event]
@@ -49,29 +47,21 @@
do-detach-component (st/emitf (dwl/detach-component id))
do-reset-component (st/emitf (dwl/reset-component id))
- do-update-component (st/emitf
- (dwu/start-undo-transaction)
- (dwl/update-component id)
- (dwl/sync-file current-file-id current-file-id)
- (dwu/commit-undo-transaction))
- confirm-update-remote-component (st/emitf
- (dwl/update-component id)
- (dwl/sync-file current-file-id
- (:component-file values))
- (dwl/sync-file (:component-file values)
- (:component-file values)))
- do-update-remote-component (st/emitf (modal/show
- {:type :confirm
- :message ""
- :title (t locale "modals.update-remote-component.message")
- :hint (t locale "modals.update-remote-component.hint")
- :cancel-label (t locale "modals.update-remote-component.cancel")
- :accept-label (t locale "modals.update-remote-component.accept")
- :accept-style :primary
- :on-accept confirm-update-remote-component}))
- do-show-component (st/emitf (dw/go-to-layout :assets))
- do-navigate-component-file (st/emitf (dwl/nav-to-component-file
- (:component-file values)))]
+ do-update-component (st/emitf (dwl/update-component-sync id component-file))
+
+ do-update-remote-component
+ (st/emitf (modal/show
+ {:type :confirm
+ :message ""
+ :title (t locale "modals.update-remote-component.message")
+ :hint (t locale "modals.update-remote-component.hint")
+ :cancel-label (t locale "modals.update-remote-component.cancel")
+ :accept-label (t locale "modals.update-remote-component.accept")
+ :accept-style :primary
+ :on-accept do-update-component}))
+
+ do-show-component (st/emitf (dw/go-to-component component-id))
+ do-navigate-component-file (st/emitf (dwl/nav-to-component-file component-file))]
(when show?
[:div.element-set
[:div.element-set-title
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs
index 0327912f42..8e9fc5a255 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs
@@ -30,24 +30,18 @@
:name (:name shape)
:exports exports}))
-(mf/defc exports-menu
- [{:keys [shape page-id file-id] :as props}]
- (let [exports (:exports shape [])
- loading? (mf/use-state false)
+(defn use-download-export
+ [shape page-id file-id exports]
+ (let [loading? (mf/use-state false)
filename (cond-> (:name shape)
(and (= (count exports) 1)
(not (empty (:suffix (first exports)))))
(str (:suffix (first exports))))
- scale-enabled?
+ on-download-callback
(mf/use-callback
- (fn [export]
- (#{:png :jpeg} (:type export))))
-
- on-download
- (mf/use-callback
- (mf/deps shape)
+ (mf/deps filename shape exports)
(fn [event]
(dom/prevent-default event)
(swap! loading? not)
@@ -59,7 +53,19 @@
(swap! loading? not)
(st/emit! (dm/error (tr "errors.unexpected-error"))))
(fn []
- (swap! loading? not))))))
+ (swap! loading? not))))))]
+ [on-download-callback @loading?]))
+
+(mf/defc exports-menu
+ [{:keys [shape page-id file-id] :as props}]
+ (let [exports (:exports shape [])
+
+ scale-enabled?
+ (mf/use-callback
+ (fn [export]
+ (#{:png :jpeg} (:type export))))
+
+ [on-download loading?] (use-download-export shape page-id file-id exports)
add-export
(mf/use-callback
@@ -143,11 +149,11 @@
i/minus]])
[:div.btn-icon-dark.download-button
- {:on-click (when-not @loading? on-download)
+ {:on-click (when-not loading? on-download)
:class (dom/classnames
- :btn-disabled @loading?)
- :disabled @loading?}
- (if @loading?
+ :btn-disabled loading?)
+ :disabled loading?}
+ (if loading?
(tr "workspace.options.exporting-object")
(tr "workspace.options.export-object"))]])]))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
index e1d0ee4b40..4de4cb4e91 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs
@@ -12,6 +12,7 @@
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.util.color :as uc]
+ [app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
@@ -20,11 +21,15 @@
:fill-opacity
:fill-color-ref-id
:fill-color-ref-file
- :fill-color-gradient])
+ :fill-color-gradient
+ :hide-fill-on-export])
+
+(def fill-attrs-shape
+ (conj fill-attrs :hide-fill-on-export))
(mf/defc fill-menu
{::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]}
- [{:keys [ids type values] :as props}]
+ [{:keys [ids type values disable-remove?] :as props}]
(let [show? (or (not (nil? (:fill-color values)))
(not (nil? (:fill-color-gradient values))))
@@ -39,53 +44,83 @@
:file-id (:fill-color-ref-file values)
:gradient (:fill-color-gradient values)}
+ hide-fill-on-export? (:hide-fill-on-export values false)
+
+ checkbox-ref (mf/use-ref)
+
on-add
(mf/use-callback
- (mf/deps ids)
- (fn [_]
- (st/emit! (dc/change-fill ids {:color cp/default-color
- :opacity 1}))))
+ (mf/deps ids)
+ (fn [_]
+ (st/emit! (dc/change-fill ids {:color cp/default-color
+ :opacity 1}))))
on-delete
(mf/use-callback
- (mf/deps ids)
- (fn [_]
- (st/emit! (dc/change-fill ids (into {} uc/empty-color)))))
+ (mf/deps ids)
+ (fn [_]
+ (st/emit! (dc/change-fill ids (into {} uc/empty-color)))))
on-change
(mf/use-callback
- (mf/deps ids)
- (fn [color]
- (let [remove-multiple (fn [[_ value]] (not= value :multiple))
- color (into {} (filter remove-multiple) color)]
- (st/emit! (dc/change-fill ids color)))))
+ (mf/deps ids)
+ (fn [color]
+ (let [remove-multiple (fn [[_ value]] (not= value :multiple))
+ color (into {} (filter remove-multiple) color)]
+ (st/emit! (dc/change-fill ids color)))))
on-detach
(mf/use-callback
- (mf/deps ids)
- (fn []
- (let [remove-multiple (fn [[_ value]] (not= value :multiple))
- color (-> (into {} (filter remove-multiple) color)
- (assoc :id nil :file-id nil))]
- (st/emit! (dc/change-fill ids color)))))
+ (mf/deps ids)
+ (fn []
+ (let [remove-multiple (fn [[_ value]] (not= value :multiple))
+ color (-> (into {} (filter remove-multiple) color)
+ (assoc :id nil :file-id nil))]
+ (st/emit! (dc/change-fill ids color)))))
- ]
+ on-change-show-fill-on-export
+ (mf/use-callback
+ (mf/deps ids)
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (st/emit! (dc/change-hide-fill-on-export ids (not value))))))]
+
+ (mf/use-layout-effect
+ (mf/deps hide-fill-on-export?)
+ #(let [checkbox (mf/ref-val checkbox-ref)]
+ (when checkbox
+ ;; Note that the "indeterminate" attribute only may be set by code, not as a static attribute.
+ ;; See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox#attr-indeterminate
+ (if (= hide-fill-on-export? :multiple)
+ (dom/set-attribute checkbox "indeterminate" true)
+ (dom/remove-attribute checkbox "indeterminate")))))
(if show?
[:div.element-set
[:div.element-set-title
[:span label]
- [:div.add-page {:on-click on-delete} i/minus]]
+ (when (not disable-remove?)
+ [:div.add-page {:on-click on-delete} i/minus])]
[:div.element-set-content
[:& color-row {:color color
:title (tr "workspace.options.fill")
:on-change on-change
- :on-detach on-detach}]]]
+ :on-detach on-detach}]
+
+ (when (or (= type :frame)
+ (and (= type :multiple) (some? hide-fill-on-export?)))
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id "show-fill-on-export"
+ :ref checkbox-ref
+ :checked (not hide-fill-on-export?)
+ :on-change on-change-show-fill-on-export}]
+
+ [:label {:for "show-fill-on-export"}
+ (tr "workspace.options.show-fill-on-export")]])]]
[:div.element-set
[:div.element-set-title
[:span label]
[:div.add-page {:on-click on-add} i/close]]])))
-
-
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
index 53a14ae5f8..f704ce7152 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs
@@ -73,6 +73,23 @@
:bottom-right (tr "workspace.options.interaction-pos-bottom-right")
:bottom-center (tr "workspace.options.interaction-pos-bottom-center")})
+(defn- animation-type-names
+ [interaction]
+ (cond->
+ {:dissolve (tr "workspace.options.interaction-animation-dissolve")
+ :slide (tr "workspace.options.interaction-animation-slide")}
+
+ (cti/allow-push? (:action-type interaction))
+ (assoc :push (tr "workspace.options.interaction-animation-push"))))
+
+(defn- easing-names
+ []
+ {:linear (tr "workspace.options.interaction-easing-linear")
+ :ease (tr "workspace.options.interaction-easing-ease")
+ :ease-in (tr "workspace.options.interaction-easing-ease-in")
+ :ease-out (tr "workspace.options.interaction-easing-ease-out")
+ :ease-in-out (tr "workspace.options.interaction-easing-ease-in-out")})
+
(def flow-for-rename-ref
(l/derived (l/in [:workspace-local :flow-for-rename]) st/state))
@@ -170,10 +187,13 @@
close-click-outside? (:close-click-outside interaction false)
background-overlay? (:background-overlay interaction false)
preserve-scroll? (:preserve-scroll interaction false)
+ way (-> interaction :animation :way)
+ direction (-> interaction :animation :direction)
extended-open? (mf/use-state false)
ext-delay-ref (mf/use-ref nil)
+ ext-duration-ref (mf/use-ref nil)
select-text
(fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref))))
@@ -237,7 +257,36 @@
change-background-overlay
(fn [event]
(let [value (-> event dom/get-target dom/checked?)]
- (update-interaction index #(cti/set-background-overlay % value))))]
+ (update-interaction index #(cti/set-background-overlay % value))))
+
+ change-animation-type
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-animation-type % value))))
+
+ change-duration
+ (fn [value]
+ (update-interaction index #(cti/set-duration % value)))
+
+ change-easing
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-easing % value))))
+
+ change-way
+ (fn [event]
+ (let [value (-> event dom/get-target dom/get-value d/read-string)]
+ (update-interaction index #(cti/set-way % value))))
+
+ change-direction
+ (fn [value]
+ (update-interaction index #(cti/set-direction % value)))
+
+ change-offset-effect
+ (fn [event]
+ (let [value (-> event dom/get-target dom/checked?)]
+ (update-interaction index #(cti/set-offset-effect % value))))
+ ]
[:*
[:div.element-set-options-group {:class (dom/classnames
@@ -249,7 +298,7 @@
[:div.interactions-summary {:on-click #(swap! extended-open? not)}
[:div.trigger-name (event-type-name interaction)]
[:div.action-summary (action-summary interaction destination)]]
- [:div.elemen-set-actions {:on-click #(remove-interaction index)}
+ [:div.element-set-actions {:on-click #(remove-interaction index)}
[:div.element-set-actions-button i/minus]]
(when @extended-open?
@@ -382,7 +431,99 @@
:checked background-overlay?
:on-change change-background-overlay}]
[:label {:for (str "background-" index)}
- (tr "workspace.options.interaction-background")]]]])])]]))
+ (tr "workspace.options.interaction-background")]]]])
+
+ (when (cti/has-animation? interaction)
+ [:*
+ ; Animation select
+ [:div.interactions-element.separator
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-animation")]
+ [:select.input-select
+ {:value (str (-> interaction :animation :animation-type))
+ :on-change change-animation-type}
+ [:option {:value ""} (tr "workspace.options.interaction-animation-none")]
+ (for [[value name] (animation-type-names interaction)]
+ [:option {:value (str value)} name])]]
+
+ ; Direction
+ (when (cti/has-way? interaction)
+ [:div.interactions-element.interactions-way-buttons
+ [:div.input-radio
+ [:input {:type "radio"
+ :id "way-in"
+ :checked (= :in way)
+ :name "animation-way"
+ :value ":in"
+ :on-change change-way}]
+ [:label {:for "way-in"} (tr "workspace.options.interaction-in")]]
+ [:div.input-radio
+ [:input {:type "radio"
+ :id "way-out"
+ :checked (= :out way)
+ :name "animation-way"
+ :value ":out"
+ :on-change change-way}]
+ [:label {:for "way-out"} (tr "workspace.options.interaction-out")]]])
+
+ ; Direction
+ (when (cti/has-direction? interaction)
+ [:div.interactions-element.interactions-direction-buttons
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= direction :right))
+ :on-click #(change-direction :right)}
+ i/animate-right]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= direction :down))
+ :on-click #(change-direction :down)}
+ i/animate-down]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= direction :left))
+ :on-click #(change-direction :left)}
+ i/animate-left]
+ [:div.element-set-actions-button
+ {:class (dom/classnames :active (= direction :up))
+ :on-click #(change-direction :up)}
+ i/animate-up]])
+
+ ; Duration
+ (when (cti/has-duration? interaction)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-duration")]
+ [:div.input-element {:title (tr "workspace.options.interaction-ms")}
+ [:> numeric-input {:ref ext-duration-ref
+ :on-click (select-text ext-duration-ref)
+ :on-change change-duration
+ :value (-> interaction :animation :duration)
+ :title (tr "workspace.options.interaction-ms")}]
+ [:span.after (tr "workspace.options.interaction-ms")]]])
+
+ ; Easing
+ (when (cti/has-easing? interaction)
+ [:div.interactions-element
+ [:span.element-set-subtitle.wide (tr "workspace.options.interaction-easing")]
+ [:select.input-select
+ {:value (str (-> interaction :animation :easing))
+ :on-change change-easing}
+ (for [[value name] (easing-names)]
+ [:option {:value (str value)} name])]
+ [:div.interactions-easing-icon
+ (case (-> interaction :animation :easing)
+ :linear i/easing-linear
+ :ease i/easing-ease
+ :ease-in i/easing-ease-in
+ :ease-out i/easing-ease-out
+ :ease-in-out i/easing-ease-in-out)]])
+
+ ; Offset effect
+ (when (cti/has-offset-effect? interaction)
+ [:div.interactions-element
+ [:div.input-checkbox
+ [:input {:type "checkbox"
+ :id (str "offset-effect-" index)
+ :checked (-> interaction :animation :offset-effect)
+ :on-change change-offset-effect}]
+ [:label {:for (str "offset-effect-" index)}
+ (tr "workspace.options.interaction-offset-effect")]]])])])]]))
(mf/defc interactions-menu
[{:keys [shape] :as props}]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
index fc87a2b3d3..19978d1649 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
@@ -199,13 +199,15 @@
:placeholder "--"
:on-click select-all
:on-change on-pos-x-change
- :value (attr->string :x values)}]]
+ :value (attr->string :x values)
+ :precision 2}]]
[:div.input-element.Yaxis {:title (tr "workspace.options.y")}
[:> numeric-input {:no-validate true
:placeholder "--"
:on-click select-all
:on-change on-pos-y-change
- :value (attr->string :y values)}]]])
+ :value (attr->string :y values)
+ :precision 2}]]])
;; ROTATION
(when (options :rotation)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
index c03706b7e0..f7e126c705 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.menus.shadow
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.workspace.changes :as dch]
@@ -25,7 +26,7 @@
(let [id (uuid/next)]
{:id id
:style :drop-shadow
- :color {:color "#000000" :opacity 0.2}
+ :color {:color clr/black :opacity 0.2}
:offset-x 4
:offset-y 4
:blur 4
@@ -77,7 +78,7 @@
update-color
(fn [index]
(fn [color opacity]
- (let [color (d/without-keys color [:id :file-id :gradient])]
+ (let [color (dissoc color :id :file-id :gradient)]
(st/emit! (dch/update-shapes
ids
#(-> %
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
index ee9560e624..bfd6948091 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs
@@ -6,13 +6,14 @@
(ns app.main.ui.workspace.sidebar.options.menus.stroke
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
- [app.common.math :as math]
[app.common.pages.spec :as spec]
[app.main.data.workspace.changes :as dch]
[app.main.data.workspace.colors :as dc]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
+ [app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
[app.util.dom :as dom]
@@ -35,9 +36,7 @@
(defn- width->string [width]
(if (= width :multiple)
""
- (str (-> width
- (d/coalesce 1)
- (math/round)))))
+ (str (or width 1))))
(defn- enum->string [value]
(if (= value :multiple)
@@ -122,12 +121,9 @@
(st/emit! (dch/update-shapes ids #(assoc % :stroke-alignment value))))))
on-stroke-width-change
- (fn [event]
- (let [value (-> (dom/get-target event)
- (dom/get-value)
- (d/parse-integer 0))]
- (when-not (str/empty? value)
- (st/emit! (dch/update-shapes ids #(assoc % :stroke-width value))))))
+ (fn [value]
+ (when-not (str/empty? value)
+ (st/emit! (dch/update-shapes ids #(assoc % :stroke-width value)))))
update-cap-attr
(fn [& kvs]
@@ -181,7 +177,7 @@
(fn [_]
(st/emit! (dch/update-shapes ids #(assoc %
:stroke-style :solid
- :stroke-color "#000000"
+ :stroke-color clr/black
:stroke-opacity 1
:stroke-width 1))))
@@ -207,11 +203,13 @@
[:div.input-element
{:class (dom/classnames :pixels (not= (:stroke-width values) :multiple))
:title (tr "workspace.options.stroke-width")}
- [:input.input-text {:type "number"
- :min "0"
- :value (-> (:stroke-width values) width->string)
- :placeholder (tr "settings.multiple")
- :on-change on-stroke-width-change}]]
+
+ [:> numeric-input
+ {:min 0
+ :value (-> (:stroke-width values) width->string)
+ :precision 2
+ :placeholder (tr "settings.multiple")
+ :on-change on-stroke-width-change}]]
[:select#style.input-select {:value (enum->string (:stroke-alignment values))
:on-change on-stroke-alignment-change}
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
index b77caab13d..24b9718c15 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs
@@ -271,14 +271,14 @@
on-convert-to-typography
(fn [_]
- (let [setted-values (-> (d/without-nils values)
- (select-keys
- (d/concat-vec text-font-attrs
- text-spacing-attrs
- text-transform-attrs)))
- typography (merge txt/default-typography setted-values)
- typography (generate-typography-name typography)
- id (uuid/next)]
+ (let [set-values (-> (d/without-nils values)
+ (select-keys
+ (d/concat-vec text-font-attrs
+ text-spacing-attrs
+ text-transform-attrs)))
+ typography (merge txt/default-typography set-values)
+ typography (generate-typography-name typography)
+ id (uuid/next)]
(st/emit! (dwl/add-typography (assoc typography :id id) false))
(run! #(emit-update! % {:typography-ref-id id
:typography-ref-file file-id}) ids)))
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
index b51955e962..c39f715994 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs
@@ -15,6 +15,7 @@
[app.main.fonts :as fonts]
[app.main.store :as st]
[app.main.ui.components.editable-select :refer [editable-select]]
+ [app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.common :refer [advanced-options]]
[app.util.dom :as dom]
@@ -22,6 +23,7 @@
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.router :as rt]
+ [app.util.strings :as ust]
[app.util.timers :as tm]
[cuerdas.core :as str]
[goog.events :as events]
@@ -30,7 +32,7 @@
(defn- attr->string [value]
(if (= value :multiple)
""
- (str value)))
+ (ust/format-precision value 2)))
(defn- get-next-font
[{:keys [id] :as current} fonts]
@@ -350,20 +352,19 @@
letter-spacing (or letter-spacing "0")
handle-change
- (fn [event attr]
- (let [new-spacing (dom/get-target-val event)]
- (on-change {attr new-spacing})))]
+ (fn [value attr]
+ (on-change {attr (str value)}))]
[:div.spacing-options
[:div.input-icon
[:span.icon-before.tooltip.tooltip-bottom
{:alt (tr "workspace.options.text-options.line-height")}
i/line-height]
- [:input.input-text
- {:type "number"
- :step "0.1"
- :min "0"
- :max "200"
+ [:> numeric-input
+ {:min -200
+ :max 200
+ :step 0.1
+ :precision 2
:value (attr->string line-height)
:placeholder (tr "settings.multiple")
:on-change #(handle-change % :line-height)
@@ -373,11 +374,11 @@
[:span.icon-before.tooltip.tooltip-bottom
{:alt (tr "workspace.options.text-options.letter-spacing")}
i/letter-spacing]
- [:input.input-text
- {:type "number"
- :step "0.1"
- :min "0"
- :max "200"
+ [:> numeric-input
+ {:min -200
+ :max 200
+ :step 0.1
+ :precision 2
:value (attr->string letter-spacing)
:placeholder (tr "settings.multiple")
:on-change #(handle-change % :letter-spacing)
@@ -433,8 +434,8 @@
;; In summary, this need to a good UX/UI/IMPL rework.
(mf/defc typography-entry
- [{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editting? focus-name? file]}]
- (let [open? (mf/use-state editting?)
+ [{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editing? focus-name? file]}]
+ (let [open? (mf/use-state editing?)
hover-detach (mf/use-state false)
name-input-ref (mf/use-ref)
@@ -458,10 +459,10 @@
(mf/set-ref-val! name-ref (dom/get-target-val event))))]
(mf/use-effect
- (mf/deps editting?)
+ (mf/deps editing?)
(fn []
- (when editting?
- (reset! open? editting?))))
+ (when editing?
+ (reset! open? editing?))))
(mf/use-effect
(mf/deps focus-name?)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs
index 42115b0b2b..a031498b76 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs
@@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.options.page
"Page options menu entries."
(:require
+ [app.common.colors :as clr]
[app.main.data.workspace :as dw]
[app.main.data.workspace.undo :as dwu]
[app.main.refs :as refs]
@@ -38,7 +39,7 @@
[:& color-row {:disable-gradient true
:disable-opacity true
:title (tr "workspace.options.canvas-background")
- :color {:color (get options :background "#E8E9EA")
+ :color {:color (get options :background clr/canvas)
:opacity 1}
:on-change on-change
:on-open on-open
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
index 55435c4e77..f6965a83e0 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs
@@ -14,7 +14,7 @@
[app.main.ui.components.numeric-input :refer [numeric-input]]
[app.main.ui.icons :as i]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
- [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
+ [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]]
[app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]]
[app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]]
[app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]]
@@ -200,6 +200,11 @@
:width 1368
:height 912}
+ {:name "ReMarkable"}
+ {:name "Remarkable 2"
+ :width 840
+ :height 1120}
+
{:name "WEB"}
{:name "Web 1280"
:width 1280
@@ -280,13 +285,13 @@
{:name "Twitter post"
:width 1024
:height 512}
- {:name "Youtube profile"
+ {:name "YouTube profile"
:width 800
:height 800}
- {:name "Youtube banner"
+ {:name "YouTube banner"
:width 2560
:height 1440}
- {:name "Youtube thumb"
+ {:name "YouTube thumb"
:width 1280
:height 720}
])
@@ -304,7 +309,7 @@
:values layer-values}]
[:& fill-menu {:ids ids
:type type
- :values (select-keys shape fill-attrs)}]
+ :values (select-keys shape fill-attrs-shape)}]
[:& stroke-menu {:ids ids
:type type
:values stroke-values}]
@@ -313,4 +318,3 @@
[:& blur-menu {:ids ids
:values (select-keys shape [:blur])}]
[:& frame-grid {:shape shape}]]))
-
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
index 5358238e21..a0d695221b 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs
@@ -9,6 +9,7 @@
[app.common.attrs :as attrs]
[app.common.data :as d]
[app.common.text :as txt]
+ [app.main.ui.hooks :as hooks]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
@@ -153,7 +154,7 @@
(defn empty-map [keys]
(into {} (map #(hash-map % nil)) keys))
-(defn get-attrs
+(defn get-attrs*
"Given a `type` of options that we want to extract and the shapes to extract them from
returns a list of tuples [id, values] with the extracted properties for the shapes that
applies (some of them ignore some attributes)"
@@ -182,28 +183,58 @@
(select-keys txt/default-text-attrs attrs)
(attrs/get-attrs-multi (txt/node-seq content) attrs))))]
:children (let [children (->> (:shapes shape []) (map #(get objects %)))
- [new-ids new-values] (get-attrs children objects attr-type)]
+ [new-ids new-values] (get-attrs* children objects attr-type)]
[(d/concat-vec ids new-ids) (merge-attrs values new-values)])
[])))]
(reduce extract-attrs [[] []] shapes)))
+(def get-attrs (memoize get-attrs*))
+
+(defn basic-shape [_ shape]
+ (cond-> shape
+ :always
+ (dissoc :selrect :points :x :y :width :height :transform :transform-inverse :rotation :svg-transform :svg-viewbox :thumbnail)
+
+ (= (:type shape) :path)
+ (dissoc :content)))
+
(mf/defc options
- {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "shapes-with-children"]))]
+ {::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "shapes-with-children"]))]
::mf/wrap-props false}
[props]
(let [shapes (unchecked-get props "shapes")
shapes-with-children (unchecked-get props "shapes-with-children")
objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v))))
+ ;; Selrect/points only used for measures and it's the one that changes the most. We separate it
+ ;; so we can memoize it
+ objects-no-measures (->> objects (d/mapm basic-shape))
+ objects-no-measures (hooks/use-equal-memo objects-no-measures)
+
type :multiple
+
[measure-ids measure-values] (get-attrs shapes objects :measure)
- [layer-ids layer-values] (get-attrs shapes objects :layer)
- [constraint-ids constraint-values] (get-attrs shapes objects :constraint)
- [fill-ids fill-values] (get-attrs shapes objects :fill)
- [shadow-ids shadow-values] (get-attrs shapes objects :shadow)
- [blur-ids blur-values] (get-attrs shapes objects :blur)
- [stroke-ids stroke-values] (get-attrs shapes objects :stroke)
- [text-ids text-values] (get-attrs shapes objects :text)]
+
+ [layer-ids layer-values
+ constraint-ids constraint-values
+ fill-ids fill-values
+ shadow-ids shadow-values
+ blur-ids blur-values
+ stroke-ids stroke-values
+ text-ids text-values]
+ (mf/use-memo
+ (mf/deps objects-no-measures)
+ (fn []
+ (into
+ []
+ (mapcat identity)
+ [(get-attrs shapes objects-no-measures :layer)
+ (get-attrs shapes objects-no-measures :constraint)
+ (get-attrs shapes objects-no-measures :fill)
+ (get-attrs shapes objects-no-measures :shadow)
+ (get-attrs shapes objects-no-measures :blur)
+ (get-attrs shapes objects-no-measures :stroke)
+ (get-attrs shapes objects-no-measures :text)])))]
[:div.options
(when-not (empty? measure-ids)
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
index 5283cd8114..ded7837f1c 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.svg-raw
(:require
+ [app.common.colors :as clr]
[app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]]
[app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]]
[app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]]
@@ -64,7 +65,7 @@
(get-in shape [:content :attrs :style :stroke]))
(parse-color))
- stroke-color (:color color "#000000")
+ stroke-color (:color color clr/black)
stroke-opacity (:opacity color 1)
stroke-style (-> (or (get-in shape [:content :attrs :stroke-style])
(get-in shape [:content :attrs :style :stroke-style])
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
index a8eccfa4d5..804703cd61 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.sidebar.options.shapes.text
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.main.data.workspace.texts :as dwt]
[app.main.refs :as refs]
@@ -36,6 +37,8 @@
fill-values (d/update-in-when fill-values [:fill-color-gradient :type] keyword)
fill-values (cond-> fill-values
+ (not (contains? fill-values :fill-color)) (assoc :fill-color clr/black)
+ (not (contains? fill-values :fill-opacity)) (assoc :fill-opacity 1)
;; Keep for backwards compatibility
(:fill fill-values) (assoc :fill-color (:fill fill-values))
(:opacity fill-values) (assoc :fill-opacity (:fill fill-values)))
@@ -73,7 +76,8 @@
[:& fill-menu
{:ids ids
:type type
- :values fill-values}]
+ :values fill-values
+ :disable-remove? true}]
[:& shadow-menu
{:ids ids
diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
index 5e77a90040..d2a65a52b5 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
@@ -18,6 +18,7 @@
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
+ [cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
@@ -66,8 +67,9 @@
(mf/use-callback
(fn [event]
(let [target (dom/event->target event)
- name (dom/get-value target)]
- (st/emit! (dw/rename-page id name))
+ name (str/trim (dom/get-value target))]
+ (when-not (str/empty? name)
+ (st/emit! (dw/rename-page id name)))
(swap! local assoc :edition false))))
on-key-down
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index f4dd216f65..bb1eb36056 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport
(:require
+ [app.common.colors :as clr]
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.main.refs :as refs]
@@ -32,6 +33,7 @@
[app.main.ui.workspace.viewport.utils :as utils]
[app.main.ui.workspace.viewport.widgets :as widgets]
[beicon.core :as rx]
+ [debug :refer [debug?]]
[rumext.alpha :as mf]))
;; --- Viewport
@@ -42,7 +44,6 @@
;; that the new parameter is sent
{:keys [edit-path
edition
- modifiers
options-mode
panning
picking-color?
@@ -60,12 +61,12 @@
;; DEREFS
drawing (mf/deref refs/workspace-drawing)
options (mf/deref refs/workspace-page-options)
- objects (mf/deref refs/workspace-page-objects)
- object-modifiers (mf/deref refs/workspace-modifiers)
- objects (mf/use-memo
- (mf/deps objects object-modifiers)
- #(gsh/merge-modifiers objects object-modifiers))
- background (get options :background "#E8E9EA")
+ base-objects (mf/deref refs/workspace-page-objects)
+ modifiers (mf/deref refs/workspace-modifiers)
+ objects-modified (mf/use-memo
+ (mf/deps base-objects modifiers)
+ #(gsh/merge-modifiers base-objects modifiers))
+ background (get options :background clr/canvas)
;; STATE
alt? (mf/use-state false)
@@ -80,7 +81,6 @@
;; REFS
viewport-ref (mf/use-ref nil)
- render-ref (mf/use-ref nil)
;; VARS
disable-paste (mf/use-var false)
@@ -93,25 +93,21 @@
drawing-tool (:tool drawing)
drawing-obj (:object drawing)
- selected-shapes (into []
- (comp (map #(get objects %))
- (filter some?))
- selected)
+ selected-shapes (into [] (keep (d/getf objects-modified)) selected)
selected-frames (into #{} (map :frame-id) selected-shapes)
;; Only when we have all the selected shapes in one frame
- selected-frame (when (= (count selected-frames) 1) (get objects (first selected-frames)))
-
+ selected-frame (when (= (count selected-frames) 1) (get base-objects (first selected-frames)))
create-comment? (= :comments drawing-tool)
drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode])))
(and (some? drawing-obj) (= :path (:type drawing-obj))))
- node-editing? (and edition (not= :text (get-in objects [edition :type])))
- text-editing? (and edition (= :text (get-in objects [edition :type])))
+ node-editing? (and edition (not= :text (get-in base-objects [edition :type])))
+ text-editing? (and edition (= :text (get-in base-objects [edition :type])))
on-click (actions/on-click hover selected edition drawing-path? drawing-tool)
on-context-menu (actions/on-context-menu hover)
- on-double-click (actions/on-double-click hover hover-ids drawing-path? objects edition)
+ on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition)
on-drag-enter (actions/on-drag-enter)
on-drag-over (actions/on-drag-over)
on-drop (actions/on-drop file viewport-ref zoom)
@@ -124,6 +120,7 @@
on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream)
on-pointer-up (actions/on-pointer-up)
on-move-selected (actions/on-move-selected hover hover-ids selected)
+ on-menu-selected (actions/on-menu-selected hover hover-ids selected)
on-frame-enter (actions/on-frame-enter frame-hover)
on-frame-leave (actions/on-frame-leave frame-hover)
@@ -147,22 +144,23 @@
(contains? layout :snap-grid))
(or drawing-obj transform))
show-selrect? (and selrect (empty? drawing))
- show-measures? (and (not transform) (not node-editing?) show-distances?)]
+ show-measures? (and (not transform) (not node-editing?) show-distances?)
+ show-artboard-names? (contains? layout :display-artboard-names)]
(hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?)
(hooks/setup-viewport-size viewport-ref)
(hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?)
(hooks/setup-resize layout viewport-ref)
(hooks/setup-keyboard alt? ctrl? space?)
- (hooks/setup-hover-shapes page-id move-stream objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
- (hooks/setup-viewport-modifiers modifiers selected objects render-ref)
+ (hooks/setup-hover-shapes page-id move-stream base-objects transform selected ctrl? hover hover-ids @hover-disabled? zoom)
+ (hooks/setup-viewport-modifiers modifiers base-objects)
(hooks/setup-shortcuts node-editing? drawing-path?)
- (hooks/setup-active-frames objects vbox hover active-frames)
+ (hooks/setup-active-frames base-objects vbox hover active-frames)
[:div.viewport
[:div.viewport-overlays
- [:& wtr/frame-renderer {:objects objects
+ [:& wtr/frame-renderer {:objects base-objects
:background background}]
(when show-comments?
@@ -181,9 +179,9 @@
:viewport-ref viewport-ref}])
[:& widgets/viewport-actions]]
+
[:svg.render-shapes
{:id "render"
- :ref render-ref
:xmlns "http://www.w3.org/2000/svg"
:xmlnsXlink "http://www.w3.org/1999/xlink"
:xmlns:penpot "https://penpot.app/xmlns"
@@ -197,11 +195,11 @@
[:& use/export-page {:options options}]
- [:& (mf/provider use/include-metadata-ctx) {:value false}
+ [:& (mf/provider use/include-metadata-ctx) {:value (debug? :show-export-metadata)}
[:& (mf/provider embed/context) {:value true}
;; Render root shape
[:& shapes/root-shape {:key page-id
- :objects objects
+ :objects base-objects
:active-frames @active-frames}]]]]
[:svg.viewport-controls
@@ -233,7 +231,7 @@
[:g {:style {:pointer-events (if disable-events? "none" "auto")}}
(when show-outlines?
[:& outline/shape-outlines
- {:objects objects
+ {:objects base-objects
:selected selected
:hover (when (not= :frame (:type @hover))
#{(or @frame-hover (:id @hover))})
@@ -247,7 +245,8 @@
:zoom zoom
:edition edition
:disable-handlers (or drawing-tool edition)
- :on-move-selected on-move-selected}])
+ :on-move-selected on-move-selected
+ :on-context-menu on-menu-selected}])
(when show-measures?
[:& msr/measurement
@@ -258,13 +257,14 @@
:zoom zoom}])
(when text-editing?
- [:& editor/text-shape-edit {:shape (get objects edition)}])
+ [:& editor/text-shape-edit {:shape (get base-objects edition)}])
[:& widgets/frame-titles
- {:objects objects
+ {:objects objects-modified
:selected selected
:zoom zoom
:modifiers modifiers
+ :show-artboard-names? show-artboard-names?
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
:on-frame-select on-frame-select}]
@@ -272,7 +272,7 @@
(when show-prototypes?
[:& widgets/frame-flows
{:flows (:flows options)
- :objects objects
+ :objects base-objects
:selected selected
:zoom zoom
:modifiers modifiers
@@ -309,7 +309,7 @@
:zoom zoom
:page-id page-id
:selected selected
- :objects objects
+ :objects base-objects
:modifiers modifiers}])
(when show-snap-distance?
@@ -334,6 +334,9 @@
(when show-prototypes?
[:& interactions/interactions
{:selected selected
+ :zoom zoom
+ :objects objects-modified
+ :current-transform transform
:hover-disabled? hover-disabled?}])
(when show-selrect?
diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
index b5427fb4e7..9f1fc6b52f 100644
--- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs
@@ -203,6 +203,16 @@
:shape @hover})
(dw/show-context-menu {:position position})))))))))
+(defn on-menu-selected
+ [hover hover-ids selected]
+ (mf/use-callback
+ (mf/deps @hover hover-ids selected)
+ (fn [event]
+ (dom/prevent-default event)
+ (dom/stop-propagation event)
+ (let [position (dom/get-client-position event)]
+ (st/emit! (dw/show-shape-context-menu {:position position}))))))
+
(defn on-mouse-up
[disable-paste]
(mf/use-callback
@@ -369,7 +379,7 @@
(/ zoom))]
(dom/prevent-default event)
(dom/stop-propagation event)
- (if (and (not (cfg/check-platform? :macos)) ;; macos sends delta-x automaticaly, don't need to do it
+ (if (and (not (cfg/check-platform? :macos)) ;; macos sends delta-x automatically, don't need to do it
(kbd/shift? event))
(st/emit! (dw/update-viewport-position {:x #(+ % delta-y)}))
(st/emit! (dw/update-viewport-position {:x #(+ % delta-x)
diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
index 740c6956c3..2ab39b52d9 100644
--- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs
@@ -22,7 +22,7 @@
[:g.draw-area
[:g {:style {:pointer-events "none"}}
- [:& shapes/shape-wrapper {:shape shape}]]
+ [:& shapes/shape-wrapper {:shape (gsh/transform-shape shape)}]]
(case tool
:path [:& path-editor {:shape shape :zoom zoom}]
@@ -39,7 +39,7 @@
[:rect.main {:x x :y y
:width width
:height height
- :style {:stroke "#1FDEA7"
+ :style {:stroke "var(--color-select)"
:fill "none"
:stroke-width (/ 1 zoom)}}])))
diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs
index 48a8668754..0164211a41 100644
--- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs
@@ -22,14 +22,14 @@
[rumext.alpha :as mf]))
(def gradient-line-stroke-width 2)
-(def gradient-line-stroke-color "white")
+(def gradient-line-stroke-color "var(--color-white)")
(def gradient-square-width 15)
(def gradient-square-radius 2)
(def gradient-square-stroke-width 2)
(def gradient-width-handler-radius 5)
-(def gradient-width-handler-color "white")
-(def gradient-square-stroke-color "white")
-(def gradient-square-stroke-color-selected "#1FDEA7")
+(def gradient-width-handler-color "var(--color-white)")
+(def gradient-square-stroke-color "var(--color-white)")
+(def gradient-square-stroke-color-selected "var(--color-select)")
(def editing-spot-ref
(l/derived (l/in [:workspace-local :editing-stop]) st/state))
@@ -83,9 +83,9 @@
:height (+ (/ (* 2 gradient-width-handler-radius) zoom) (/ 2 zoom) 4)
:offset (/ 2 zoom)}])
-(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=")
+(def checkerboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA8AAAAPCAIAAAC0tAIdAAACvUlEQVQoFQGyAk39AeLi4gAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAB0dHQAAAAAAAOPj4wAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB////AAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAA4+PjAAAAAAAAHR0dAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAAdHR0AAAAAAADj4+MAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjScaa0cU7nIAAAAASUVORK5CYII=")
-#_(def checkboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
+#_(def checkerboard "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAADFJREFUOE9jZGBgEAFifOANPknGUQMYhkkYEEgG+NMJKAwIAbwJbdQABnBCIgRoG4gAIF8IsXB/Rs4AAAAASUVORK5CYII=")
(mf/defc gradient-color-handler
[{:keys [filter-id zoom point color angle selected
@@ -93,7 +93,7 @@
[:g {:filter (str/fmt "url(#%s)" filter-id)
:transform (gmt/rotate-matrix angle point)}
- [:image {:href checkboard
+ [:image {:href checkerboard
:x (- (:x point) (/ gradient-square-width 2 zoom))
:y (- (:y point) (/ gradient-square-width 2 zoom))
:width (/ gradient-square-width zoom)
@@ -115,7 +115,7 @@
:rx (/ gradient-square-radius zoom)
:width (/ gradient-square-width zoom)
:height (/ gradient-square-width zoom)
- :stroke (if selected "#31EFB8" "white")
+ :stroke (if selected "var(--color-primary)" "var(--color-white)")
:stroke-width (/ gradient-square-stroke-width zoom)
:fill (:value color)
:fill-opacity (:opacity color)
@@ -173,7 +173,7 @@
[:g.gradient-handlers
[:defs
[:& gradient-line-drop-shadow-filter {:id "gradient_line_drop_shadow" :from-p from-p :to-p to-p :zoom zoom}]
- [:& gradient-line-drop-shadow-filter {:id "gradient_widh_line_drop_shadow" :from-p from-p :to-p width-p :zoom zoom}]
+ [:& gradient-line-drop-shadow-filter {:id "gradient_width_line_drop_shadow" :from-p from-p :to-p width-p :zoom zoom}]
[:& gradient-square-drop-shadow-filter {:id "gradient_square_from_drop_shadow" :point from-p :zoom zoom}]
[:& gradient-square-drop-shadow-filter {:id "gradient_square_to_drop_shadow" :point to-p :zoom zoom}]
[:& gradient-width-handler-shadow-filter {:id "gradient_width_handler_drop_shadow" :point width-p :zoom zoom}]]
@@ -187,7 +187,7 @@
:stroke-width (/ gradient-line-stroke-width zoom)}]]
(when width-p
- [:g {:filter "url(#gradient_widh_line_drop_shadow)"}
+ [:g {:filter "url(#gradient_width_line_drop_shadow)"}
[:line {:x1 (:x from-p)
:y1 (:y from-p)
:x2 (:x width-p)
diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
index b2e596e113..2ff6e17445 100644
--- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs
@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.hooks
(:require
+ [app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.shortcuts :as dsc]
@@ -120,7 +121,7 @@
(->> move-stream
;; When transforming shapes we stop querying the worker
(rx/filter #(not (some? (mf/ref-val transform-ref))))
- (rx/switch-map query-point))
+ (rx/merge-map query-point))
(->> move-stream
;; When transforming shapes we stop querying the worker
@@ -169,22 +170,31 @@
(reset! hover hover-shape)
(reset! hover-ids ids))))))
-(defn setup-viewport-modifiers [modifiers selected objects render-ref]
- (let [roots (mf/use-memo
- (mf/deps objects selected)
- (fn []
- (let [roots-ids (cp/clean-loops objects selected)]
- (->> roots-ids (mapv #(get objects %))))))]
+(defn setup-viewport-modifiers
+ [modifiers objects]
+ (let [transforms
+ (mf/use-memo
+ (mf/deps modifiers)
+ (fn []
+ (d/mapm (fn [id {modifiers :modifiers}]
+ (let [center (gsh/center-shape (get objects id))]
+ (gsh/modifiers->transform center modifiers)))
+ modifiers)))
+
+ shapes
+ (mf/use-memo
+ (mf/deps transforms)
+ (fn []
+ (->> (keys transforms)
+ (mapv (d/getf objects)))))]
;; Layout effect is important so the code is executed before the modifiers
;; are applied to the shape
(mf/use-layout-effect
- (mf/deps modifiers roots)
-
- #(when-let [render-node (mf/ref-val render-ref)]
- (if modifiers
- (utils/update-transform render-node roots modifiers)
- (utils/remove-transform render-node roots))))))
+ (mf/deps transforms)
+ (fn []
+ (utils/update-transform shapes transforms modifiers)
+ #(utils/remove-transform shapes)))))
(defn inside-vbox [vbox objects frame-id]
(let [frame (get objects frame-id)]
diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
index ed2300b65a..90f120024c 100644
--- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs
@@ -8,6 +8,7 @@
"Visually show shape interactions in workspace"
(:require
[app.common.data :as d]
+ [app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.common.types.interactions :as cti]
[app.main.data.workspace :as dw]
@@ -116,7 +117,7 @@
(when icon-pdata
[:path {:fill stroke
:stroke-width 2
- :stroke "#FFFFFF"
+ :stroke "var(--color-white)"
:d icon-pdata
:transform (str
"scale(" inv-zoom ", " inv-zoom ") "
@@ -149,7 +150,7 @@
(if-not selected?
[:g {:on-mouse-down #(on-mouse-down % index orig-shape)}
- [:path {:stroke "#B1B2B5"
+ [:path {:stroke "var(--color-gray-20)"
:fill "none"
:pointer-events "visible"
:stroke-width (/ 2 zoom)
@@ -158,13 +159,13 @@
[:& interaction-marker {:index index
:x dest-x
:y dest-y
- :stroke "#B1B2B5"
+ :stroke "var(--color-gray-20)"
:action-type action-type
:arrow-dir arrow-dir
:zoom zoom}])]
[:g {:on-mouse-down #(on-mouse-down % index orig-shape)}
- [:path {:stroke "#31EFB8"
+ [:path {:stroke "var(--color-primary)"
:fill "none"
:pointer-events "visible"
:stroke-width (/ 2 zoom)
@@ -172,17 +173,17 @@
(when dest-shape
[:& outline {:shape dest-shape
- :color "#31EFB8"}])
+ :color "var(--color-primary)"}])
[:& interaction-marker {:index index
:x orig-x
:y orig-y
- :stroke "#31EFB8"
+ :stroke "var(--color-primary)"
:zoom zoom}]
[:& interaction-marker {:index index
:x dest-x
:y dest-y
- :stroke "#31EFB8"
+ :stroke "var(--color-primary)"
:action-type action-type
:arrow-dir arrow-dir
:zoom zoom}]])))
@@ -196,7 +197,7 @@
[:g {:on-mouse-down #(on-mouse-down % index shape)}
[:& interaction-marker {:x handle-x
:y handle-y
- :stroke "#31EFB8"
+ :stroke "var(--color-primary)"
:action-type :navigate
:arrow-dir :right
:zoom zoom}]]))
@@ -217,8 +218,8 @@
[:g {:on-mouse-down start-move-position
:on-mouse-enter #(reset! hover-disabled? true)
:on-mouse-leave #(reset! hover-disabled? false)}
- [:path {:stroke "#31EFB8"
- :fill "#000000"
+ [:path {:stroke "var(--color-primary)"
+ :fill "var(--color-black)"
:fill-opacity 0.3
:stroke-width 1
:d (str "M" marker-x " " marker-y " "
@@ -232,21 +233,26 @@
[:circle {:cx (+ marker-x (/ width 2))
:cy (+ marker-y (/ height 2))
:r 8
- :fill "#31EFB8"}]]))))
+ :fill "var(--color-primary)"}]]))))
(mf/defc interactions
- [{:keys [selected hover-disabled?] :as props}]
- (let [local (mf/deref refs/workspace-local)
- zoom (mf/deref refs/selected-zoom)
- current-transform (:transform local)
- objects (mf/deref refs/workspace-page-objects)
- active-shapes (filter #(seq (:interactions %)) (vals objects))
- selected-shapes (map #(get objects %) selected)
- editing-interaction-index (:editing-interaction-index local)
- draw-interaction-to (:draw-interaction-to local)
- draw-interaction-to-frame (:draw-interaction-to-frame local)
- move-overlay-to (:move-overlay-to local)
- move-overlay-index (:move-overlay-index local)
+ [{:keys [current-transform objects zoom selected hover-disabled?] :as props}]
+ (let [active-shapes (into []
+ (comp (filter #(seq (:interactions %)))
+ (map gsh/transform-shape))
+ (vals objects))
+
+ selected-shapes (into []
+ (comp (map (d/getf objects))
+ (map gsh/transform-shape))
+ selected)
+
+ {:keys [editing-interaction-index
+ draw-interaction-to
+ draw-interaction-to-frame
+ move-overlay-to
+ move-overlay-index]} (mf/deref refs/interactions-data)
+
first-selected (first selected-shapes)
calc-level (fn [index interactions]
diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs
index d4c41ad5d4..f525bb31f8 100644
--- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs
@@ -70,7 +70,7 @@
(let [shapes (obj/get props "shapes")
zoom (obj/get props "zoom")
color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes))))
- "#31EFB8" "#00E0FF")]
+ "var(--color-primary)" "var(--color-component-highlight)")]
(for [shape shapes]
[:& outline {:key (str "outline-" (:id shape))
:shape (gsh/transform-shape shape)
diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs
index 1373a151e0..3c09e952f3 100644
--- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs
@@ -6,13 +6,14 @@
(ns app.main.ui.workspace.viewport.pixel-overlay
(:require
+ [app.common.data :as d]
[app.common.uuid :as uuid]
[app.main.data.modal :as modal]
[app.main.data.workspace.colors :as dwc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.cursors :as cur]
- [app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]]
+ [app.main.ui.workspace.shapes :as shapes]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
@@ -36,16 +37,16 @@
(let [data (mf/deref refs/workspace-page)
objects (:objects data)
root (get objects uuid/zero)
- shapes (->> (:shapes root) (map #(get objects %)))]
- [:*
- [:g.shapes
- (for [item shapes]
- (if (= (:type item) :frame)
- [:& frame-wrapper {:shape item
- :key (:id item)
- :objects objects}]
- [:& shape-wrapper {:shape item
- :key (:id item)}]))]]))
+ shapes (->> (:shapes root)
+ (map (d/getf objects)))]
+ [:g.shapes
+ (for [item shapes]
+ (if (= (:type item) :frame)
+ [:& shapes/frame-wrapper {:shape item
+ :key (:id item)
+ :objects objects}]
+ [:& shapes/shape-wrapper {:shape item
+ :key (:id item)}]))]))
(mf/defc pixel-overlay
{::mf/wrap-props false}
diff --git a/frontend/src/app/main/ui/workspace/viewport/presence.cljs b/frontend/src/app/main/ui/workspace/viewport/presence.cljs
index 272b0628b4..a6059fcf35 100644
--- a/frontend/src/app/main/ui/workspace/viewport/presence.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/presence.cljs
@@ -14,45 +14,34 @@
[rumext.alpha :as mf]))
(def pointer-icon-path
- (str "M5.292 4.027L1.524.26l-.05-.01L0 0l.258 1.524 3.769 3.768zm-.45 "
- "0l-.313.314L1.139.95l.314-.314zm-.5.5l-.315.316-3.39-3.39.315-.315 "
- "3.39 3.39zM1.192.526l-.668.667L.431.646.64.43l.552.094z"))
+ (str "M11.58,-0.47L11.47,-0.35L0.34,10.77L0.30,10.96L-0.46,"
+ "15.52L4.29,14.72L15.53,3.47L11.58,-0.47ZL11.58,"
+ "-0.47ZL11.58,-0.47ZM11.58,1.3C12.31,2.05,13.02,"
+ "2.742,13.76,3.47L4.0053,13.23C3.27,12.50,2.55,"
+ "11.78,1.82,11.05L11.58,1.30ZL11.58,1.30ZM1.37,12.15L2.90,"
+ "13.68L1.67,13.89L1.165,13.39L1.37,12.15ZL1.37,12.15Z"))
(mf/defc session-cursor
[{:keys [session profile] :as props}]
- (let [zoom (mf/deref refs/selected-zoom)
- point (:point session)
- color (:color session "#000000")
- transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 4 zoom))]
+ (let [zoom (mf/deref refs/selected-zoom)
+ point (:point session)
+ background-color (:color session "var(--color-black)")
+ text-color (:text-color session "var(--color-white)")
+ transform (str/fmt "translate(%s, %s) scale(%s)" (:x point) (:y point) (/ 1 zoom))
+ shown-name (if (> (count (:fullname profile)) 16)
+ (str (str/slice (:fullname profile) 0 12) "...")
+ (:fullname profile))]
[:g.multiuser-cursor {:transform transform}
- [:path {:fill color
- :d pointer-icon-path
- }]
- [:g {:transform "translate(0 -291.708)"}
- [:rect {:width 25
- :height 5
- :x 7
- :y 291.5
- :fill color
- :fill-opacity 0.8
- :paint-order "stroke fill markers"
- :rx 1
- :ry 1}]
- [:text {:x 8
- :y 295
- :width 25
- :height 5
- :overflow "hidden"
- :fill "#fff"
- :stroke-width 1
- :font-family "Works Sans"
- :font-size 3
- :font-weight 400
- :letter-spacing 0
- :style { :line-height 1.25 }
- :word-spacing 0}
- (str (str/slice (:fullname profile) 0 14)
- (when (> (count (:fullname profile)) 14) "..."))]]]))
+ [:path {:fill background-color
+ :d pointer-icon-path}]
+ [:g {:transform "translate(17 -10)"}
+ [:foreignObject {:x -0.3
+ :y -12.5
+ :width 300
+ :height 120}
+ [:div.profile-name {:style {:background-color background-color
+ :color text-color}}
+ shown-name]]]]))
(mf/defc active-cursors
{::mf/wrap [mf/memo]}
diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
index 9dc30ad5d2..0b60675a75 100644
--- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs
@@ -15,10 +15,10 @@
[app.main.store :as st]
[app.main.ui.cursors :as cur]
[app.main.ui.workspace.shapes.path.editor :refer [path-editor]]
- [app.util.debug :refer [debug?]]
[app.util.dom :as dom]
[app.util.object :as obj]
[cuerdas.core :as str]
+ [debug :refer [debug?]]
[rumext.alpha :as mf]
[rumext.util :refer [map->obj]]))
@@ -27,13 +27,13 @@
(def resize-point-circle-radius 10)
(def resize-point-rect-size 8)
(def resize-side-height 8)
-(def selection-rect-color-normal "#1FDEA7")
-(def selection-rect-color-component "#00E0FF")
+(def selection-rect-color-normal "var(--color-select)")
+(def selection-rect-color-component "var(--color-component-highlight)")
(def selection-rect-width 1)
(def min-selrect-side 10)
(def small-selrect-side 30)
-(mf/defc selection-rect [{:keys [transform rect zoom color on-move-selected]}]
+(mf/defc selection-rect [{:keys [transform rect zoom color on-move-selected on-context-menu]}]
(when rect
(let [{:keys [x y width height]} rect]
[:rect.main.viewport-selrect
@@ -43,6 +43,7 @@
:height height
:transform transform
:on-mouse-down on-move-selected
+ :on-context-menu on-context-menu
:style {:stroke color
:stroke-width (/ selection-rect-width zoom)
:fill "none"}}])))
@@ -149,7 +150,7 @@
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"}
- :fill "#FFFFFF"
+ :fill "var(--color-white)"
:stroke (if (and (= position :bottom-right) overflow-text) "red" color)
:cx cx'
:cy cy'}]
@@ -179,7 +180,7 @@
)]))
(mf/defc resize-side-handler
- "The side handler is always rendered horizontaly and then rotated"
+ "The side handler is always rendered horizontally and then rotated"
[{:keys [x y length align angle zoom position rotation transform on-resize]}]
(let [res-point (if (#{:top :bottom} position)
{:y y}
@@ -223,6 +224,7 @@
zoom (obj/get props "zoom")
color (obj/get props "color")
on-move-selected (obj/get props "on-move-selected")
+ on-context-menu (obj/get props "on-context-menu")
on-resize (obj/get props "on-resize")
on-rotate (obj/get props "on-rotate")
disable-handlers (obj/get props "disable-handlers")
@@ -244,7 +246,8 @@
:transform transform
:zoom zoom
:color color
- :on-move-selected on-move-selected}]
+ :on-move-selected on-move-selected
+ :on-context-menu on-context-menu}]
;; Handlers
(for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)]
@@ -281,7 +284,7 @@
:fill "none"}}]]))
(mf/defc multiple-selection-handlers
- [{:keys [shapes selected zoom color disable-handlers on-move-selected] :as props}]
+ [{:keys [shapes selected zoom color disable-handlers on-move-selected on-context-menu] :as props}]
(let [shape (mf/use-memo
(mf/deps shapes)
#(->> shapes
@@ -310,17 +313,20 @@
:disable-handlers disable-handlers
:on-move-selected on-move-selected
:on-resize on-resize
- :on-rotate on-rotate}]
+ :on-rotate on-rotate
+ :on-context-menu on-context-menu}]
(when (debug? :selection-center)
[:circle {:cx (:x shape-center) :cy (:y shape-center) :r 5 :fill "yellow"}])]))
(mf/defc single-selection-handlers
- [{:keys [shape zoom color disable-handlers on-move-selected] :as props}]
+ [{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}]
(let [shape-id (:id shape)
shape (geom/transform-shape shape {:round-coords? false})
- shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape)
+ shape' (if (debug? :simple-selection)
+ (geom/setup {:type :rect} (geom/selection-rect [shape]))
+ shape)
on-resize
(fn [current-position _initial-position event]
@@ -340,11 +346,12 @@
:on-rotate on-rotate
:on-resize on-resize
:disable-handlers disable-handlers
- :on-move-selected on-move-selected}]))
+ :on-move-selected on-move-selected
+ :on-context-menu on-context-menu}]))
(mf/defc selection-handlers
{::mf/wrap [mf/memo]}
- [{:keys [shapes selected edition zoom disable-handlers on-move-selected] :as props}]
+ [{:keys [shapes selected edition zoom disable-handlers on-move-selected on-context-menu] :as props}]
(let [num (count shapes)
{:keys [type] :as shape} (first shapes)
@@ -361,7 +368,8 @@
:zoom zoom
:color color
:disable-handlers disable-handlers
- :on-move-selected on-move-selected}]
+ :on-move-selected on-move-selected
+ :on-context-menu on-context-menu}]
(and (= type :text)
(= edition (:id shape)))
@@ -378,4 +386,5 @@
:zoom zoom
:color color
:disable-handlers disable-handlers
- :on-move-selected on-move-selected}])))
+ :on-move-selected on-move-selected
+ :on-context-menu on-context-menu}])))
diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
index aecd0b52f1..cf990d062c 100644
--- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs
@@ -17,7 +17,7 @@
[cuerdas.core :as str]
[rumext.alpha :as mf]))
-(def ^:private line-color "#D383DA")
+(def ^:private line-color "var(--color-snap)")
(def ^:private segment-gap 2)
(def ^:private segment-gap-side 5)
@@ -79,7 +79,7 @@
[:text {:x (if (= coord :x) x (+ x (/ width 2)))
:y (- (+ y (/ (/ pill-text-height zoom) 2) (- (/ 6 zoom))) (if (= coord :x) (/ 2 zoom) 0))
:font-size (/ pill-text-font-size zoom)
- :fill "white"
+ :fill "var(--color-white)"
:text-anchor "middle"}
(mth/precision distance 0)]])
diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
index fd8a015e0e..ec95528eec 100644
--- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs
@@ -15,7 +15,7 @@
[beicon.core :as rx]
[rumext.alpha :as mf]))
-(def ^:private line-color "#D383DA")
+(def ^:private line-color "var(--color-snap)")
(def ^:private line-opacity 0.6)
(def ^:private line-width 1)
@@ -58,7 +58,7 @@
(->> shapes (first)))
shape (if modifiers
- (-> shape (assoc :modifiers modifiers) gsh/transform-shape)
+ (-> shape (merge (get modifiers (:id shape))) gsh/transform-shape)
shape)
frame-id (snap/snap-frame-id shapes)]
diff --git a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs
index 85a9ab316b..8b53a9e21c 100644
--- a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs
@@ -28,12 +28,24 @@
(when node
(let [img-node (mf/ref-val thumbnail-img)]
(timers/schedule-on-idle
- #(let [frame-node (dom/get-element (str "shape-" (:id shape)))
- loading-node (when frame-node
- (dom/query frame-node "[data-loading=\"true\"]"))]
- (if (and (some? frame-node) (not (some? loading-node)))
- (let [xml (-> (js/XMLSerializer.)
- (.serializeToString frame-node)
+ #(let [frame-node (dom/get-element (str "shape-" (:id shape)))
+ thumb-node (dom/query frame-node ".frame-thumbnail")
+ loading-node (dom/query frame-node "[data-loading=\"true\"]")]
+ (if (and (some? frame-node)
+ ;; Not render if the thumbnail is in display
+ (nil? thumb-node)
+ ;; Not render if some image is still loading
+ (nil? loading-node))
+ (let [frame-html (-> (js/XMLSerializer.)
+ (.serializeToString frame-node))
+
+ ;; We need to wrap the group node into a SVG with a viewbox that matches the selrect of the frame
+ svg-node (.createElementNS js/document "http://www.w3.org/2000/svg" "svg")
+ _ (.setAttribute svg-node "version" "1.1")
+ _ (.setAttribute svg-node "viewBox" (str (:x shape) " " (:y shape) " " (:width shape) " " (:height shape)))
+ _ (unchecked-set svg-node "innerHTML" frame-html)
+ xml (-> (js/XMLSerializer.)
+ (.serializeToString svg-node)
js/encodeURIComponent
js/unescape
js/btoa)
@@ -117,7 +129,9 @@
;; after a time
(reset! shape-id nil)
(rx/push! next :next)
- (timers/schedule-on-idle (st/emitf (dwp/update-frame-thumbnail frame-id)))))]
+ (timers/schedule-on-idle
+ 100
+ (st/emitf (dwp/update-frame-thumbnail frame-id)))))]
(mf/use-effect
(mf/deps render-frame)
diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
index 384f381177..088d175494 100644
--- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs
@@ -7,35 +7,145 @@
(ns app.main.ui.workspace.viewport.utils
(:require
[app.common.data :as d]
+ [app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.main.ui.cursors :as cur]
[app.util.dom :as dom]
[cuerdas.core :as str]))
-;; TODO: looks like first argument is not necessary.
-(defn update-transform [_node shapes modifiers]
- (doseq [{:keys [id type]} shapes]
- (let [shape-node (dom/get-element (str "shape-" id))
+(defn- text-corrected-transform
+ "If we apply a scale directly to the texts it will show deformed so we need to create this
+ correction matrix to \"undo\" the resize but keep the other transformations."
+ [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers]
- ;; When the shape is a frame we maybe need to move its thumbnail
- thumb-node (dom/get-element (str "thumbnail-" id))]
- (when-let [node (cond
- (and (some? shape-node) (= :frame type))
- (.-parentNode shape-node)
+ (let [corner-pt (first points)
+ corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse))
- (and (some? thumb-node) (= :frame type))
- (.-parentNode thumb-node)
+ resize-x? (some? (:resize-vector modifiers))
+ resize-y? (some? (:resize-vector-2 modifiers))
- :else
- shape-node)]
- (dom/set-attribute node "transform" (str (:displacement modifiers)))))))
+ flip-x? (neg? (get-in modifiers [:resize-vector :x]))
+ flip-y? (or (neg? (get-in modifiers [:resize-vector :y]))
+ (neg? (get-in modifiers [:resize-vector-2 :y])))
-;; TODO: looks like first argument is not necessary.
-(defn remove-transform [_node shapes]
- (doseq [{:keys [id type]} shapes]
- (when-let [node (dom/get-element (str "shape-" id))]
- (let [node (if (= :frame type) (.-parentNode node) node)]
- (dom/remove-attribute node "transform")))))
+ result (cond-> (gmt/matrix)
+ (and (some? transform) (or resize-x? resize-y?))
+ (gmt/multiply transform)
+
+ resize-x?
+ (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt)
+
+ resize-y?
+ (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt)
+
+ flip-x?
+ (gmt/scale (gpt/point -1 1) corner-pt)
+
+ flip-y?
+ (gmt/scale (gpt/point 1 -1) corner-pt)
+
+ (and (some? transform) (or resize-x? resize-y?))
+ (gmt/multiply transform-inverse))
+
+ [width height]
+ (if (or resize-x? resize-y?)
+ (let [pc (cond-> (gpt/point x y)
+ (some? transform)
+ (gpt/transform transform)
+
+ (some? current-transform)
+ (gpt/transform current-transform))
+
+ pw (cond-> (gpt/point (+ x width) y)
+ (some? transform)
+ (gpt/transform transform)
+
+ (some? current-transform)
+ (gpt/transform current-transform))
+
+ ph (cond-> (gpt/point x (+ y height))
+ (some? transform)
+ (gpt/transform transform)
+
+ (some? current-transform)
+ (gpt/transform current-transform))]
+ [(gpt/distance pc pw) (gpt/distance pc ph)])
+ [width height])]
+
+ [result width height]))
+
+(defn get-nodes
+ "Retrieve the DOM nodes to apply the matrix transformation"
+ [{:keys [id type masked-group?]}]
+ (let [shape-node (dom/get-element (str "shape-" id))
+
+ frame? (= :frame type)
+ group? (= :group type)
+ text? (= :text type)
+ mask? (and group? masked-group?)
+
+ ;; When the shape is a frame we maybe need to move its thumbnail
+ thumb-node (when frame? (dom/get-element (str "thumbnail-" id)))]
+
+ (cond
+ frame?
+ [thumb-node
+ (dom/query shape-node ".frame-background")
+ (dom/query shape-node ".frame-clip")]
+
+ ;; For groups we don't want to transform the whole group but only
+ ;; its filters/masks
+ mask?
+ [(dom/query shape-node ".mask-clip-path")
+ (dom/query shape-node ".mask-shape")]
+
+ group?
+ []
+
+ text?
+ [shape-node
+ (dom/query shape-node "foreignObject")
+ (dom/query shape-node ".text-shape")]
+
+ :else
+ [shape-node])))
+
+(defn update-transform [shapes transforms modifiers]
+ (doseq [{:keys [id type] :as shape} shapes]
+ (when-let [nodes (get-nodes shape)]
+ (let [transform (get transforms id)
+ modifiers (get-in modifiers [id :modifiers])
+
+ [text-transform text-width text-height]
+ (when (= :text type)
+ (text-corrected-transform shape transform modifiers))]
+
+ (doseq [node nodes]
+ (cond
+ (dom/class? node "text-shape")
+ (when (some? text-transform)
+ (dom/set-attribute node "transform" (str text-transform)))
+
+ (= (dom/get-tag-name node) "foreignObject")
+ (when (and (some? text-width) (some? text-height))
+ (dom/set-attribute node "width" text-width)
+ (dom/set-attribute node "height" text-height))
+
+ (and (some? transform) (some? node))
+ (dom/set-attribute node "transform" (str transform))))))))
+
+(defn remove-transform [shapes]
+ (doseq [shape shapes]
+ (when-let [nodes (get-nodes shape)]
+ (doseq [node nodes]
+ (when (some? node)
+ (cond
+ (= (dom/get-tag-name node) "foreignObject")
+ ;; The shape width/height will be automaticaly setup when the modifiers are applied
+ nil
+
+ :else
+ (dom/remove-attribute node "transform")))))))
(defn format-viewbox [vbox]
(str/join " " [(+ (:x vbox 0) (:left-offset vbox 0))
diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
index 9d7f54ed4a..2a1e9df0f8 100644
--- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs
@@ -31,7 +31,7 @@
:pattern-units "userSpaceOnUse"}
[:path {:d "M 1 0 L 0 0 0 1"
:style {:fill "none"
- :stroke "#59B9E2"
+ :stroke "var(--color-info)"
:stroke-opacity "0.2"
:stroke-width (str (/ 1 zoom))}}]]]
[:rect {:x (:x vbox)
@@ -90,7 +90,8 @@
"translate(" (* zoom x) ", " (* zoom y) ")")))
(mf/defc frame-title
- [{:keys [frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}]
+ {::mf/wrap [mf/memo]}
+ [{:keys [frame modifiers selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}]
(let [{:keys [width x y]} (gsh/transform-shape frame)
label-pos (gpt/point x (- y (/ 10 zoom)))
@@ -110,6 +111,16 @@
(st/emitf (dw/go-to-layout :layers)
(dw/start-rename-shape (:id frame))))
+ on-context-menu
+ (mf/use-callback
+ (mf/deps frame)
+ (fn [bevent]
+ (let [event (.-nativeEvent bevent)
+ position (dom/get-client-position event)]
+ (dom/prevent-default event)
+ (dom/stop-propagation event)
+ (st/emit! (dw/show-shape-context-menu {:position position :shape frame})))))
+
on-pointer-enter
(mf/use-callback
(mf/deps (:id frame) on-frame-enter)
@@ -130,9 +141,11 @@
:transform (str (when (and selected? modifiers)
(str (:displacement modifiers) " " ))
(text-transform label-pos zoom))
- :style {:fill (when selected? "#28c295")}
+ :style {:fill (when selected? "var(--color-primary-dark)")}
+ :visibility (if show-artboard-names? "visible" "hidden")
:on-mouse-down on-mouse-down
:on-double-click on-double-click
+ :on-context-menu on-context-menu
:on-pointer-enter on-pointer-enter
:on-pointer-leave on-pointer-leave}
(:name frame)]))
@@ -144,6 +157,7 @@
zoom (unchecked-get props "zoom")
modifiers (unchecked-get props "modifiers")
selected (or (unchecked-get props "selected") #{})
+ show-artboard-names? (unchecked-get props "show-artboard-names?")
on-frame-enter (unchecked-get props "on-frame-enter")
on-frame-leave (unchecked-get props "on-frame-leave")
on-frame-select (unchecked-get props "on-frame-select")
@@ -154,6 +168,7 @@
[:& frame-title {:frame frame
:selected? (contains? selected (:id frame))
:zoom zoom
+ :show-artboard-names? show-artboard-names?
:modifiers modifiers
:on-frame-enter on-frame-enter
:on-frame-leave on-frame-leave
diff --git a/frontend/src/app/main/worker.cljs b/frontend/src/app/main/worker.cljs
index c9004a9f00..8bd60284fe 100644
--- a/frontend/src/app/main/worker.cljs
+++ b/frontend/src/app/main/worker.cljs
@@ -11,20 +11,24 @@
(defn on-error
[error]
- (js/console.error "Error on worker" error))
+ (js/console.error "Error on worker" (pr-str error)))
-(defonce instance
- (when (not= *target* "nodejs")
- (uw/init cfg/worker-uri on-error)))
+(defonce instance (atom nil))
+
+(defn init!
+ []
+ (reset!
+ instance
+ (uw/init cfg/worker-uri on-error)))
(defn ask!
[message]
- (uw/ask! instance message))
+ (when @instance (uw/ask! @instance message)))
(defn ask-buffered!
[message]
- (uw/ask-buffered! instance message))
+ (when @instance (uw/ask-buffered! @instance message)))
(defn ask-many!
[message]
- (uw/ask-many! instance message))
+ (when @instance (uw/ask-many! @instance message)))
diff --git a/frontend/src/app/util/avatars.cljs b/frontend/src/app/util/avatars.cljs
index acca7decaa..efe1ec07ea 100644
--- a/frontend/src/app/util/avatars.cljs
+++ b/frontend/src/app/util/avatars.cljs
@@ -12,6 +12,7 @@
(defn generate*
[{:keys [name color size]
:or {color "#000000" size 128}}]
+
(let [parts (str/words (str/upper name))
letters (if (= 1 (count parts))
(ffirst parts)
@@ -27,7 +28,7 @@
(obj/set! context "font" (str (/ size 2) "px Arial"))
(obj/set! context "textAlign" "center")
- (obj/set! context "fillStyle" "#FFFFFF")
+ (obj/set! context "fillStyle" "#ffffff")
(.fillText context letters (/ size 2) (/ size 1.5))
(.toDataURL canvas)))
diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs
index a4069406c3..19978d5a89 100644
--- a/frontend/src/app/util/code_gen.cljs
+++ b/frontend/src/app/util/code_gen.cljs
@@ -180,8 +180,9 @@
shape-to-prop (->> text-shape-style vals (map :to-prop) (reduce merge))
shape-format (->> text-shape-style vals (map :format) (reduce merge))
-
- text-values (->> (search-text-attrs (:content shape) (conj (:props style-text) :fill-color-gradient))
+ text-values (->> (search-text-attrs
+ (:content shape)
+ (conj (:props style-text) :fill-color-gradient :fill-opacity))
(d/merge txt/default-text-attrs))]
(str/join
"\n"
diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs
deleted file mode 100644
index c3ee88d9f9..0000000000
--- a/frontend/src/app/util/debug.cljs
+++ /dev/null
@@ -1,95 +0,0 @@
-(ns app.util.debug
- "Debugging utils"
- (:require
- [app.common.math :as mth]
- [app.util.object :as obj]
- [app.util.timers :as timers]
- [cljs.pprint :refer [pprint]]))
-
-(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection})
-
-;; These events are excluded when we activate the :events flag
-(def debug-exclude-events
- #{:app.main.data.workspace.notifications/handle-pointer-update
- :app.main.data.workspace.selection/change-hover-state})
-
-(defonce ^:dynamic *debug* (atom #{#_:events}))
-
-(defn debug-all! [] (reset! *debug* debug-options))
-(defn debug-none! [] (reset! *debug* #{}))
-(defn debug! [option] (swap! *debug* conj option))
-(defn -debug! [option] (swap! *debug* disj option))
-
-(defn ^:export ^boolean debug?
- [option]
- (if *assert*
- (boolean (@*debug* option))
- false))
-
-(defn ^:export toggle-debug [name] (let [option (keyword name)]
- (if (debug? option)
- (-debug! option)
- (debug! option))))
-(defn ^:export debug-all [] (debug-all!))
-(defn ^:export debug-none [] (debug-none!))
-
-(defn ^:export tap
- "Transducer function that can execute a side-effect `effect-fn` per input"
- [effect-fn]
-
- (fn [rf]
- (fn
- ([] (rf))
- ([result] (rf result))
- ([result input]
- (effect-fn input)
- (rf result input)))))
-
-(defn ^:export logjs
- ([str] (tap (partial logjs str)))
- ([str val]
- (js/console.log str (clj->js val))
- val))
-
-(when (exists? js/window)
- (set! (.-dbg ^js js/window) clj->js)
- (set! (.-pp ^js js/window) pprint))
-
-
-(defonce widget-style "
- background: black;
- bottom: 10px;
- color: white;
- height: 20px;
- padding-left: 8px;
- position: absolute;
- right: 10px;
- width: 40px;
- z-index: 99999;
- opacity: 0.5;
-")
-
-(defn ^:export fps
- "Adds a widget to keep track of the average FPS's"
- []
- (let [last (volatile! (.now js/performance))
- avg (volatile! 0)
- node (-> (.createElement js/document "div")
- (obj/set! "id" "fps")
- (obj/set! "style" widget-style))
- body (obj/get js/document "body")
-
- do-thing (fn do-thing []
- (timers/raf
- (fn []
- (let [cur (.now js/performance)
- ts (/ 1000 (* (- cur @last)))
- val (+ @avg (* (- ts @avg) 0.1))]
-
- (obj/set! node "innerText" (mth/precision val 0))
- (vreset! last cur)
- (vreset! avg val)
- (do-thing)))))]
-
- (.appendChild body node)
- (do-thing)))
diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs
index 398961057a..37e75d1a9f 100644
--- a/frontend/src/app/util/dom.cljs
+++ b/frontend/src/app/util/dom.cljs
@@ -17,21 +17,24 @@
;; --- Deprecated methods
(defn event->inner-text
- [e]
- (.-innerText (.-target e)))
+ [^js e]
+ (when (some? e)
+ (.-innerText (.-target e))))
(defn event->value
- [e]
- (.-value (.-target e)))
+ [^js e]
+ (when (some? e)
+ (.-value (.-target e))))
(defn event->target
- [e]
- (.-target e))
+ [^js e]
+ (when (some? e)
+ (.-target e)))
;; --- New methods
(defn set-html-title
- [title]
+ [^string title]
(set! (.-title globals/document) title))
(defn set-page-style
@@ -44,7 +47,7 @@
(.insertAdjacentHTML head "beforeend"
(str "