diff --git a/.distignore b/.distignore index bafb6689992a..304353c18c80 100644 --- a/.distignore +++ b/.distignore @@ -3,7 +3,6 @@ .idea .storybook __mocks__ -assets/images/templates assets/src bin build diff --git a/.github/workflows/cleanup-pr-assets.yml b/.github/workflows/cleanup-pr-assets.yml new file mode 100644 index 000000000000..ebb3dcba3843 --- /dev/null +++ b/.github/workflows/cleanup-pr-assets.yml @@ -0,0 +1,34 @@ +# This workflow removes any assets created for manual QA testing +# from the GitHub Wiki repository once a pull request is closed. +# +# See https://github.com/google/web-stories-wp/issues/1398 + +name: Clean up PR assets for closed PRs + +on: + pull_request: + types: [closed] + +jobs: + remove-pr: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + repository: ${{ github.repository }}.wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Prune PR files + run: | + rm -rf "refs/pull/${{ github.event.pull_request.number }}" + git add . + git status + git diff --staged --quiet && echo 'No changes to commit; exiting!' && exit 0 + git commit -m "Prune refs/pull/${{ github.event.pull_request.number }}" + git push origin master + env: + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_AUTHOR_NAME: ${{ github.actor }} + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_NAME: ${{ github.actor }} diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5f571a82cba5..cdadebca30c4 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -1,6 +1,11 @@ name: Continuous Integration -on: pull_request +on: + push: + branches: + - master + - release/* + pull_request: jobs: phpcs: @@ -21,18 +26,20 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Setup cache + - name: Setup Composer cache uses: pat-s/always-upload-cache@v1.1.4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: | + ${{ runner.os }}-composer- + ${{ runner.os }}- - name: Validate composer.json run: composer --no-interaction validate --no-check-all - name: Install dependencies - run: composer install --prefer-dist --no-suggest --no-progress + run: composer install --prefer-dist --no-suggest --no-progress --no-interaction - name: Detect coding standard violations (PHPCS) run: vendor/bin/phpcs -q --report=checkstyle --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 | cs2pr --graceful-warnings @@ -64,22 +71,23 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: "${{ steps.nvm.outputs.NVMRC }}" + node-version: '${{ steps.nvm.outputs.NVMRC }}' - - name: Setup cache + - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Detect coding standard violations (ESLint) run: npm run lint:js:report @@ -88,8 +96,8 @@ jobs: - name: Annotate Code Linting Results uses: ataylorme/eslint-annotate-action@1.0.4 with: - repo-token: "${{ secrets.GITHUB_TOKEN }}" - report-json: "build/lint-js-report.json" + repo-token: '${{ secrets.GITHUB_TOKEN }}' + report-json: 'build/lint-js-report.json' markdownlint: name: Markdown Code Style @@ -105,22 +113,23 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: "${{ steps.nvm.outputs.NVMRC }}" + node-version: '${{ steps.nvm.outputs.NVMRC }}' - - name: Setup cache + - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Detect Markdown code style violations (MarkdownLint) run: npm run lint:md @@ -139,22 +148,23 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: "${{ steps.nvm.outputs.NVMRC }}" + node-version: '${{ steps.nvm.outputs.NVMRC }}' - - name: Setup cache + - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Detect coding standard violations (stylelint) run: npm run lint:css @@ -162,7 +172,7 @@ jobs: bundle-size: name: Bundle size check runs-on: ubuntu-latest - + if: ${{ github.event_name != 'push' }} steps: - uses: actions/checkout@v2 - uses: preactjs/compressed-size-action@v1 @@ -172,10 +182,218 @@ jobs: # Todo: compare final plugin ZIP file size too. build-script: 'build:js' + unit-js: + name: JavaScript Unit Tests + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Read .nvmrc + run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" + id: nvm + + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: ${{ steps.nvm.outputs.NVMRC }} + + - name: Setup npm cache + uses: pat-s/always-upload-cache@v1.1.4 + with: + path: node_modules + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- + + - name: Setup Jest cache + uses: actions/cache@v1 + id: cache-jest-cache + with: + path: .jest-cache + key: ${{ runner.os }}-${{ steps.nvm.outputs.NVMRC }}-jest + + - name: Install dependencies + run: npm ci + env: + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + + - name: Run JavaScript unit tests + run: npm run test:js -- --ci --cacheDirectory="$HOME/.jest-cache" --collectCoverage + + - name: Uplaod code coverage report + uses: codecov/codecov-action@v1 + with: + file: build/logs/lcov.info + + unit-php: + name: PHP Unit Tests (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + services: + mysql: + image: mariadb:latest + env: + MYSQL_ALLOW_EMPTY_PASSWORD: true + MYSQL_ROOT_PASSWORD: + MYSQL_DATABASE: wordpress_test + ports: + - 3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + continue-on-error: ${{ matrix.experimental == true }} + strategy: + matrix: + php: ['7.4', '7.0', '5.6'] + wp: ['latest'] + include: + - php: '8.0' + wp: 'trunk' + experimental: true + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: mysql + coverage: xdebug + tools: composer, cs2pr + + - name: Shutdown default MySQL service + run: sudo service mysql stop + + - name: Verify MariaDB connection + run: | + while ! mysqladmin ping -h"127.0.0.1" -P"${{ job.services.mysql.ports[3306] }}" --silent; do + sleep 1 + done + + - name: Get Composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Setup Composer cache + uses: pat-s/always-upload-cache@v1.1.4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + ${{ runner.os }}- + + - name: Install dependencies + run: composer install --prefer-dist --no-suggest --no-progress --no-interaction + + - name: Set up tests + run: bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1:${{ job.services.mysql.ports['3306'] }} ${{ matrix.wp }} true + + - name: Run tests + run: npm run test:php + if: ${{ matrix.wp != 'latest' || matrix.php != 7.4 }} + + - name: Run tests with coverage + run: npm run test:php -- --coverage-clover build/logs/clover.xml + if: ${{ matrix.wp == 'latest' && matrix.php == 7.4 }} + + - name: Uplaod code coverage report + uses: codecov/codecov-action@v1 + with: + file: build/logs/clover.xml + if: ${{ matrix.wp == 'latest' && matrix.php == 7.4 }} + + e2e: + name: E2E Tests (${{ matrix.browser }}) + runs-on: ubuntu-latest + strategy: + matrix: + browser: ['chrome', 'firefox'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + # See https://github.com/puppeteer/puppeteer/blob/master/docs/troubleshooting.md#chrome-headless-doesnt-launch-on-unix + - name: Install libgbm1 + run: sudo apt-get install libgbm1 + + - name: Read .nvmrc + run: echo "##[set-output name=NVMRC;]$(cat .nvmrc)" + id: nvm + + - name: Setup Node + uses: actions/setup-node@v1 + with: + node-version: '${{ steps.nvm.outputs.NVMRC }}' + + - name: Setup npm cache + uses: pat-s/always-upload-cache@v1.1.4 + with: + path: node_modules + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: | + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + coverage: none + tools: composer + + - name: Get Composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Setup Composer cache + uses: pat-s/always-upload-cache@v1.1.4 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-composer- + ${{ runner.os }}- + + - name: Install dependencies + run: | + npm ci + composer install --prefer-dist --no-suggest --no-progress --no-interaction + env: + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true + + - name: Install Puppeteer with correct browser + run: npm install puppeteer + env: + PUPPETEER_PRODUCT: ${{ matrix.browser }} + + - name: Build plugin + run: npm run build:js + + - name: Start Docker environment + run: npm run env:start + env: + COMPOSE_INTERACTIVE_NO_CLI: true + + - name: Run E2E tests + run: npm run test:e2e + env: + PUPPETEER_PRODUCT: ${{ matrix.browser }} + + - name: Stop Docker environment + run: npm run env:stop + env: + COMPOSE_INTERACTIVE_NO_CLI: true + build: name: Build plugin runs-on: ubuntu-latest - steps: - name: Checkout uses: actions/checkout@v2 @@ -187,17 +405,17 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: "${{ steps.nvm.outputs.NVMRC }}" + node-version: '${{ steps.nvm.outputs.NVMRC }}' - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -215,27 +433,82 @@ jobs: with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: | + ${{ runner.os }}-composer- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: | + npm ci + composer install --prefer-dist --no-suggest --no-progress --no-interaction env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Bundle plugin run: | + npm run build:js npm run build:plugin npm run bundle-plugin web-stories.zip npm run bundle-plugin web-stories-composer.zip -- --composer + - name: Bundle development version + run: | + rm -rf assets/css/* assets/js/* + NODE_ENV=development npx webpack + npm run build:plugin + npm run bundle-plugin web-stories-dev.zip + - name: Upload full bundle - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: web-stories.zip path: build/web-stories.zip - name: Upload composer bundle - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2 with: name: web-stories-composer.zip path: build/web-stories-composer.zip + + - name: Upload development bundle + uses: actions/upload-artifact@v2 + with: + name: web-stories-dev.zip + path: build/web-stories-dev.zip + + - name: Upload bundles in combined ZIP file + uses: actions/upload-artifact@v2 + with: + name: All ZIP Files + path: build/*.zip + + deploy-to-wiki: + name: Deploy artefacts to wiki for easier QA testing + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + repository: ${{ github.repository }}.wiki + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Download combined ZIP file + uses: actions/download-artifact@v2 + with: + name: All ZIP Files + path: ${{ github.ref }} + + - name: Commit updates + run: | + git add . + git status + git commit -m "Build and publish ${{ github.ref }}" + git pull --no-edit --quiet + git push origin master + env: + GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_AUTHOR_NAME: ${{ github.actor }} + GIT_COMMITTER_EMAIL: ${{ github.actor }}@users.noreply.github.com + GIT_COMMITTER_NAME: ${{ github.actor }} diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/release-assets.yml similarity index 80% rename from .github/workflows/continuous-deployment.yml rename to .github/workflows/release-assets.yml index 5d1fc5d15d21..c026670a68db 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/release-assets.yml @@ -1,4 +1,4 @@ -name: Continuous Deployment +name: Release Assets on: release: @@ -21,17 +21,17 @@ jobs: - name: Setup Node uses: actions/setup-node@v1 with: - node-version: "${{ steps.nvm.outputs.NVMRC }}" + node-version: '${{ steps.nvm.outputs.NVMRC }}' - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Setup PHP uses: shivammathur/setup-php@v2 @@ -49,12 +49,15 @@ jobs: with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} - restore-keys: ${{ runner.os }}-composer- + restore-keys: | + ${{ runner.os }}-composer- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Bundle plugin run: | @@ -72,7 +75,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.release.outputs.upload_url }} - asset_path: /build/web-stories.zip + asset_path: build/web-stories.zip asset_name: web-stories.zip asset_content_type: application/zip @@ -82,6 +85,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.release.outputs.upload_url }} - asset_path: /build/web-stories-composer.zip + asset_path: build/web-stories-composer.zip asset_name: web-stories-composer.zip asset_content_type: application/zip diff --git a/.github/workflows/update-google-fonts.yml b/.github/workflows/update-google-fonts.yml index dc02e6edefde..1f23996f0352 100644 --- a/.github/workflows/update-google-fonts.yml +++ b/.github/workflows/update-google-fonts.yml @@ -20,20 +20,21 @@ jobs: with: node-version: '${{ steps.nvm.outputs.NVMRC }}' - - name: Setup cache + - name: Setup npm cache uses: pat-s/always-upload-cache@v1.1.4 with: path: node_modules - key: ${{ runner.OS }}-npm-${{ hashFiles('**/package-lock.json') }} + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} restore-keys: | - ${{ runner.OS }}-npm-${{ env.cache-name }}- - ${{ runner.OS }}-npm- - ${{ runner.OS }}- + ${{ runner.os }}-npm-${{ env.cache-name }}- + ${{ runner.os }}-npm- + ${{ runner.os }}- - name: Install dependencies - run: npm install + run: npm ci env: - CI: TRUE + CI: true + PUPPETEER_SKIP_CHROMIUM_DOWNLOAD: true - name: Update list of Google Fonts env: diff --git a/.travis.yml b/.travis.yml index e175a912c8fd..985234fa8b64 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,9 @@ dist: xenial language: php +php: + - 7.3 + notifications: email: on_success: never @@ -25,10 +28,8 @@ notifications: cache: directories: - $HOME/.cache/composer - - $HOME/.jest-cache - $HOME/.npm - $HOME/.nvm/.cache - - $HOME/.wp-cli/.cache branches: only: @@ -39,120 +40,29 @@ env: - PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true install: - - set -e - - | - if [ "$JS" = "1" ] || [ "$E2E" = "1" ] || [ "$DEPLOY_PLUGIN" = "1" ]; then - nvm install - npm ci - fi - - | - if ! [ -z $PUPPETEER_PRODUCT ] ; then - npm ci puppeteer - fi - - | - if [ "$PHP" = "1" ] || [ "$E2E" = "1" ] || [ "$DEPLOY_PLUGIN" = "1" ]; then - composer install --no-interaction - fi - - set +e - -before_script: - - set -e - - | - if [ "$COVERAGE" != "1" ]; then - phpenv config-rm xdebug.ini || echo "xdebug.ini does not exist." - fi - - set +e + - nvm install + - npm ci + - composer install --no-interaction script: - - set -e - - | - if [ "$JS" = "1" ]; then - if [ "$COVERAGE" = "1" ]; then - npm run test:js -- --ci --cacheDirectory="$HOME/.jest-cache" --collectCoverage - bash <(curl -s https://codecov.io/bash) -cF javascript -f build/logs/lcov.info - else - npm run test:js -- --ci --cacheDirectory="$HOME/.jest-cache" - fi - fi - - | - if [ "$PHP" = "1" ]; then - bash bin/install-wp-tests.sh wordpress_test root '' localhost - if [ "$COVERAGE" = "1" ]; then - npm run test:php -- --coverage-clover build/logs/clover.xml - bash <(curl -s https://codecov.io/bash) -cF php -f build/logs/clover.xml - else - npm run test:php - fi - fi - - | - if [ "$E2E" = "1" ]; then - npm run build:js - npm run env:start - npm run test:e2e - npm run env:stop - fi - - set +e + - npm run build:js -jobs: - fast_finish: true - # These need to be exact matches, including whitespace! - allow_failures: - # PHP unit tests (nightly, WordPress trunk) - - env: PHP=1 WP_VERSION=trunk - php: nightly - include: - - stage: test - name: JavaScript unit tests - php: 7.4 - env: JS=1 - - name: PHP unit tests (7.4, WordPress latest) - php: 7.4 - env: PHP=1 WP_VERSION=latest - - name: PHP unit tests (7.3, WordPress latest) - php: 7.3 - env: PHP=1 WP_VERSION=latest - - name: PHP unit tests (7.0, WordPress latest) - php: 7.0 - env: PHP=1 WP_VERSION=latest - - name: PHP unit tests (5.6, WordPress latest) - php: 5.6 - env: PHP=1 WP_VERSION=latest - - name: PHP unit tests (nightly, WordPress trunk) - php: nightly - env: PHP=1 WP_VERSION=trunk - - name: PHP and JavaScript unit tests (7.4, WordPress latest, with code coverage) - php: 7.4 - env: JS=1 PHP=1 COVERAGE=1 WP_VERSION=latest - - name: E2E tests (Chromium) - php: 7.4 - env: E2E=1 WP_VERSION=latest PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= - - name: E2E tests (Firefox) - php: 7.4 - env: E2E=1 WP_VERSION=latest PUPPETEER_SKIP_CHROMIUM_DOWNLOAD= PUPPETEER_PRODUCT=firefox - - stage: deploy - if: (NOT type IN (pull_request)) AND (branch = master) - php: 7.3 - name: Deploy Storybook / Playground - env: DEPLOY_PLUGIN=1 - before_deploy: - - npm run build:storybook - deploy: - - provider: pages - edge: true - github_token: $GITHUB_TOKEN - keep_history: true - local_dir: build/storybook - on: - branch: master - - provider: script - edge: true - # This script also runs `npm run build`. - script: bin/deploy-to-test-environment.sh - on: - branch: master +before_deploy: + - npm run build:storybook -services: - - mysql +deploy: + - provider: pages + edge: true + github_token: $GITHUB_TOKEN + keep_history: true + local_dir: build/storybook + on: + branch: master + - provider: script + edge: true + script: bin/deploy-to-test-environment.sh + on: + branch: master addons: ssh_known_hosts: codeserver.dev.6b7f1eeb-705b-4201-864d-2007030c8372.drush.in:2222 diff --git a/assets/images/do-not-fit.png b/assets/images/do-not-fit.png deleted file mode 100644 index 27575ef3786d..000000000000 Binary files a/assets/images/do-not-fit.png and /dev/null differ diff --git a/assets/images/fit-to-device.png b/assets/images/fit-to-device.png deleted file mode 100644 index c73007b328b0..000000000000 Binary files a/assets/images/fit-to-device.png and /dev/null differ diff --git a/assets/images/templates/cooking/cooking_icon_arrow.png b/assets/images/templates/cooking/cooking_icon_arrow.png new file mode 100644 index 000000000000..454083ea46d4 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_arrow.png differ diff --git a/assets/images/templates/cooking/cooking_icon_artichoke.png b/assets/images/templates/cooking/cooking_icon_artichoke.png new file mode 100644 index 000000000000..f10683bcedce Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_artichoke.png differ diff --git a/assets/images/templates/cooking/cooking_icon_carrot.png b/assets/images/templates/cooking/cooking_icon_carrot.png new file mode 100644 index 000000000000..efb3ec40865b Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_carrot.png differ diff --git a/assets/images/templates/cooking/cooking_icon_eggplant.png b/assets/images/templates/cooking/cooking_icon_eggplant.png new file mode 100644 index 000000000000..d4d44088fc78 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_eggplant.png differ diff --git a/assets/images/templates/cooking/cooking_icon_facebook.png b/assets/images/templates/cooking/cooking_icon_facebook.png new file mode 100644 index 000000000000..15876737b07e Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_facebook.png differ diff --git a/assets/images/templates/cooking/cooking_icon_garlic.png b/assets/images/templates/cooking/cooking_icon_garlic.png new file mode 100644 index 000000000000..baa4c1381d31 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_garlic.png differ diff --git a/assets/images/templates/cooking/cooking_icon_instagram.png b/assets/images/templates/cooking/cooking_icon_instagram.png new file mode 100644 index 000000000000..46ef79900b14 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_instagram.png differ diff --git a/assets/images/templates/cooking/cooking_icon_lettuce.png b/assets/images/templates/cooking/cooking_icon_lettuce.png new file mode 100644 index 000000000000..69335d9dc373 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_lettuce.png differ diff --git a/assets/images/templates/cooking/cooking_icon_logo.png b/assets/images/templates/cooking/cooking_icon_logo.png new file mode 100644 index 000000000000..151089f9705e Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_logo.png differ diff --git a/assets/images/templates/cooking/cooking_icon_mushroom.png b/assets/images/templates/cooking/cooking_icon_mushroom.png new file mode 100644 index 000000000000..53940e8cad3f Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_mushroom.png differ diff --git a/assets/images/templates/cooking/cooking_icon_persimmon.png b/assets/images/templates/cooking/cooking_icon_persimmon.png new file mode 100644 index 000000000000..9d64263ce512 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_persimmon.png differ diff --git a/assets/images/templates/cooking/cooking_icon_pickle.png b/assets/images/templates/cooking/cooking_icon_pickle.png new file mode 100644 index 000000000000..df4c832ab0cf Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_pickle.png differ diff --git a/assets/images/templates/cooking/cooking_icon_radish.png b/assets/images/templates/cooking/cooking_icon_radish.png new file mode 100644 index 000000000000..85112dea107e Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_radish.png differ diff --git a/assets/images/templates/cooking/cooking_icon_salad.png b/assets/images/templates/cooking/cooking_icon_salad.png new file mode 100644 index 000000000000..125c97440938 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_salad.png differ diff --git a/assets/images/templates/cooking/cooking_icon_twitter.png b/assets/images/templates/cooking/cooking_icon_twitter.png new file mode 100644 index 000000000000..45fc8b59605d Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_twitter.png differ diff --git a/assets/images/templates/cooking/cooking_icon_up_arrow.png b/assets/images/templates/cooking/cooking_icon_up_arrow.png new file mode 100644 index 000000000000..63a900f39553 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_up_arrow.png differ diff --git a/assets/images/templates/cooking/cooking_icon_youtube.png b/assets/images/templates/cooking/cooking_icon_youtube.png new file mode 100644 index 000000000000..e65c65805a34 Binary files /dev/null and b/assets/images/templates/cooking/cooking_icon_youtube.png differ diff --git a/assets/images/templates/cooking/cooking_page1_bg.jpg b/assets/images/templates/cooking/cooking_page1_bg.jpg new file mode 100644 index 000000000000..2e7ce7f5550d Binary files /dev/null and b/assets/images/templates/cooking/cooking_page1_bg.jpg differ diff --git a/assets/images/templates/cooking/cooking_page3_image1.jpg b/assets/images/templates/cooking/cooking_page3_image1.jpg new file mode 100644 index 000000000000..72bbb08ee05a Binary files /dev/null and b/assets/images/templates/cooking/cooking_page3_image1.jpg differ diff --git a/assets/images/templates/cooking/cooking_page5_bg.jpg b/assets/images/templates/cooking/cooking_page5_bg.jpg new file mode 100644 index 000000000000..c1980be0b383 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page5_bg.jpg differ diff --git a/assets/images/templates/cooking/cooking_page7_bg.jpg b/assets/images/templates/cooking/cooking_page7_bg.jpg new file mode 100644 index 000000000000..bade0734fa3c Binary files /dev/null and b/assets/images/templates/cooking/cooking_page7_bg.jpg differ diff --git a/assets/images/templates/cooking/cooking_page8_bg.jpg b/assets/images/templates/cooking/cooking_page8_bg.jpg new file mode 100644 index 000000000000..964a42eb9e56 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page8_bg.jpg differ diff --git a/assets/images/templates/cooking/cooking_page9_image1.jpg b/assets/images/templates/cooking/cooking_page9_image1.jpg new file mode 100644 index 000000000000..5e34a6497ce8 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page9_image1.jpg differ diff --git a/assets/images/templates/cooking/cooking_page9_image2.jpg b/assets/images/templates/cooking/cooking_page9_image2.jpg new file mode 100644 index 000000000000..9eb3bae8a8b5 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page9_image2.jpg differ diff --git a/assets/images/templates/cooking/cooking_page9_image3.jpg b/assets/images/templates/cooking/cooking_page9_image3.jpg new file mode 100644 index 000000000000..eb712a545632 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page9_image3.jpg differ diff --git a/assets/images/templates/cooking/cooking_page9_image4.jpg b/assets/images/templates/cooking/cooking_page9_image4.jpg new file mode 100644 index 000000000000..b1655d648e50 Binary files /dev/null and b/assets/images/templates/cooking/cooking_page9_image4.jpg differ diff --git a/assets/images/templates/ent/ent_icon_fb.png b/assets/images/templates/ent/ent_icon_fb.png new file mode 100644 index 000000000000..3f821dade03e Binary files /dev/null and b/assets/images/templates/ent/ent_icon_fb.png differ diff --git a/assets/images/templates/ent/ent_icon_gray_star.png b/assets/images/templates/ent/ent_icon_gray_star.png new file mode 100644 index 000000000000..48c3e225ab58 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_gray_star.png differ diff --git a/assets/images/templates/ent/ent_icon_insta.png b/assets/images/templates/ent/ent_icon_insta.png new file mode 100644 index 000000000000..c93f60033ae2 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_insta.png differ diff --git a/assets/images/templates/ent/ent_icon_logo.png b/assets/images/templates/ent/ent_icon_logo.png new file mode 100644 index 000000000000..b5fce765a079 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_logo.png differ diff --git a/assets/images/templates/ent/ent_icon_twtr.png b/assets/images/templates/ent/ent_icon_twtr.png new file mode 100644 index 000000000000..1e7c0e202abe Binary files /dev/null and b/assets/images/templates/ent/ent_icon_twtr.png differ diff --git a/assets/images/templates/ent/ent_icon_up_arrow.png b/assets/images/templates/ent/ent_icon_up_arrow.png new file mode 100644 index 000000000000..63a900f39553 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_up_arrow.png differ diff --git a/assets/images/templates/ent/ent_icon_white_star.png b/assets/images/templates/ent/ent_icon_white_star.png new file mode 100644 index 000000000000..bb032a405be4 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_white_star.png differ diff --git a/assets/images/templates/ent/ent_icon_yt.png b/assets/images/templates/ent/ent_icon_yt.png new file mode 100644 index 000000000000..e7eb02bfdf34 Binary files /dev/null and b/assets/images/templates/ent/ent_icon_yt.png differ diff --git a/assets/images/templates/ent/ent_page1_bg.jpg b/assets/images/templates/ent/ent_page1_bg.jpg new file mode 100644 index 000000000000..f18aac094083 Binary files /dev/null and b/assets/images/templates/ent/ent_page1_bg.jpg differ diff --git a/assets/images/templates/ent/ent_page2_image1.jpg b/assets/images/templates/ent/ent_page2_image1.jpg new file mode 100644 index 000000000000..9aaf09aa13e9 Binary files /dev/null and b/assets/images/templates/ent/ent_page2_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page4_image1.jpg b/assets/images/templates/ent/ent_page4_image1.jpg new file mode 100644 index 000000000000..eb8ff325c45c Binary files /dev/null and b/assets/images/templates/ent/ent_page4_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page5_image1.jpg b/assets/images/templates/ent/ent_page5_image1.jpg new file mode 100644 index 000000000000..87195824ea0a Binary files /dev/null and b/assets/images/templates/ent/ent_page5_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page6_image1.jpg b/assets/images/templates/ent/ent_page6_image1.jpg new file mode 100644 index 000000000000..3cc86c4864b3 Binary files /dev/null and b/assets/images/templates/ent/ent_page6_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page7_image1.jpg b/assets/images/templates/ent/ent_page7_image1.jpg new file mode 100644 index 000000000000..5402e3c8a1b0 Binary files /dev/null and b/assets/images/templates/ent/ent_page7_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page8_image1.jpg b/assets/images/templates/ent/ent_page8_image1.jpg new file mode 100644 index 000000000000..985085480c8b Binary files /dev/null and b/assets/images/templates/ent/ent_page8_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page8_image2.jpg b/assets/images/templates/ent/ent_page8_image2.jpg new file mode 100644 index 000000000000..e8a8d9342f69 Binary files /dev/null and b/assets/images/templates/ent/ent_page8_image2.jpg differ diff --git a/assets/images/templates/ent/ent_page9_image1.jpg b/assets/images/templates/ent/ent_page9_image1.jpg new file mode 100644 index 000000000000..8c72e1afac51 Binary files /dev/null and b/assets/images/templates/ent/ent_page9_image1.jpg differ diff --git a/assets/images/templates/ent/ent_page9_image2.jpg b/assets/images/templates/ent/ent_page9_image2.jpg new file mode 100644 index 000000000000..6cc5fa187c89 Binary files /dev/null and b/assets/images/templates/ent/ent_page9_image2.jpg differ diff --git a/assets/images/templates/ent/ent_page9_image3.jpg b/assets/images/templates/ent/ent_page9_image3.jpg new file mode 100644 index 000000000000..c3bf1274581f Binary files /dev/null and b/assets/images/templates/ent/ent_page9_image3.jpg differ diff --git a/assets/images/templates/fashion/fashion_page1_hero.jpg b/assets/images/templates/fashion/fashion_page1_hero.jpg new file mode 100644 index 000000000000..07ce728298b1 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page1_hero.jpg differ diff --git a/assets/images/templates/fashion/fashion_page1_logotype.png b/assets/images/templates/fashion/fashion_page1_logotype.png new file mode 100644 index 000000000000..c6ebce4cc093 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page1_logotype.png differ diff --git a/assets/images/templates/fashion/fashion_page2_arrow.png b/assets/images/templates/fashion/fashion_page2_arrow.png new file mode 100644 index 000000000000..b8a6449c708d Binary files /dev/null and b/assets/images/templates/fashion/fashion_page2_arrow.png differ diff --git a/assets/images/templates/fashion/fashion_page2_pinwheel.png b/assets/images/templates/fashion/fashion_page2_pinwheel.png new file mode 100644 index 000000000000..252c6c3ec0e4 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page2_pinwheel.png differ diff --git a/assets/images/templates/fashion/fashion_page2_pinwheel_full.png b/assets/images/templates/fashion/fashion_page2_pinwheel_full.png new file mode 100644 index 000000000000..2a46049ab213 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page2_pinwheel_full.png differ diff --git a/assets/images/templates/fashion/fashion_page3_arrow.png b/assets/images/templates/fashion/fashion_page3_arrow.png new file mode 100644 index 000000000000..7822baa6a3e8 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page3_arrow.png differ diff --git a/assets/images/templates/fashion/fashion_page3_bg.jpg b/assets/images/templates/fashion/fashion_page3_bg.jpg new file mode 100644 index 000000000000..81d5f594def6 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page3_bg.jpg differ diff --git a/assets/images/templates/fashion/fashion_page5_arrow.png b/assets/images/templates/fashion/fashion_page5_arrow.png new file mode 100644 index 000000000000..66412c29c6f6 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page5_arrow.png differ diff --git a/assets/images/templates/fashion/fashion_page5_figure.jpg b/assets/images/templates/fashion/fashion_page5_figure.jpg new file mode 100644 index 000000000000..9f2e04d6a816 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page5_figure.jpg differ diff --git a/assets/images/templates/fashion/fashion_page5_handbag.png b/assets/images/templates/fashion/fashion_page5_handbag.png new file mode 100644 index 000000000000..3e9799f6b396 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page5_handbag.png differ diff --git a/assets/images/templates/fashion/fashion_page6_pinwheel-outline.png b/assets/images/templates/fashion/fashion_page6_pinwheel-outline.png new file mode 100644 index 000000000000..878a2e2d7245 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page6_pinwheel-outline.png differ diff --git a/assets/images/templates/fashion/fashion_page7_arrow.png b/assets/images/templates/fashion/fashion_page7_arrow.png new file mode 100644 index 000000000000..f903a8469e95 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page7_arrow.png differ diff --git a/assets/images/templates/fashion/fashion_page7_hero.jpg b/assets/images/templates/fashion/fashion_page7_hero.jpg new file mode 100644 index 000000000000..1c8d537645b1 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page7_hero.jpg differ diff --git a/assets/images/templates/fashion/fashion_page7_pinwheel.png b/assets/images/templates/fashion/fashion_page7_pinwheel.png new file mode 100644 index 000000000000..5f59dc31df41 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page7_pinwheel.png differ diff --git a/assets/images/templates/fashion/fashion_page8_arrow.png b/assets/images/templates/fashion/fashion_page8_arrow.png new file mode 100644 index 000000000000..269f17a1e20d Binary files /dev/null and b/assets/images/templates/fashion/fashion_page8_arrow.png differ diff --git a/assets/images/templates/fashion/fashion_page8_bg.jpg b/assets/images/templates/fashion/fashion_page8_bg.jpg new file mode 100644 index 000000000000..647081b3b322 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page8_bg.jpg differ diff --git a/assets/images/templates/fashion/fashion_page9_facebook.png b/assets/images/templates/fashion/fashion_page9_facebook.png new file mode 100755 index 000000000000..bd689de49f9f Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_facebook.png differ diff --git a/assets/images/templates/fashion/fashion_page9_instagram.png b/assets/images/templates/fashion/fashion_page9_instagram.png new file mode 100755 index 000000000000..76ed28a11bce Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_instagram.png differ diff --git a/assets/images/templates/fashion/fashion_page9_item1-cropped.png b/assets/images/templates/fashion/fashion_page9_item1-cropped.png new file mode 100644 index 000000000000..c318e3ac269d Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_item1-cropped.png differ diff --git a/assets/images/templates/fashion/fashion_page9_item1.jpg b/assets/images/templates/fashion/fashion_page9_item1.jpg new file mode 100644 index 000000000000..9885d91c120e Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_item1.jpg differ diff --git a/assets/images/templates/fashion/fashion_page9_item2.jpg b/assets/images/templates/fashion/fashion_page9_item2.jpg new file mode 100644 index 000000000000..b71e4eeb6e4e Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_item2.jpg differ diff --git a/assets/images/templates/fashion/fashion_page9_item3.jpg b/assets/images/templates/fashion/fashion_page9_item3.jpg new file mode 100644 index 000000000000..a4d0b4ad190b Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_item3.jpg differ diff --git a/assets/images/templates/fashion/fashion_page9_twitter.png b/assets/images/templates/fashion/fashion_page9_twitter.png new file mode 100755 index 000000000000..5b6544257df7 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_twitter.png differ diff --git a/assets/images/templates/fashion/fashion_page9_youtube.png b/assets/images/templates/fashion/fashion_page9_youtube.png new file mode 100755 index 000000000000..f42060d0dc42 Binary files /dev/null and b/assets/images/templates/fashion/fashion_page9_youtube.png differ diff --git a/assets/images/templates/fitness/fitness_page1_bg-alt.png b/assets/images/templates/fitness/fitness_page1_bg-alt.png new file mode 100644 index 000000000000..7261a9321713 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page1_bg-alt.png differ diff --git a/assets/images/templates/fitness/fitness_page1_bg.jpg b/assets/images/templates/fitness/fitness_page1_bg.jpg new file mode 100644 index 000000000000..01e594e52619 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page1_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page1_logo.png b/assets/images/templates/fitness/fitness_page1_logo.png new file mode 100644 index 000000000000..83e0f7676f6c Binary files /dev/null and b/assets/images/templates/fitness/fitness_page1_logo.png differ diff --git a/assets/images/templates/fitness/fitness_page2_figure-formatted.png b/assets/images/templates/fitness/fitness_page2_figure-formatted.png new file mode 100644 index 000000000000..637cef9522d9 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page2_figure-formatted.png differ diff --git a/assets/images/templates/fitness/fitness_page2_figure.jpg b/assets/images/templates/fitness/fitness_page2_figure.jpg new file mode 100644 index 000000000000..85c49fec2f4e Binary files /dev/null and b/assets/images/templates/fitness/fitness_page2_figure.jpg differ diff --git a/assets/images/templates/fitness/fitness_page3_arrow.png b/assets/images/templates/fitness/fitness_page3_arrow.png new file mode 100644 index 000000000000..7822baa6a3e8 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page3_arrow.png differ diff --git a/assets/images/templates/fitness/fitness_page3_bg.jpg b/assets/images/templates/fitness/fitness_page3_bg.jpg new file mode 100644 index 000000000000..95e736e1fc09 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page3_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page4_bg.jpg b/assets/images/templates/fitness/fitness_page4_bg.jpg new file mode 100644 index 000000000000..76c459eafc01 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page4_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page5_bg.jpg b/assets/images/templates/fitness/fitness_page5_bg.jpg new file mode 100644 index 000000000000..5367a9a68529 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page5_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page6_bg.jpg b/assets/images/templates/fitness/fitness_page6_bg.jpg new file mode 100644 index 000000000000..e007556a8d94 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page6_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page6_watch.png b/assets/images/templates/fitness/fitness_page6_watch.png new file mode 100644 index 000000000000..47157da61b57 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page6_watch.png differ diff --git a/assets/images/templates/fitness/fitness_page7_bg-formatted.png b/assets/images/templates/fitness/fitness_page7_bg-formatted.png new file mode 100644 index 000000000000..56c854739fcc Binary files /dev/null and b/assets/images/templates/fitness/fitness_page7_bg-formatted.png differ diff --git a/assets/images/templates/fitness/fitness_page7_bg.jpg b/assets/images/templates/fitness/fitness_page7_bg.jpg new file mode 100644 index 000000000000..91f470d44a42 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page7_bg.jpg differ diff --git a/assets/images/templates/fitness/fitness_page8_Facebook.png b/assets/images/templates/fitness/fitness_page8_Facebook.png new file mode 100755 index 000000000000..ed6a3f3c0af7 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_Facebook.png differ diff --git a/assets/images/templates/fitness/fitness_page8_Instagram.png b/assets/images/templates/fitness/fitness_page8_Instagram.png new file mode 100755 index 000000000000..669786b85555 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_Instagram.png differ diff --git a/assets/images/templates/fitness/fitness_page8_Twitter.png b/assets/images/templates/fitness/fitness_page8_Twitter.png new file mode 100755 index 000000000000..40aa6d74e5e4 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_Twitter.png differ diff --git a/assets/images/templates/fitness/fitness_page8_Youtube.png b/assets/images/templates/fitness/fitness_page8_Youtube.png new file mode 100755 index 000000000000..21bbb16a9ac6 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_Youtube.png differ diff --git a/assets/images/templates/fitness/fitness_page8_offsetblock.png b/assets/images/templates/fitness/fitness_page8_offsetblock.png new file mode 100644 index 000000000000..b0f6ec232fac Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_offsetblock.png differ diff --git a/assets/images/templates/fitness/fitness_page8_story1-formatted.png b/assets/images/templates/fitness/fitness_page8_story1-formatted.png new file mode 100644 index 000000000000..a1334249b060 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story1-formatted.png differ diff --git a/assets/images/templates/fitness/fitness_page8_story1.jpg b/assets/images/templates/fitness/fitness_page8_story1.jpg new file mode 100644 index 000000000000..a2f0cc40fe48 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story1.jpg differ diff --git a/assets/images/templates/fitness/fitness_page8_story2-formatted.png b/assets/images/templates/fitness/fitness_page8_story2-formatted.png new file mode 100644 index 000000000000..526248ba9021 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story2-formatted.png differ diff --git a/assets/images/templates/fitness/fitness_page8_story2.jpg b/assets/images/templates/fitness/fitness_page8_story2.jpg new file mode 100644 index 000000000000..026a42cd8280 Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story2.jpg differ diff --git a/assets/images/templates/fitness/fitness_page8_story3-formatted.png b/assets/images/templates/fitness/fitness_page8_story3-formatted.png new file mode 100644 index 000000000000..320ab3851d9a Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story3-formatted.png differ diff --git a/assets/images/templates/fitness/fitness_page8_story3.jpg b/assets/images/templates/fitness/fitness_page8_story3.jpg new file mode 100644 index 000000000000..8fbdd754fbec Binary files /dev/null and b/assets/images/templates/fitness/fitness_page8_story3.jpg differ diff --git a/assets/src/dashboard/app/api/apiProvider.js b/assets/src/dashboard/app/api/apiProvider.js index 894d4712fd09..cba67898719b 100644 --- a/assets/src/dashboard/app/api/apiProvider.js +++ b/assets/src/dashboard/app/api/apiProvider.js @@ -24,16 +24,27 @@ import { createContext, useMemo } from 'react'; * Internal dependencies */ import { useConfig } from '../config'; +import dataAdapter from './wpAdapter'; import useFontApi from './useFontApi'; import useStoryApi from './useStoryApi'; import useTemplateApi from './useTemplateApi'; -import dataAdapter from './wpAdapter'; +import useTagsApi from './useTagsApi'; +import useCategoriesApi from './useCategoriesApi'; +import useUsersApi from './useUserApi'; export const ApiContext = createContext({ state: {}, actions: {} }); export default function ApiProvider({ children }) { const { api, editStoryURL, pluginDir } = useConfig(); + const { users, api: usersApi } = useUsersApi(dataAdapter, { + wpApi: api.users, + }); + const { tags, api: tagsApi } = useTagsApi(dataAdapter, { wpApi: api.tags }); + const { categories, api: categoriesApi } = useCategoriesApi(dataAdapter, { + wpApi: api.categories, + }); + const { templates, api: templateApi } = useTemplateApi(dataAdapter, { pluginDir, }); @@ -50,14 +61,32 @@ export default function ApiProvider({ children }) { state: { stories, templates, + tags, + categories, + users, }, actions: { storyApi, templateApi, fontApi, + tagsApi, + categoriesApi, + usersApi, }, }), - [stories, templates, storyApi, templateApi, fontApi] + [ + users, + categories, + tags, + stories, + templates, + storyApi, + templateApi, + fontApi, + tagsApi, + categoriesApi, + usersApi, + ] ); return {children}; diff --git a/assets/src/dashboard/app/api/fetchAllFromPages.js b/assets/src/dashboard/app/api/fetchAllFromPages.js new file mode 100644 index 000000000000..e1a6d9c0598c --- /dev/null +++ b/assets/src/dashboard/app/api/fetchAllFromPages.js @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies + */ +import queryString from 'query-string'; + +/** + * This function takes a response from the WordPress fetch and will query + * for every entity for every possible page. This is used for tags, categories, and users + * on the Dashboard where individual requests for each story on each layout would not + * be ideal. + * + * @param response The response from the initial wpFetch call + * @param dataAdapter The WP Data Adapter that will make the additional page fetch requests + * @param wpApi The base url for the fetch calls + * @return Promise A flattened array with all the entities + */ + +export default async function fetchAllFromTotalPages( + response, + dataAdapter, + wpApi +) { + const totalPages = parseInt(response.headers.get('X-WP-TotalPages')); + const additionalRequests = []; + + if (totalPages > 1) { + for (let i = 2; i <= totalPages; i++) { + additionalRequests.push( + dataAdapter.get( + queryString.stringifyUrl({ + url: wpApi, + query: { page: i }, + }) + ) + ); + } + } + try { + return (await Promise.all([response.json(), ...additionalRequests])).flat( + 1 + ); + } catch (e) { + return []; + } +} diff --git a/assets/src/dashboard/app/api/test/apiProvider.js b/assets/src/dashboard/app/api/test/apiProvider.js index 86857facf7ec..142a00af45ed 100644 --- a/assets/src/dashboard/app/api/test/apiProvider.js +++ b/assets/src/dashboard/app/api/test/apiProvider.js @@ -38,6 +38,9 @@ jest.mock('../wpAdapter', () => ({ { id: 123, status: 'published', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, title: { rendered: 'Carlos', raw: 'Carlos' }, story_data: { pages: [{ id: 1, elements: [] }] }, modified: '1970-01-01T00:00:00.000Z', @@ -50,6 +53,9 @@ jest.mock('../wpAdapter', () => ({ id: data.id || 456, status: 'published', title: { rendered: title, raw: title }, + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, story_data: { pages: [{ id: 1, elements: [] }] }, modified: '1970-01-01T00:00:00.000Z', }); @@ -87,10 +93,16 @@ describe('ApiProvider', () => { centerTargetAction: '', id: 123, modified: moment('1970-01-01T00:00:00.000Z'), + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, originalStoryData: { id: 123, modified: '1970-01-01T00:00:00.000Z', status: 'published', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, story_data: { pages: [ { @@ -153,10 +165,16 @@ describe('ApiProvider', () => { centerTargetAction: '', id: 123, modified: moment('1970-01-01T00:00:00.000Z'), + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, originalStoryData: { id: 123, modified: '1970-01-01T00:00:00.000Z', status: 'published', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, story_data: { pages: [ { @@ -208,8 +226,14 @@ describe('ApiProvider', () => { ], status: 'published', title: 'Carlos', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, originalStoryData: { story_data: { + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, pages: [ { elements: [], @@ -230,10 +254,16 @@ describe('ApiProvider', () => { centerTargetAction: '', id: 123, modified: moment('1970-01-01T00:00:00.000Z'), + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, originalStoryData: { id: 123, modified: '1970-01-01T00:00:00.000Z', status: 'published', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, story_data: { pages: [ { @@ -261,10 +291,16 @@ describe('ApiProvider', () => { centerTargetAction: '', id: 456, modified: moment('1970-01-01T00:00:00.000Z'), + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, originalStoryData: { id: 456, modified: '1970-01-01T00:00:00.000Z', status: 'published', + tags: [1, 2, 3], + categories: [4, 5, 6], + author: 1, story_data: { pages: [ { diff --git a/assets/src/dashboard/app/api/test/useCategoryApi.js b/assets/src/dashboard/app/api/test/useCategoryApi.js new file mode 100644 index 000000000000..693e8020ca9a --- /dev/null +++ b/assets/src/dashboard/app/api/test/useCategoryApi.js @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { act, renderHook } from '@testing-library/react-hooks'; +/** + * Internal dependencies + */ +import useCategoriesApi from '../useCategoriesApi'; +import wpAdapter from '../wpAdapter'; + +jest.mock('../wpAdapter', () => ({ + get: () => + Promise.resolve({ + headers: { + get: () => '1', + }, + json: () => + Promise.resolve([ + { + id: 13, + name: 'Music', + }, + { + id: 23, + name: 'Art', + }, + ]), + }), +})); + +describe('useCategoryApi', () => { + it('should return categories in state data when the API request is fired', async () => { + const { result } = renderHook(() => + useCategoriesApi(wpAdapter, { wpApi: 'categories' }) + ); + + await act(async () => { + await result.current.api.fetchCategories(); + }); + + expect(result.current.categories).toStrictEqual({ + 13: { + id: 13, + name: 'Music', + }, + 23: { + id: 23, + name: 'Art', + }, + }); + }); +}); diff --git a/assets/src/dashboard/app/api/test/useTagsApi.js b/assets/src/dashboard/app/api/test/useTagsApi.js new file mode 100644 index 000000000000..0948126629d3 --- /dev/null +++ b/assets/src/dashboard/app/api/test/useTagsApi.js @@ -0,0 +1,62 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { act, renderHook } from '@testing-library/react-hooks'; +/** + * Internal dependencies + */ +import useTagsApi from '../useTagsApi'; +import wpAdapter from '../wpAdapter'; + +jest.mock('../wpAdapter', () => ({ + get: () => + Promise.resolve({ + headers: { + get: () => '1', + }, + json: () => + Promise.resolve([ + { + id: 7, + name: 'Fun', + slug: 'fun', + }, + ]), + }), +})); + +describe('useTagsApi', () => { + it('should return tags in state data when the API request is fired', async () => { + const { result } = renderHook(() => + useTagsApi(wpAdapter, { wpApi: 'tags' }) + ); + + await act(async () => { + await result.current.api.fetchTags(); + }); + + expect(result.current.tags).toStrictEqual({ + 7: { + id: 7, + name: 'Fun', + slug: 'fun', + }, + }); + }); +}); diff --git a/assets/src/dashboard/app/api/test/useUserApi.js b/assets/src/dashboard/app/api/test/useUserApi.js new file mode 100644 index 000000000000..fba108400da8 --- /dev/null +++ b/assets/src/dashboard/app/api/test/useUserApi.js @@ -0,0 +1,60 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { act, renderHook } from '@testing-library/react-hooks'; +/** + * Internal dependencies + */ +import useUserApi from '../useUserApi'; +import wpAdapter from '../wpAdapter'; + +jest.mock('../wpAdapter', () => ({ + get: () => + Promise.resolve({ + headers: { + get: () => '1', + }, + json: () => + Promise.resolve([ + { + id: 9, + name: 'admin', + }, + ]), + }), +})); + +describe('useUserApi', () => { + it('should return user in state data when the API request is fired', async () => { + const { result } = renderHook(() => + useUserApi(wpAdapter, { wpApi: 'user' }) + ); + + await act(async () => { + await result.current.api.fetchUsers(); + }); + + expect(result.current.users).toStrictEqual({ + 9: { + id: 9, + name: 'admin', + }, + }); + }); +}); diff --git a/assets/src/dashboard/app/api/useCategoriesApi.js b/assets/src/dashboard/app/api/useCategoriesApi.js new file mode 100644 index 000000000000..411685fea62f --- /dev/null +++ b/assets/src/dashboard/app/api/useCategoriesApi.js @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useCallback, useEffect, useMemo, useState } from 'react'; + +/** + * Internal dependencies + */ +import queryString from 'query-string'; +import groupBy from '../../utils/groupBy'; +import fetchAllFromTotalPages from './fetchAllFromPages'; + +export default function useCategoriesApi(dataAdapter, { wpApi }) { + const [categories, setCategories] = useState({}); + const fetchCategories = useCallback(async () => { + try { + const response = await dataAdapter.get( + queryString.stringifyUrl({ + url: wpApi, + query: { per_page: 100 }, + }), + { + parse: false, + } + ); + + const categoriesJson = await fetchAllFromTotalPages( + response, + dataAdapter, + wpApi + ); + + setCategories( + groupBy( + categoriesJson.map(({ _links, ...category }) => category), + 'id' + ) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + setCategories({}); + } + }, [dataAdapter, wpApi]); + + useEffect(() => { + fetchCategories(); + }, [fetchCategories]); + + return useMemo( + () => ({ + api: { fetchCategories }, + categories, + }), + [fetchCategories, categories] + ); +} diff --git a/assets/src/dashboard/app/api/useStoryApi.js b/assets/src/dashboard/app/api/useStoryApi.js index 2a4a411baa28..b44cb09410e4 100644 --- a/assets/src/dashboard/app/api/useStoryApi.js +++ b/assets/src/dashboard/app/api/useStoryApi.js @@ -48,6 +48,9 @@ export function reshapeStoryObject(editStoryURL) { title, modified, status, + tags, + categories, + author, story_data: storyData, } = originalStoryData; if ( @@ -63,6 +66,9 @@ export function reshapeStoryObject(editStoryURL) { title: title.rendered, modified: moment(modified), pages: storyData.pages, + tags, + categories, + author, centerTargetAction: '', bottomTargetAction: `${editStoryURL}&post=${id}`, originalStoryData, diff --git a/assets/src/dashboard/app/api/useTagsApi.js b/assets/src/dashboard/app/api/useTagsApi.js new file mode 100644 index 000000000000..cee769ccec7e --- /dev/null +++ b/assets/src/dashboard/app/api/useTagsApi.js @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useCallback, useEffect, useMemo, useState } from 'react'; + +/** + * Internal dependencies + */ +import queryString from 'query-string'; +import groupBy from '../../utils/groupBy'; +import fetchAllFromTotalPages from './fetchAllFromPages'; + +export default function useTagsApi(dataAdapter, { wpApi }) { + const [tags, setTags] = useState({}); + const fetchTags = useCallback(async () => { + try { + const response = await dataAdapter.get( + queryString.stringifyUrl({ + url: wpApi, + query: { per_page: 100 }, + }), + { + parse: false, + } + ); + + const tagsJson = await fetchAllFromTotalPages( + response, + dataAdapter, + wpApi + ); + + setTags( + groupBy( + tagsJson.map(({ _links, ...tag }) => tag), + 'id' + ) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + setTags({}); + } + }, [dataAdapter, wpApi]); + + useEffect(() => { + fetchTags(); + }, [fetchTags]); + + return useMemo( + () => ({ + api: { fetchTags }, + tags, + }), + [fetchTags, tags] + ); +} diff --git a/assets/src/dashboard/app/api/useTemplateApi.js b/assets/src/dashboard/app/api/useTemplateApi.js index 93a1b4580321..41fdfd3cf644 100644 --- a/assets/src/dashboard/app/api/useTemplateApi.js +++ b/assets/src/dashboard/app/api/useTemplateApi.js @@ -159,25 +159,51 @@ const useTemplateApi = (dataAdapter, config) => { } }, []); + const createTemplateFromStory = useCallback(async (story) => { + // api call to create a template from a story + await dispatch({ + type: TEMPLATE_ACTION_TYPES.CREATE_TEMPLATE_FROM_STORY, + }); + }, []); + + const fetchRelatedTemplates = useCallback(() => { + if (!state.templates) { + return []; + } + // this will return anywhere between 1 and 5 "related" templates + const randomStartingIndex = Math.floor( + Math.random() * state.templatesOrderById.length + ); + return [...state.templatesOrderById] + .splice(randomStartingIndex, 5) + .map((id) => { + return state.templates[id]; + }); + }, [state.templatesOrderById, state.templates]); + const api = useMemo( () => ({ bookmarkTemplateById, createStoryFromTemplatePages, + createTemplateFromStory, fetchBookmarkedTemplates, - fetchSavedTemplates, - fetchMyTemplates, - fetchMyTemplateById, fetchExternalTemplates, fetchExternalTemplateById, + fetchMyTemplates, + fetchMyTemplateById, + fetchRelatedTemplates, + fetchSavedTemplates, }), [ bookmarkTemplateById, createStoryFromTemplatePages, + createTemplateFromStory, fetchBookmarkedTemplates, fetchExternalTemplateById, fetchExternalTemplates, - fetchMyTemplateById, fetchMyTemplates, + fetchMyTemplateById, + fetchRelatedTemplates, fetchSavedTemplates, ] ); diff --git a/assets/src/dashboard/app/api/useUserApi.js b/assets/src/dashboard/app/api/useUserApi.js new file mode 100644 index 000000000000..330e6a7cdbf8 --- /dev/null +++ b/assets/src/dashboard/app/api/useUserApi.js @@ -0,0 +1,73 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useCallback, useEffect, useMemo, useState } from 'react'; + +/** + * Internal dependencies + */ +import queryString from 'query-string'; +import groupBy from '../../utils/groupBy'; +import fetchAllFromTotalPages from './fetchAllFromPages'; + +export default function useUsersApi(dataAdapter, { wpApi }) { + const [users, setUsers] = useState({}); + const fetchUsers = useCallback(async () => { + try { + const response = await dataAdapter.get( + queryString.stringifyUrl({ + url: wpApi, + query: { per_page: 100 }, + }), + { + parse: false, + } + ); + + const usersJson = await fetchAllFromTotalPages( + response, + dataAdapter, + wpApi + ); + + setUsers( + groupBy( + usersJson.map(({ _links, ...user }) => user), + 'id' + ) + ); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + setUsers({}); + } + }, [dataAdapter, wpApi]); + + useEffect(() => { + fetchUsers(); + }, [fetchUsers]); + + return useMemo( + () => ({ + api: { fetchUsers }, + users, + }), + [fetchUsers, users] + ); +} diff --git a/assets/src/dashboard/app/reducer/templates.js b/assets/src/dashboard/app/reducer/templates.js index 3cb9894c8db4..2ce3b93c14a4 100644 --- a/assets/src/dashboard/app/reducer/templates.js +++ b/assets/src/dashboard/app/reducer/templates.js @@ -20,6 +20,7 @@ import groupBy from '../../utils/groupBy'; export const ACTION_TYPES = { + CREATE_TEMPLATE_FROM_STORY: 'create_template_from_story', LOADING_TEMPLATES: 'loading_templates', FETCH_TEMPLATES_SUCCESS: 'fetch_templates_success', FETCH_TEMPLATES_FAILURE: 'fetch_templates_failure', @@ -38,6 +39,9 @@ export const defaultTemplatesState = { function templateReducer(state, action) { switch (action.type) { + case ACTION_TYPES.CREATE_TEMPLATE_FROM_STORY: { + return state; + } case ACTION_TYPES.LOADING_TEMPLATES: { return { ...state, diff --git a/assets/src/dashboard/app/reducer/test/templates.js b/assets/src/dashboard/app/reducer/test/templates.js index 9424d181e1cb..1729d4bd33df 100644 --- a/assets/src/dashboard/app/reducer/test/templates.js +++ b/assets/src/dashboard/app/reducer/test/templates.js @@ -182,4 +182,13 @@ describe('templateReducer', () => { isError: true, }); }); + + it(`should return the existing state when ${ACTION_TYPES.CREATE_TEMPLATE_FROM_STORY} is called`, () => { + const result = templateReducer( + { ...initialState }, + { type: ACTION_TYPES.CREATE_TEMPLATE_FROM_STORY } + ); + + expect(result).toMatchObject(initialState); + }); }); diff --git a/assets/src/dashboard/app/views/myStories/index.js b/assets/src/dashboard/app/views/myStories/index.js index 0d17abbee8b1..ac098fc0cf20 100644 --- a/assets/src/dashboard/app/views/myStories/index.js +++ b/assets/src/dashboard/app/views/myStories/index.js @@ -53,6 +53,7 @@ import { NoResults, StoryGridView, StoryListView, + HeaderToggleButtonContainer, } from '../shared'; const DefaultBodyText = styled.p` @@ -88,6 +89,7 @@ function MyStories() { const { actions: { storyApi: { updateStory, fetchStories, trashStory, duplicateStory }, + templateApi: { createTemplateFromStory }, }, state: { stories: { @@ -98,6 +100,9 @@ function MyStories() { totalStories, totalPages, }, + tags, + categories, + users, }, } = useContext(ApiContext); @@ -186,8 +191,9 @@ function MyStories() { @@ -200,11 +206,14 @@ function MyStories() { case VIEW_STYLE.LIST: return ( ); default: @@ -212,6 +221,7 @@ function MyStories() { } }, [ duplicateStory, + createTemplateFromStory, trashStory, viewStyle, updateStory, @@ -219,6 +229,9 @@ function MyStories() { currentStorySort, currentListSortDirection, handleNewStorySort, + tags, + categories, + users, ]); const storiesViewControls = useMemo(() => { @@ -284,21 +297,23 @@ function MyStories() { - { - return { - handleClick: () => - handleFilterStatusUpdate(storyStatus.value), - key: storyStatus.value, - isActive: status === storyStatus.value, - text: storyStatus.label, - }; - })} - /> + + { + return { + handleClick: () => + handleFilterStatusUpdate(storyStatus.value), + key: storyStatus.value, + isActive: status === storyStatus.value, + text: storyStatus.label, + }; + })} + /> + {storiesViewControls} diff --git a/assets/src/dashboard/app/views/shared/bodyViewOptions.js b/assets/src/dashboard/app/views/shared/bodyViewOptions.js index 8d0d95f5d08c..f826565e7411 100644 --- a/assets/src/dashboard/app/views/shared/bodyViewOptions.js +++ b/assets/src/dashboard/app/views/shared/bodyViewOptions.js @@ -74,6 +74,7 @@ const BodyViewOptions = ({ {layoutStyle === VIEW_STYLE.GRID && ( + centerContent ? 'center' : 'flex-end'}; height: 100%; `; @@ -68,22 +70,31 @@ const SearchInner = styled.div` top: 0; right: 0; width: min(190px, 100%); + display: flex; + justify-content: flex-end; `; const HeadingBodyWrapper = styled(BodyWrapper)` display: grid; grid-template-columns: 25% 50% 1fr; - align-items: start; + align-items: center; height: 75px; padding-bottom: 3px; border-bottom: ${({ theme }) => theme.subNavigationBar.border}; `; +export const HeaderToggleButtonContainer = styled.div` + display: block; + flex: 1; + height: 65%; +`; + const PageHeading = ({ children, defaultTitle, searchPlaceholder, - filteredStories = [], + centerContent = false, + stories = [], handleTypeaheadChange, typeaheadValue = '', }) => { @@ -94,13 +105,13 @@ const PageHeading = ({ {defaultTitle} - {children} + {children} @@ -115,9 +126,10 @@ PageHeading.propTypes = { PropTypes.arrayOf(PropTypes.node), PropTypes.node, ]), + centerContent: PropTypes.bool, defaultTitle: PropTypes.string.isRequired, searchPlaceholder: PropTypes.string, - filteredStories: StoriesPropType, + stories: StoriesPropType, handleTypeaheadChange: PropTypes.func, typeaheadValue: PropTypes.string, }; diff --git a/assets/src/dashboard/app/views/shared/stories/pageHeading.js b/assets/src/dashboard/app/views/shared/stories/pageHeading.js new file mode 100644 index 000000000000..ef74e5289787 --- /dev/null +++ b/assets/src/dashboard/app/views/shared/stories/pageHeading.js @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { boolean, text } from '@storybook/addon-knobs'; +import { action } from '@storybook/addon-actions'; +import styled from 'styled-components'; + +/** + * Internal dependencies + */ +import { PageHeading } from '../'; + +export default { + title: 'Dashboard/Components/PageHeading', + component: PageHeading, +}; + +const InnerContent = styled.div` + background-color: red; + width: 100%; + height: 50%; +`; + +export const _default = () => { + return ( + action('Search with value: ', value)} + defaultTitle={text('Page Heading', 'My Stories')} + searchPlaceholder={text('Search Placeholder', 'Find Stories')} + > + + + ); +}; diff --git a/assets/src/dashboard/app/views/shared/storyGridView.js b/assets/src/dashboard/app/views/shared/storyGridView.js index c47a3fd963e6..2901586fdbf5 100644 --- a/assets/src/dashboard/app/views/shared/storyGridView.js +++ b/assets/src/dashboard/app/views/shared/storyGridView.js @@ -55,9 +55,10 @@ const StoryGrid = styled(CardGrid)` `; const StoryGridView = ({ - filteredStories, + stories, centerActionLabel, bottomActionLabel, + createTemplateFromStory, updateStory, trashStory, duplicateStory, @@ -81,6 +82,10 @@ const StoryGridView = ({ duplicateStory(story); break; + case STORY_CONTEXT_MENU_ACTIONS.CREATE_TEMPLATE: + createTemplateFromStory(story); + break; + case STORY_CONTEXT_MENU_ACTIONS.DELETE: if ( window.confirm( @@ -99,7 +104,7 @@ const StoryGridView = ({ break; } }, - [trashStory, duplicateStory] + [createTemplateFromStory, duplicateStory, trashStory] ); const handleOnRenameStory = useCallback( @@ -112,7 +117,7 @@ const StoryGridView = ({ return ( - {filteredStories.map((story) => ( + {stories.map((story) => ( theme.previewWidth.thumbnail}px; - height: ${({ theme }) => theme.previewWidth.thumbnail * PAGE_RATIO}px; + height: ${({ theme }) => theme.previewWidth.thumbnail / PAGE_RATIO}px; vertical-align: middle; display: inline-block; `; @@ -78,7 +83,12 @@ const ArrowIcon = styled.div` `; const ArrowIconWithTitle = styled(ArrowIcon)` + display: ${({ active }) => (active ? 'inline' : 'none')}; margin-left: 15px; + + @media ${({ theme }) => theme.breakpoint.largeDisplayPhone} { + margin-left: 5px; + } `; const SelectableTitle = styled.span.attrs({ tabIndex: 0 })` @@ -92,13 +102,32 @@ const toggleSortLookup = { [SORT_DIRECTION.ASC]: SORT_DIRECTION.DESC, }; +const LastModifiedTableHeaderCell = styled(TableHeaderCell)` + min-width: 160px; +`; + +const AuthorTableHeaderCell = styled(TableHeaderCell)` + min-width: 110px; +`; + export default function StoryListView({ - filteredStories, + stories, storySort, handleSortChange, handleSortDirectionChange, sortDirection, + tags, + categories, + users, }) { + const metadataStringForIds = useCallback((metadata, ids) => { + const metadataString = ids + .reduce((memo, current) => [...memo, metadata[current]?.name], []) + .filter(Boolean) + .join(', '); + return metadataString === '' ? '—' : metadataString; + }, []); + const onSortTitleSelected = useCallback( (newStorySort) => { if (newStorySort !== storySort) { @@ -128,7 +157,7 @@ export default function StoryListView({ - + onSortTitleSelected(STORY_SORT_OPTIONS.CREATED_BY) @@ -142,29 +171,28 @@ export default function StoryListView({ > - + {__('Categories', 'web-stories')} {__('Tags', 'web-stories')} - + onSortTitleSelected(STORY_SORT_OPTIONS.LAST_MODIFIED) } > {__('Last Modified', 'web-stories')} + + + - - - - - + - {filteredStories.map((story) => ( + {stories.map((story) => ( @@ -174,9 +202,11 @@ export default function StoryListView({ {story.title} - {__('—', 'web-stories')} - {__('—', 'web-stories')} - {__('—', 'web-stories')} + {users[story.author].name} + + {metadataStringForIds(categories, story.categories)} + + {metadataStringForIds(tags, story.tags)} {story.modified.startOf('day').fromNow()} ))} @@ -187,7 +217,10 @@ export default function StoryListView({ } StoryListView.propTypes = { - filteredStories: StoriesPropType, + stories: StoriesPropType, + tags: TagsPropType, + categories: CategoriesPropType, + users: UsersPropType, handleSortChange: PropTypes.func.isRequired, handleSortDirectionChange: PropTypes.func.isRequired, storySort: PropTypes.string.isRequired, diff --git a/assets/src/dashboard/app/views/shared/typeaheadSearch.js b/assets/src/dashboard/app/views/shared/typeaheadSearch.js index 200d17cdc2fe..272935b2c0c2 100644 --- a/assets/src/dashboard/app/views/shared/typeaheadSearch.js +++ b/assets/src/dashboard/app/views/shared/typeaheadSearch.js @@ -28,17 +28,17 @@ export default function TypeaheadSearch({ placeholder = '', handleChange, currentValue = '', - filteredStories = [], + stories = [], }) { const typeaheadMenuOptions = useMemo(() => { // todo add different option sets, value and label won't always be the same - return filteredStories.map((filteredStory) => { + return stories.map((story) => { return { - label: filteredStory.title, - value: filteredStory.title, + label: story.title, + value: story.title, }; }); - }, [filteredStories]); + }, [stories]); return ( ` - padding: 0 ${theme.pageGutter.large.desktop}px; + padding-top: ${theme.navBar.height + 40}px; + margin: 0 ${theme.pageGutter.large.desktop}px; + @media ${theme.breakpoint.tablet} { - padding: 0 ${theme.pageGutter.large.tablet}px; + padding-top: ${theme.navBar.height + 20}px; + margin: 0 ${theme.pageGutter.large.tablet}px; } - @media ${theme.breakpoint.largeDisplayPhone} { - padding: 0; + @media ${theme.breakpoint.smallDisplayPhone} { + margin: 0 ${theme.pageGutter.small.min}px; } `} `; @@ -134,3 +137,19 @@ export const NavButton = styled(Button)` } `} `; + +export const RowContainer = styled.section` + display: flex; + flex-wrap: wrap; + padding-bottom: 40px; + margin: 0 20px 0; +`; + +export const SubHeading = styled.h2` + font-family: ${({ theme }) => theme.fonts.heading3.family}; + font-size: ${({ theme }) => theme.fonts.heading3.size}px; + line-height: ${({ theme }) => theme.fonts.heading3.lineHeight}px; + letter-spacing: ${({ theme }) => theme.fonts.heading3.letterSpacing}em; + font-weight: 500; + margin: 0 0 20px 0; +`; diff --git a/assets/src/dashboard/app/views/templates/detail.js b/assets/src/dashboard/app/views/templates/detail.js index e166e57ce64d..0210c8ff209c 100644 --- a/assets/src/dashboard/app/views/templates/detail.js +++ b/assets/src/dashboard/app/views/templates/detail.js @@ -33,6 +33,8 @@ import { ApiContext } from '../../api/apiProvider'; import { ReactComponent as LeftArrow } from '../../../icons/left-arrow.svg'; import { ReactComponent as RightArrow } from '../../../icons/right-arrow.svg'; import { TransformProvider } from '../../../../edit-story/components/transform'; +import { UnitsProvider } from '../../../../edit-story/units'; + import FontProvider from '../../font/fontProvider'; import { CardGallery, @@ -40,9 +42,12 @@ import { PreviewPage, Pill, TemplateNavBar, + Layout, } from '../../../components'; import { ICON_METRICS } from '../../../constants'; -import clamp from '../../../utils/clamp'; +import { clamp, usePagePreviewSize } from '../../../utils/'; +import { StoryGridView } from '../shared'; + import { ByLine, ContentContainer, @@ -51,13 +56,15 @@ import { DetailContainer, MetadataContainer, NavButton, + RowContainer, + SubHeading, Text, Title, } from './components'; function TemplateDetail() { const [template, setTemplate] = useState(null); - + const { pageSize } = usePagePreviewSize(); const { state: { queryParams: { id: templateId, isLocal }, @@ -69,7 +76,11 @@ function TemplateDetail() { templates: { templates, templatesOrderById }, }, actions: { - templateApi: { fetchMyTemplateById, fetchExternalTemplateById }, + templateApi: { + fetchMyTemplateById, + fetchExternalTemplateById, + fetchRelatedTemplates, + }, }, } = useContext(ApiContext); const { isRTL } = useConfig(); @@ -93,6 +104,10 @@ function TemplateDetail() { } }, [fetchMyTemplateById, fetchExternalTemplateById, templateId, isLocal]); + const relatedTemplates = useMemo(() => { + return fetchRelatedTemplates(); + }, [fetchRelatedTemplates]); + const orderedTemplates = useMemo(() => { return templatesOrderById.map((templateByOrderId) => { return templates[templateByOrderId]; @@ -186,39 +201,60 @@ function TemplateDetail() { template && ( - - - - - {PrevButton} - {previewPages} - - - - {template.title} - {byLine} - {template.description} - - {template.tags.map((tag) => ( - {}} - value={tag} - > - {tag} - - ))} - - - - - - {NextButton} - - - + + + + + + + + + {PrevButton} + {previewPages} + + + + {template.title} + {byLine} + {template.description} + + {template.tags.map((tag) => ( + {}} + value={tag} + > + {tag} + + ))} + + + + + + {NextButton} + + + {relatedTemplates.length > 0 && ( + + + {__('Related Templates', 'web-stories')} + + + + + + )} + + + ) diff --git a/assets/src/dashboard/app/views/templates/index.js b/assets/src/dashboard/app/views/templates/index.js index 4a9bf3e3f637..1077c38073d3 100644 --- a/assets/src/dashboard/app/views/templates/index.js +++ b/assets/src/dashboard/app/views/templates/index.js @@ -149,7 +149,7 @@ function TemplatesGallery() { return ( diff --git a/assets/src/dashboard/components/cardGallery/index.js b/assets/src/dashboard/components/cardGallery/index.js index 8804e9f6e058..86dd052e2e56 100644 --- a/assets/src/dashboard/components/cardGallery/index.js +++ b/assets/src/dashboard/components/cardGallery/index.js @@ -62,7 +62,7 @@ function CardGallery({ children }) { const activeCardSize = useMemo( () => ({ width: activeCardWidth, - height: activeCardWidth * PAGE_RATIO, + height: activeCardWidth / PAGE_RATIO, }), [activeCardWidth] ); @@ -70,7 +70,7 @@ function CardGallery({ children }) { const miniCardSize = useMemo( () => ({ width: miniCardWidth, - height: miniCardWidth * PAGE_RATIO, + height: miniCardWidth / PAGE_RATIO, }), [miniCardWidth] ); @@ -78,7 +78,7 @@ function CardGallery({ children }) { const miniWrapperCardSize = useMemo( () => ({ width: miniCardWidth + CARD_WRAPPER_BUFFER, - height: miniCardWidth * PAGE_RATIO + CARD_WRAPPER_BUFFER, + height: miniCardWidth / PAGE_RATIO + CARD_WRAPPER_BUFFER, }), [miniCardWidth] ); diff --git a/assets/src/dashboard/components/cardGrid/index.js b/assets/src/dashboard/components/cardGrid/index.js index 7ccc2d7823ef..f6d5a2e62e6e 100644 --- a/assets/src/dashboard/components/cardGrid/index.js +++ b/assets/src/dashboard/components/cardGrid/index.js @@ -28,21 +28,22 @@ import usePagePreviewSize from '../../utils/usePagePreviewSize'; const DashboardGrid = styled.div` display: grid; width: 100%; - align-content: space-between; - grid-column-gap: 10px; + grid-column-gap: 1vw; grid-row-gap: 20px; grid-template-columns: ${({ columnWidth }) => `repeat(auto-fill, minmax(${columnWidth}px, 1fr))`}; - @media ${({ theme }) => theme.breakpoint.min} { - grid-template-columns: repeat(2, 1fr); - } + grid-template-rows: ${({ columnHeight }) => + `minmax(${columnHeight}px, auto)`}; `; const CardGrid = ({ children }) => { const { pageSize } = usePagePreviewSize(); - - return {children}; + return ( + + {children} + + ); }; CardGrid.propTypes = { diff --git a/assets/src/dashboard/components/cardGridItem/index.js b/assets/src/dashboard/components/cardGridItem/index.js index c6b4b382f264..9281f31d633a 100644 --- a/assets/src/dashboard/components/cardGridItem/index.js +++ b/assets/src/dashboard/components/cardGridItem/index.js @@ -23,15 +23,12 @@ import PropTypes from 'prop-types'; /** * Internal dependencies */ -import { CARD_TITLE_AREA_HEIGHT } from '../../constants'; import usePagePreviewSize from '../../utils/usePagePreviewSize'; import { MoreVerticalButton } from './cardItemMenu'; import { ActionLabel } from './types'; const StyledCard = styled.div` - margin: auto 0; - height: ${({ cardSize, isTemplate }) => - cardSize.height + (isTemplate ? 0 : CARD_TITLE_AREA_HEIGHT)}px; + margin: 0; width: ${({ cardSize }) => `${cardSize.width}px`}; display: flex; flex-direction: column; diff --git a/assets/src/dashboard/components/dropdown/index.js b/assets/src/dashboard/components/dropdown/index.js index d34ded517999..8a2f1214d14e 100644 --- a/assets/src/dashboard/components/dropdown/index.js +++ b/assets/src/dashboard/components/dropdown/index.js @@ -51,7 +51,7 @@ export const DropdownContainer = styled.div` const Label = styled.label` display: flex; flex-direction: column; - align-items: center; + align-items: ${({ alignment }) => alignment}; `; export const InnerDropdown = styled.button` @@ -123,6 +123,7 @@ const ClearButton = styled.div` `; const Dropdown = ({ + alignment = 'center', ariaLabel, items, disabled, @@ -179,7 +180,7 @@ const Dropdown = ({ return ( - diff --git a/assets/src/dashboard/components/typeaheadInput/stories/index.js b/assets/src/dashboard/components/typeaheadInput/stories/index.js index 25f44403573b..c305559bb4a7 100644 --- a/assets/src/dashboard/components/typeaheadInput/stories/index.js +++ b/assets/src/dashboard/components/typeaheadInput/stories/index.js @@ -20,6 +20,7 @@ import { action } from '@storybook/addon-actions'; import { boolean, number, text } from '@storybook/addon-knobs'; import { useState } from 'react'; +import styled from 'styled-components'; /** * Internal dependencies */ @@ -40,46 +41,32 @@ const items = [ { label: 'Basil', value: 'basil' }, { label: 'water', value: 'agua' }, ]; + +const Container = styled.div` + width: 400px; + margin: 50px 100px; + padding: 40px; + background-color: white; +`; export const _default = () => { const [value, setValue] = useState(''); return ( - { - if (!inputValue) { - setValue(''); - } - action(`input changed ${inputValue}`)(inputValue); - }} - maxItemsVisible={number('maxItemsVisible', 7)} - value={value} - placeholder={text('placeholder', 'Search Stories')} - ariaLabel={text('ariaLabel', 'my search for seasonings')} - disabled={boolean('disabled')} - /> - ); -}; - -export const _filterLoadedSearchItems = () => { - const [value, setValue] = useState('Sports'); - return ( - { - if (!inputValue) { - setValue(''); - } - action(`input changed ${inputValue}`)(inputValue); - setValue(inputValue); - }} - value={value} - maxItemsVisible={number('maxItemsVisible')} - placeholder={text('placeholder', 'Search Stories')} - ariaLabel={text('ariaLabel', 'my search for seasonings')} - disabled={boolean('disabled')} - /> + + { + if (!inputValue) { + setValue(''); + } + action(`input changed ${inputValue}`)(inputValue); + }} + maxItemsVisible={number('maxItemsVisible', 7)} + value={value} + placeholder={text('placeholder', 'Search Stories')} + ariaLabel={text('ariaLabel', 'my search for seasonings')} + disabled={boolean('disabled')} + /> + ); }; diff --git a/assets/src/dashboard/components/typeaheadOptions/index.js b/assets/src/dashboard/components/typeaheadOptions/index.js index e23651ad923f..5d99c4c3c741 100644 --- a/assets/src/dashboard/components/typeaheadOptions/index.js +++ b/assets/src/dashboard/components/typeaheadOptions/index.js @@ -28,36 +28,40 @@ import { KEYS, Z_INDEX } from '../../constants'; import { DROPDOWN_ITEM_PROP_TYPE } from '../types'; export const Menu = styled.ul` - width: 100%; - align-items: flex-start; - background-color: ${({ theme }) => theme.colors.white}; - display: flex; - flex-direction: column; - margin: 0; - opacity: ${({ isOpen }) => (isOpen ? 1 : 0)}; - overflow: hidden; - padding: 0; - pointer-events: ${({ isOpen }) => (isOpen ? 'auto' : 'none')}; - z-index: ${Z_INDEX.TYPEAHEAD_OPTIONS}; + ${({ theme, isOpen }) => ` + width: 100%; + align-items: flex-start; + background-color: ${theme.colors.white}; + box-shadow: ${theme.expandedTypeahead.boxShadow}; + border-radius: 5px; + display: flex; + flex-direction: column; + margin: 5px 0 0; + opacity: ${isOpen ? 1 : 0}; + overflow: hidden; + padding: 5px 0; + pointer-events: ${isOpen ? 'auto' : 'none'}; + z-index: ${Z_INDEX.TYPEAHEAD_OPTIONS}; + `} `; Menu.propTypes = { isOpen: PropTypes.bool, }; const MenuItem = styled.li` - padding: 14px 16px 14px 44px; - background: ${({ isHovering, theme }) => - isHovering ? theme.colors.gray50 : 'none'}; - color: ${({ theme }) => theme.colors.gray700}; - cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')}; - display: flex; - font-family: ${({ theme }) => theme.fonts.typeaheadOptions.family}; - font-size: ${({ theme }) => theme.fonts.typeaheadOptions.size}px; - line-height: ${({ theme }) => theme.fonts.typeaheadOptions.lineHeight}px; - font-weight: ${({ theme }) => theme.fonts.typeaheadOptions.weight}; - letter-spacing: ${({ theme }) => - theme.fonts.typeaheadOptions.letterSpacing}em; - width: 100%; + ${({ theme, isDisabled, isHovering }) => ` + padding: 10px 20px; + background: ${isHovering ? theme.colors.gray25 : 'none'}; + color: ${theme.colors.gray700}; + cursor: ${isDisabled ? 'default' : 'pointer'}; + display: flex; + font-family: ${theme.fonts.typeaheadOptions.family}; + font-size: ${theme.fonts.typeaheadOptions.size}px; + line-height: ${theme.fonts.typeaheadOptions.lineHeight}px; + font-weight: ${theme.fonts.typeaheadOptions.weight}; + letter-spacing: ${theme.fonts.typeaheadOptions.letterSpacing}em; + width: 100%; + `} `; MenuItem.propTypes = { diff --git a/assets/src/dashboard/constants/index.js b/assets/src/dashboard/constants/index.js index 17f23437c3b4..19a4bb478ede 100644 --- a/assets/src/dashboard/constants/index.js +++ b/assets/src/dashboard/constants/index.js @@ -58,7 +58,7 @@ export const Z_INDEX = { POPOVER_PANEL: 10, }; -export const PAGE_RATIO = PAGE_HEIGHT / PAGE_WIDTH; +export const PAGE_RATIO = PAGE_WIDTH / PAGE_HEIGHT; export const CARD_TITLE_AREA_HEIGHT = 80; export const APP_ROUTES = { @@ -156,7 +156,7 @@ export const STORY_SORT_OPTIONS = { DATE_CREATED: 'date', LAST_MODIFIED: 'modified', LAST_OPENED: 'modified', - CREATED_BY: 'author', + CREATED_BY: 'story_author', }; export const SORT_DIRECTION = { diff --git a/assets/src/dashboard/templates/data/cooking.js b/assets/src/dashboard/templates/data/cooking.js new file mode 100644 index 000000000000..e3aa2d665b73 --- /dev/null +++ b/assets/src/dashboard/templates/data/cooking.js @@ -0,0 +1,6698 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default function (imageBaseUrl) { + return { + version: 15, + pages: [ + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '900a850f-fb71-4262-84f0-d6b803224ac7', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: -1, + y: 251, + width: 441, + height: 410, + scale: 180, + focalX: 56.790123544773586, + focalY: 41.131267206826344, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '8f4fcc58-2834-46af-a2cb-64e139f333d5', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page1_bg.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 433, + title: 'cooking_page1_bg', + alt: 'cooking_page1_bg', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 68, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 0.88, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Food & Stuff', + x: 92, + y: 160, + width: 256, + height: 118, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'be108bc9-3a51-48c4-b495-24be6163e44c', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_logo.png`, + width: 56, + height: 12, + posterId: 0, + id: 466, + title: 'cooking_icon_logo', + alt: 'cooking_icon_logo', + local: false, + sizes: [], + }, + x: 165, + y: 59, + width: 111, + height: 24, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '0134d8b1-1c28-47d3-b7ba-1649a629173f', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '385e34b1-ed6f-4205-be75-c5158f29c34d', + id: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '385e34b1-ed6f-4205-be75-c5158f29c34d', + id: '3f594bd0-848d-454e-9a36-942e8b5771cc', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + x: 418, + y: 19.5, + }, + ], + backgroundElementId: '900a850f-fb71-4262-84f0-d6b803224ac7', + type: 'page', + id: 'dd6a669f-ff4b-4633-8eb4-c601e98b40f1', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: 'b8ea29e5-ce25-4204-8660-afe56bb6b952', + type: 'shape', + }, + { + opacity: 15, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 109, + y: -1, + width: 331, + height: 611, + scale: 110, + focalX: 65.53717192208168, + focalY: 54.5046354516614, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '26a03b9b-d49f-4f87-930d-9ac98798895f', + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_radish.png`, + width: 220, + height: 309, + posterId: 0, + id: 452, + title: 'cooking_icon_radish', + alt: 'cooking_icon_radish', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 30, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.2, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + '"Your body is not a temple, it\'s an amusement park. Enjoy the ride."', + x: 100, + y: 207, + width: 240, + height: 144, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '5ad5b20a-e1ef-4341-a9ea-afc442cca079', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.2, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '- Anthony Bourdain', + width: 279, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5ad5b20a-e1ef-4341-a9ea-afc442cca079', + id: 'd3bb1cdf-9ab7-4444-a71b-e4a5bfd27410', + x: 81, + y: 394, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '4a83ef25-717a-4c07-a385-6b9b2c0efe02', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: '2cfde52f-f9e7-41ec-bf3d-30562fcaa3a1', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '2883aa79-202b-48e4-b05a-c2755b36c863', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '8f82d839-f0dd-4462-a525-ea1ccc25490d', + x: 418, + y: 19.5, + }, + ], + backgroundElementId: 'b8ea29e5-ce25-4204-8660-afe56bb6b952', + backgroundOverlay: 'none', + type: 'page', + id: '434c112e-3463-4dc6-aa8e-cb97f13a4e8e', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '268f9f40-cce2-4d37-899e-ea2422f8df7b', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 0, + y: 0, + width: 441, + height: 422, + scale: 160, + focalX: 50.05854474590312, + focalY: 37.514436843109465, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '4b496f17-b72e-4ead-aaa9-92936156e01f', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page3_image1.jpg`, + width: 220, + height: 147, + posterId: 0, + id: 434, + title: 'cooking_page3_image1', + alt: 'cooking_page3_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '95089d25-0d42-46c0-a360-b614a903b126', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: '7bce701a-8cf9-436b-8cf9-d4395010d4fc', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '37f4c3ab-6ff0-460b-93ee-7ced9887cab8', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '6ca2862c-7890-4149-a810-9896c04f35da', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 332, + y: 495, + width: 87, + height: 147, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '43e46644-1442-41a2-9248-bb29050d806d', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '7bce701a-8cf9-436b-8cf9-d4395010d4fc', + id: '9a1692e1-638e-41ce-a8a9-178388ee628f', + x: 20, + y: 495, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 101, + y: 422, + width: 2, + height: 76, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '8c0f6617-cf5a-44d1-80bc-477390556ecd', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_persimmon.png`, + width: 220, + height: 228, + posterId: 0, + id: 450, + title: 'cooking_icon_persimmon', + alt: 'cooking_icon_persimmon', + local: false, + sizes: {}, + }, + x: 43, + y: 439, + width: 38, + height: 39, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'bb94cc1e-f262-41b6-9505-398a280d3777', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 30, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Dan Williams', + x: 103, + y: 444, + width: 317, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '6c29ba73-482b-4759-bbc0-2a503f075b61', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'We go behind the scene of his famous Soho restaurant, where Dan will show us what inspires him this season and how he plans his menu accordingly.', + x: 43, + y: 518, + width: 277, + height: 80, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '31272720-81e0-416c-b7e6-870d7c68bf81', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 10, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'VIEW\nMORE', + width: 87, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '31272720-81e0-416c-b7e6-870d7c68bf81', + id: 'd189d8f5-809a-4775-8043-4f1ff432a932', + x: 332, + y: 549, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_arrow.png`, + width: 100, + height: 47, + posterId: 0, + id: 494, + title: 'cooking_icon_arrow', + alt: 'cooking_icon_arrow', + local: false, + sizes: {}, + }, + x: 363.5, + y: 581, + width: 24, + height: 12, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '6d8358b9-7bf1-4982-ae88-963409073fcf', + }, + ], + backgroundElementId: '268f9f40-cce2-4d37-899e-ea2422f8df7b', + backgroundOverlay: 'none', + type: 'page', + id: 'a378f919-785d-4ff2-8f8c-e7922a221cbf', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '10cd0ef0-78ac-41f8-9e90-810376fb7a0e', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_radish.png`, + width: 220, + height: 309, + posterId: 0, + id: 452, + title: 'cooking_icon_radish', + alt: 'cooking_icon_radish', + local: false, + sizes: {}, + }, + x: 283, + y: 128, + width: 106, + height: 150, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'e353e648-0b11-469a-91c4-83aacbc7207a', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_carrot.png`, + width: 220, + height: 237, + posterId: 0, + id: 443, + title: 'cooking_icon_carrot', + alt: 'cooking_icon_carrot', + local: false, + sizes: {}, + }, + x: 104, + y: 223, + width: 189, + height: 205, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '17aace61-9107-41e8-a0a7-9f9beb47a6b4', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_persimmon.png`, + width: 220, + height: 228, + posterId: 0, + id: 450, + title: 'cooking_icon_persimmon', + alt: 'cooking_icon_persimmon', + local: false, + sizes: {}, + }, + x: 284, + y: 423, + width: 82, + height: 85, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '640880ab-32cf-4d53-a914-8632d1195af0', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 10, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_artichoke.png`, + width: 220, + height: 223, + posterId: 0, + id: 442, + title: 'cooking_icon_artichoke', + alt: 'cooking_icon_artichoke', + local: false, + sizes: {}, + }, + x: 80, + y: 503, + width: 150, + height: 153, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'd617989e-2925-4856-be17-29085bb495ed', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Radishes', + x: 103, + y: 169, + width: 235, + height: 59, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '30276475-9eaf-4f66-824b-4619e28b788f', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Carrots', + width: 235, + height: 60, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '30276475-9eaf-4f66-824b-4619e28b788f', + id: '2312c664-2f6e-4aa7-a179-2d76900b7ab1', + x: 103, + y: 292, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Persimmons', + width: 374, + height: 59, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '2312c664-2f6e-4aa7-a179-2d76900b7ab1', + id: '1ea46b67-82b6-43dc-9b68-761f60b23367', + x: 34, + y: 416, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Artichokes', + width: 374, + height: 60, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '1ea46b67-82b6-43dc-9b68-761f60b23367', + id: '429d7f44-4a17-4bbe-af08-6786b3b122f2', + x: 33, + y: 540, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '7723d0c5-df11-49b1-a88f-048e0bc7286a', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: '8e45fe3f-af60-4f01-bd74-2d2c51b25a8d', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '11587356-52ed-4c05-a2b7-2bc89e9a2a0c', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '9e86559e-a7a7-4b7d-9469-a16686c36589', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5516f7b8-05e0-4fab-8b52-9dcd8d8f346d', + id: '5fe7e903-115a-4e30-99fc-89216a2c16cc', + x: 20, + y: 106, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Currently in season', + width: 377, + height: 25, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'a6b673a3-db8d-48a2-9469-1892e6266eb3', + id: 'b4b4b005-1524-4a37-ba21-0ef518581240', + x: 27, + y: 51, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 24, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5fe7e903-115a-4e30-99fc-89216a2c16cc', + id: 'd3799e22-bfe3-4d56-8bb4-5f5603ecaeae', + x: 207.5, + y: 252, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 24, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'd3799e22-bfe3-4d56-8bb4-5f5603ecaeae', + id: 'ea9de973-d615-466a-bf7e-7f6539688a63', + x: 207.5, + y: 376, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 24, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'd3799e22-bfe3-4d56-8bb4-5f5603ecaeae', + id: '2645174d-c03f-4e45-b801-a92cf39cccfb', + x: 207.5, + y: 499, + }, + ], + backgroundElementId: '10cd0ef0-78ac-41f8-9e90-810376fb7a0e', + backgroundOverlay: 'none', + type: 'page', + id: 'e6314f4e-5a59-425c-a6a3-b0a3ecff301c', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 130, + focalX: 38.46153846153847, + focalY: 56.49222899862286, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page5_bg.jpg`, + width: 220, + height: 391, + posterId: 0, + id: 435, + title: 'cooking_page5_bg', + alt: 'cooking_page5_bg', + local: false, + sizes: {}, + }, + x: 46, + y: 77, + width: 220, + height: 391, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '85bf1bf4-8ef0-4408-9fd1-755769555106', + isBackground: true, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 126, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 0.84, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Pers\nimm\non', + x: 50, + y: 172, + width: 339, + height: 315, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'b93db3ac-4bbd-4189-90fa-c509f358a954', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 205.5, + y: 578, + width: 29, + height: 29, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: 'a2c52a8f-296a-4c66-a3dc-430ef4f3b702', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 700, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See Full Story', + x: 140, + y: 617, + width: 160, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '92cc9ea9-89dd-43ac-bb8c-e36762171caf', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_up_arrow.png`, + width: 150, + height: 69, + posterId: 0, + id: 404, + title: 'cooking_icon_up_arrow', + alt: 'cooking_icon_up_arrow', + local: false, + sizes: {}, + }, + x: 214, + y: 589.5, + width: 12, + height: 6, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'a7f0d6cf-06e0-4361-be20-a24efc459deb', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'The ultimate recipe guide on', + x: 27, + y: 51, + width: 377, + height: 25, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'a6b673a3-db8d-48a2-9469-1892e6266eb3', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: 'a199532b-80e1-4568-864b-42c3e283975d', + x: 20, + y: 552, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: '38860cbe-6dfb-4b1c-bad9-7a0258a8e59b', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 534, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: 'e3277a17-b6f8-4889-8c78-8285aad4c23a', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 2, + height: 534, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '6fd32a10-5666-4cca-b7e8-6fd8645da27f', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'a199532b-80e1-4568-864b-42c3e283975d', + id: '5516f7b8-05e0-4fab-8b52-9dcd8d8f346d', + x: 20, + y: 106, + }, + ], + backgroundElementId: '85bf1bf4-8ef0-4408-9fd1-755769555106', + backgroundOverlay: 'none', + type: 'page', + id: 'b08e5e3d-08c8-4865-b82f-20a04e8f6fc7', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '353201ca-58e7-487f-b6f7-6360c5b72aa9', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 140, + y: -140, + width: 160, + height: 440, + scale: 130, + focalX: 46.3086913086913, + focalY: 38.18681318681318, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '15c7cc7e-e144-43db-bb67-1a8d4b6a561e', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page5_bg.jpg`, + width: 220, + height: 391, + posterId: 0, + id: 435, + title: 'cooking_page5_bg', + alt: 'cooking_page5_bg', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '7a8b930e-43fd-4785-86f9-38956dafe911', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: '654892bd-8aa7-441a-afad-dedb0e856ca1', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '351448da-74b6-4e7a-9a3c-fd6317b84790', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '518b38fd-aa57-415f-9111-fdb7fcf90a58', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 20, + y: 581, + width: 400, + height: 60, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 10, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'DISCOVER THEM', + width: 100, + height: 10, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '962ba57a-da00-4ea0-8ac9-649c7996768d', + id: '7979d059-fcdd-4a43-b92c-a9700cab7519', + x: 145, + y: 605, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_arrow.png`, + width: 100, + height: 47, + posterId: 0, + id: 494, + title: 'cooking_icon_arrow', + alt: 'cooking_icon_arrow', + local: false, + sizes: {}, + }, + width: 24, + height: 12, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '6f01f79c-d867-4f6c-83d2-47ef1aa4c36b', + id: '574eeaef-2baf-4055-9bab-81c1caacb414', + x: 248.5, + y: 604, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: '80882443-6412-42c8-970e-256e81dee23d', + x: 19.5, + y: 518, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: 'bd8a801c-bbc8-492e-8f58-d68e658e0b93', + x: 19.5, + y: 458, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: 'a72655e5-ce7d-4deb-927d-4675ef2c8fdb', + x: 19.5, + y: 398, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: 'c23ab445-deae-4080-be05-c18988f6e12d', + x: 19.5, + y: 338, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: 'd7766bd8-dca6-4f13-aa5c-7985bc20add0', + x: 19.5, + y: 278, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '91a27bce-42e9-43c7-b6c3-b0891a89d19e', + id: '92a36a65-8b19-494f-90be-8446d6475079', + x: 19.5, + y: 218, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 422, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '351448da-74b6-4e7a-9a3c-fd6317b84790', + id: '5177f639-0462-43d9-9dbc-9833c1f38fcd', + x: 78, + y: 160, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Persimmon Salsa Tacos', + width: 277, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'b6d08619-ca6a-4466-8f40-e4845ec26eda', + id: '38be890d-5627-4041-b008-2f24707bf749', + x: 97, + y: 179, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Steamed Persimmon Pudding', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '38be890d-5627-4041-b008-2f24707bf749', + id: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + x: 97, + y: 239, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Persimmon and Brie Crostini', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + id: '260c32cf-161f-4d2c-9b0b-f9d04cbf57f9', + x: 97, + y: 299, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Persimmon, Beet, and Citrus Salad', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + id: '9aa100d8-d095-4c1a-8430-1b3a5bbbc5b4', + x: 97, + y: 359, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Persimmon and Bresaola Bites', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + id: 'e3681a5a-e2a2-4754-b9d2-5a466a46c218', + x: 97, + y: 419, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Squash Soup with Persimmon', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + id: '1094fc63-1577-4c24-a4b2-3b41857e6ac7', + x: 97, + y: 479, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Mixed Chicories with Persimmons', + width: 277, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '127f9ed4-f754-45f1-9bc5-60fee6cbc90c', + id: 'bb6e59b5-478b-40a9-a0ec-e08cc16cad71', + x: 97, + y: 539, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '38be890d-5627-4041-b008-2f24707bf749', + id: 'c65251ab-3347-4658-a18a-1425b12b7081', + x: 38.5, + y: 176, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '2', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: '3d65fd5b-fc80-4b1a-90ba-d866036d934a', + x: 38.5, + y: 236, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '3', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: '87a59266-ad7d-43cd-a626-0d2a0126828d', + x: 38.5, + y: 296, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '4', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: '050e9d9e-6b37-44ef-9f9c-a516ea88c8a4', + x: 38.5, + y: 356, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '5', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: '86a51872-f144-4332-8b93-13b189f1d30b', + x: 38.5, + y: 416, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '6', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: 'ac878a4a-4cf7-41be-8269-c1f317f3fef6', + x: 38.5, + y: 476, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '7', + width: 25, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c65251ab-3347-4658-a18a-1425b12b7081', + id: '0b59089d-80b3-4422-aff7-13f068d3051d', + x: 38.5, + y: 536, + }, + { + opacity: 25, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 42, + g: 41, + b: 40, + }, + }, + isFill: false, + x: 19, + y: 19, + width: 399, + height: 139, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'eb9552a0-9023-46e9-8440-84051e67e5d2', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Top 8', + x: 78, + y: 61, + width: 141, + height: 59, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'c0040186-618e-4b75-bbda-ec1803464e6e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'recipes with\npersimmon', + x: 229, + y: 70.5, + width: 135, + height: 40, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '88dd72fa-b6d4-4749-85f7-a5430f7e51c2', + }, + ], + backgroundElementId: '353201ca-58e7-487f-b6f7-6360c5b72aa9', + backgroundOverlay: 'none', + type: 'page', + id: '4ce57acc-c58e-40ab-8a3d-87f6dc5fb106', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '9b628714-3fa2-490a-b766-4cf019ebe394', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 441, + height: 422, + scale: 130, + focalX: 53.015125503173735, + focalY: 37.93949798730505, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page7_bg.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 436, + title: 'cooking_page7_bg', + alt: 'cooking_page7_bg', + local: false, + sizes: {}, + }, + basedOn: '4b496f17-b72e-4ead-aaa9-92936156e01f', + id: '6f97df5b-14e7-4b83-be99-b82cd767f0ff', + x: 0, + y: 0, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '3cae8a3b-13ae-49c3-b949-38766d0bdc6c', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: 'dd412ea4-30e9-4c05-a1fe-d89bbc11c56d', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '6657411b-58d6-42d6-8e38-9968c6ecbf8b', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: '1435c65b-9a69-4c5b-88aa-a4a6ae46e027', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 87, + height: 127, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '43e46644-1442-41a2-9248-bb29050d806d', + id: '41fd386c-f6cb-413d-9dd0-808ffaa7467a', + x: 332, + y: 515, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 10, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LEARN\nMORE', + width: 87, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'd189d8f5-809a-4775-8043-4f1ff432a932', + id: '962ba57a-da00-4ea0-8ac9-649c7996768d', + x: 332, + y: 558, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_arrow.png`, + width: 100, + height: 47, + posterId: 0, + id: 494, + title: 'cooking_icon_arrow', + alt: 'cooking_icon_arrow', + local: false, + sizes: {}, + }, + width: 24, + height: 12, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '6d8358b9-7bf1-4982-ae88-963409073fcf', + id: '6f01f79c-d867-4f6c-83d2-47ef1aa4c36b', + x: 363.5, + y: 590, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 30, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Squash Soup \nwith Persimmon', + width: 270, + height: 60, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '6c29ba73-482b-4759-bbc0-2a503f075b61', + id: 'f4b2e739-5428-4639-8b71-b25630dac775', + x: 41, + y: 438, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'dd412ea4-30e9-4c05-a1fe-d89bbc11c56d', + id: 'bfd310a8-f17d-48a7-a00c-b2ab435e4812', + x: 20, + y: 515, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 313, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'bfd310a8-f17d-48a7-a00c-b2ab435e4812', + id: 'f7e13d69-1243-487b-9dbe-10bc50bf30a3', + x: 20, + y: 578, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 127, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '6657411b-58d6-42d6-8e38-9968c6ecbf8b', + id: '6ca4f6e6-23c4-487a-8b8c-7fe1840f8d36', + x: 176, + y: 514.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 400, + fontSize: 12, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0.25, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'YIELD', + x: 41, + y: 530, + width: 78, + height: 12, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '33d395dd-462d-4457-97c0-7b82f8c7c5d8', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '4-6 people', + width: 78, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '33d395dd-462d-4457-97c0-7b82f8c7c5d8', + id: '6eff8e7e-7799-456b-9bd3-5abe4fffac5d', + x: 41, + y: 546, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 400, + fontSize: 12, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0.25, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TYPE', + width: 78, + height: 12, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '33d395dd-462d-4457-97c0-7b82f8c7c5d8', + id: 'a5f5f1d9-015f-4f74-8bb7-a92e3ec3ea9a', + x: 41, + y: 594, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Vegetarian', + width: 78, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '6eff8e7e-7799-456b-9bd3-5abe4fffac5d', + id: '5ac12eaa-a7f3-4ded-8273-ba0d9f73955e', + x: 41, + y: 608, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 400, + fontSize: 12, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0.25, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TIME', + width: 78, + height: 12, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'a5f5f1d9-015f-4f74-8bb7-a92e3ec3ea9a', + id: '296554e7-af85-41ab-8f40-44b091a1b3a8', + x: 198, + y: 533, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '40 minutes', + width: 98, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5ac12eaa-a7f3-4ded-8273-ba0d9f73955e', + id: 'b6d08619-ca6a-4466-8f40-e4845ec26eda', + x: 198, + y: 547, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 400, + fontSize: 12, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0.25, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LEVEL', + width: 78, + height: 12, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '296554e7-af85-41ab-8f40-44b091a1b3a8', + id: '36968290-c6c8-4de1-bb7b-b8faf7a5872e', + x: 198, + y: 593, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Beginner', + width: 78, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'b6d08619-ca6a-4466-8f40-e4845ec26eda', + id: 'a3d09960-be5c-4ce2-b6ef-669d2efa927b', + x: 198, + y: 607, + }, + ], + backgroundElementId: '9b628714-3fa2-490a-b766-4cf019ebe394', + backgroundOverlay: 'none', + type: 'page', + id: 'c0a40237-d827-4aa3-b3ca-01e668dc90aa', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '7805eaf2-f323-4b3e-8c2b-a328f3e95056', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 441, + height: 422, + scale: 170, + focalX: 54.81042662170661, + focalY: 45.77973362299256, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page8_bg.jpg`, + width: 220, + height: 147, + posterId: 0, + id: 437, + title: 'cooking_page8_bg', + alt: 'cooking_page8_bg', + local: false, + sizes: {}, + }, + basedOn: '6f97df5b-14e7-4b83-be99-b82cd767f0ff', + id: 'c9526803-1a13-4a9e-93ca-9f6d9b14cc61', + x: 0, + y: 0, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '448e1fa8-9dde-4d5c-a478-cea0042df8ea', + id: '00d11324-86be-4ab7-8985-86c7ccae5551', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f594bd0-848d-454e-9a36-942e8b5771cc', + id: 'fb9dab5e-83af-4883-b26a-ba5378c6aca9', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b289967b-0cd4-4bd6-bdbf-16dbd855bad7', + id: '5244c225-7140-424a-93dc-3dbc88ccea7e', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '0ffca772-2972-4c47-80d9-0ffc1c5a2457', + id: 'b6dcbfd4-c93e-4773-b459-029ffeb750df', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '9a1692e1-638e-41ce-a8a9-178388ee628f', + id: '71b9e393-acb5-4318-94d6-270b2485c274', + x: 20, + y: 495, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + x: 22, + y: 422, + width: 78, + height: 76, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '96433920-00d6-4bbd-ab58-91961e1b3631', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Prep the Squash', + width: 211, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '6c29ba73-482b-4759-bbc0-2a503f075b61', + id: '6b49808a-0de8-4ef8-87a0-0ee0781dab8a', + x: 153, + y: 448.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 30, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '3', + width: 76, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '6b49808a-0de8-4ef8-87a0-0ee0781dab8a', + id: '4377060a-594e-4d7d-a544-d6ab79b91c99', + x: 21, + y: 443, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit integer auctor sollicitudin dolor, vel lacinia mi vehicula sed. Sed semper tortor est, et scelerisque lorem finibus efficitur maecenas vel aliquet nisl curabitur sollicitudin libero.', + width: 359, + height: 94, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '31272720-81e0-416c-b7e6-870d7c68bf81', + id: 'e474a0ae-051d-4788-8578-b5888531dac8', + x: 40, + y: 519, + }, + ], + backgroundElementId: '7805eaf2-f323-4b3e-8c2b-a328f3e95056', + backgroundOverlay: 'none', + type: 'page', + id: '11f60c54-4345-4979-8f10-edf4b43c7f44', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 249, + b: 238, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '25cbd8d2-adac-46e7-8cbb-24ef7536a9f5', + type: 'shape', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 22.5, + y: 124, + width: 95, + height: 95, + scale: 150, + focalX: 66.39097470744551, + focalY: 64.85345906426349, + mask: { + type: 'rectangle', + }, + type: 'image', + id: 'f5d57c6a-fc29-4136-ba9d-08d3119d1cd2', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page9_image1.jpg`, + width: 220, + height: 294, + posterId: 0, + id: 438, + title: 'cooking_page9_image1', + alt: 'cooking_page9_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 95, + height: 95, + scale: 130, + focalX: 50, + focalY: 57.634470218345214, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page9_image2.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 439, + title: 'cooking_page9_image2', + alt: 'cooking_page9_image2', + local: false, + sizes: {}, + }, + basedOn: 'f5d57c6a-fc29-4136-ba9d-08d3119d1cd2', + id: '90256502-5ed3-4354-90f3-16ef1e0d1d3a', + x: 22.5, + y: 219, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 95, + height: 95, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page9_image3.jpg`, + width: 220, + height: 275, + posterId: 0, + id: 440, + title: 'cooking_page9_image3', + alt: 'cooking_page9_image3', + local: false, + sizes: {}, + }, + basedOn: '90256502-5ed3-4354-90f3-16ef1e0d1d3a', + id: '73ba04c5-2f32-44c3-9c96-a9300e9c4e89', + x: 22.5, + y: 313, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 95, + height: 95, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_page9_image4.jpg`, + width: 220, + height: 284, + posterId: 0, + id: 441, + title: 'cooking_page9_image4', + alt: 'cooking_page9_image4', + local: false, + sizes: {}, + }, + basedOn: '73ba04c5-2f32-44c3-9c96-a9300e9c4e89', + id: '2729ff59-dfa0-4d0d-9023-de6d26f812ac', + x: 22.5, + y: 409, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_facebook.png`, + width: 50, + height: 100, + posterId: 0, + id: 445, + title: 'cooking_icon_facebook', + alt: 'cooking_icon_facebook', + local: false, + sizes: {}, + }, + x: 58, + y: 597.25, + width: 11, + height: 22, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '914b9a00-7fe7-446b-bdd9-b367b05b7216', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_instagram.png`, + width: 150, + height: 150, + posterId: 0, + id: 447, + title: 'cooking_icon_instagram', + alt: 'cooking_icon_instagram', + local: false, + sizes: {}, + }, + x: 149, + y: 597.25, + width: 22, + height: 22, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '0ec93b14-267e-42f4-871f-315611fddf80', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_youtube.png`, + width: 100, + height: 68, + posterId: 0, + id: 455, + title: 'cooking_icon_youtube', + alt: 'cooking_icon_youtube', + local: false, + sizes: {}, + }, + x: 251, + y: 599.75, + width: 25, + height: 17, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'bf9bba0f-a1d2-4954-b532-183b0ed3e9cc', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/cooking/cooking_icon_twitter.png`, + width: 100, + height: 79, + posterId: 0, + id: 454, + title: 'cooking_icon_twitter', + alt: 'cooking_icon_twitter', + local: false, + sizes: {}, + }, + x: 356, + y: 600.75, + width: 24, + height: 19, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'a981c1f8-a8a8-40c8-8e34-0a3797ae53a6', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '00d11324-86be-4ab7-8985-86c7ccae5551', + id: 'f25afc55-eaff-42c7-9e55-516b41d28d89', + x: 20, + y: 640, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'fb9dab5e-83af-4883-b26a-ba5378c6aca9', + id: '58bd7344-869e-4dec-bcd3-a3821475b13e', + x: 20, + y: 19, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5244c225-7140-424a-93dc-3dbc88ccea7e', + id: '88fa9820-d868-42c9-9375-745429b4316f', + x: 20, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 621, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b6dcbfd4-c93e-4773-b459-029ffeb750df', + id: '5214e92c-b401-4f03-8d8d-53fdbf7e6800', + x: 418, + y: 19.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '58bd7344-869e-4dec-bcd3-a3821475b13e', + id: '689b8c53-e90a-41e2-9a26-ce5d03867b32', + x: 20, + y: 571, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '58bd7344-869e-4dec-bcd3-a3821475b13e', + id: '37cb19aa-cc36-4532-93e9-d92d83d3cea6', + x: 22, + y: 503, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '58bd7344-869e-4dec-bcd3-a3821475b13e', + id: '05d74b3b-e92d-4e3e-8220-d57581e0ff1f', + x: 20, + y: 408, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '05d74b3b-e92d-4e3e-8220-d57581e0ff1f', + id: 'e2495f7d-7d94-4c06-a049-475056594b43', + x: 20, + y: 313, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'e2495f7d-7d94-4c06-a049-475056594b43', + id: '5b373054-651c-4164-a378-fecd36d5d1c2', + x: 20, + y: 219, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 400, + height: 2, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5b373054-651c-4164-a378-fecd36d5d1c2', + id: 'adcf1924-1824-48dd-8924-0e1b4424e9e0', + x: 20, + y: 124, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Follow us', + width: 211, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '6b49808a-0de8-4ef8-87a0-0ee0781dab8a', + id: 'b6b37e1f-c36b-4d93-97d4-a3eff77486e6', + x: 114, + y: 528, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 700, + fontSize: 30, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'More stories', + width: 211, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'b6b37e1f-c36b-4d93-97d4-a3eff77486e6', + id: 'a2be26e3-7314-4fc5-aa65-c69d390a173f', + x: 118, + y: 58, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Our best taco recipes to date', + width: 250, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '38be890d-5627-4041-b008-2f24707bf749', + id: 'c4a34697-400c-4b45-8646-b81c1ada574f', + x: 143, + y: 163, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Comfort food for all occasions', + width: 250, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c4a34697-400c-4b45-8646-b81c1ada574f', + id: '8b2ad361-4db2-41ef-9f96-142b4f150556', + x: 143, + y: 258, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Desserts you can’t live without', + width: 250, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c4a34697-400c-4b45-8646-b81c1ada574f', + id: '55aae5bd-87d3-4ecb-add2-ed214ce75805', + x: 143, + y: 351, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Gelasio', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 103, + g: 100, + b: 97, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Easy brunch recipes that will impress your guests', + width: 186, + height: 40, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c4a34697-400c-4b45-8646-b81c1ada574f', + id: '21aed5d1-b061-4a20-87e8-3e9c9b804d41', + x: 143, + y: 437, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 68, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '8c0f6617-cf5a-44d1-80bc-477390556ecd', + id: '9a8efaef-377d-4d97-8c1c-60d85dfed9df', + x: 114, + y: 572.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 68, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '9a8efaef-377d-4d97-8c1c-60d85dfed9df', + id: '47ff94c1-a734-436a-abe2-5f45eafa83a3', + x: 316, + y: 572.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 146, + b: 46, + }, + }, + isFill: false, + width: 2, + height: 68, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '47ff94c1-a734-436a-abe2-5f45eafa83a3', + id: '099ba772-ae7b-46ae-a4b1-891ec1126b3f', + x: 215, + y: 572.5, + }, + ], + backgroundElementId: '25cbd8d2-adac-46e7-8cbb-24ef7536a9f5', + backgroundOverlay: 'none', + type: 'page', + id: 'e8a21c0b-1db7-4777-b25d-b7d9a51b10fe', + }, + ], + autoAdvance: true, + defaultPageDuration: 7, + }; +} diff --git a/assets/src/dashboard/templates/data/diy.js b/assets/src/dashboard/templates/data/diy.js index cb5d380e3caa..7cd2c88f2f7f 100644 --- a/assets/src/dashboard/templates/data/diy.js +++ b/assets/src/dashboard/templates/data/diy.js @@ -78,7 +78,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page1_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page1_bg.jpg`, width: 220, height: 330, posterId: 0, @@ -379,7 +379,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_org_cross.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_org_cross.png`, width: 150, height: 150, posterId: 0, @@ -467,7 +467,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page2_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page2_bg.jpg`, width: 220, height: 330, posterId: 0, @@ -654,7 +654,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page3_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page3_image1.jpg`, width: 220, height: 147, posterId: 0, @@ -956,7 +956,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page4_page5_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page4_page5_bg.jpg`, width: 220, height: 275, posterId: 0, @@ -1101,7 +1101,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page4_page5_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page4_page5_bg.jpg`, width: 220, height: 275, posterId: 0, @@ -1364,7 +1364,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_arrow.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_arrow.png`, width: 128, height: 128, posterId: 0, @@ -1410,7 +1410,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page6_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page6_bg.jpg`, width: 220, height: 147, posterId: 0, @@ -1673,7 +1673,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_wh_cross.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_wh_cross.png`, width: 150, height: 158, posterId: 0, @@ -1761,7 +1761,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -2012,7 +2012,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_up_arrow.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_up_arrow.png`, width: 150, height: 69, posterId: 0, @@ -2239,7 +2239,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page8_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page8_image1.jpg`, width: 220, height: 131, posterId: 0, @@ -2278,7 +2278,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page8_image2.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page8_image2.jpg`, width: 220, height: 147, posterId: 0, @@ -2324,7 +2324,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page8_image3.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page8_image3.jpg`, width: 220, height: 147, posterId: 0, @@ -2736,7 +2736,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_arrow.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_arrow.png`, width: 128, height: 128, posterId: 0, @@ -2782,7 +2782,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -3375,7 +3375,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -3414,7 +3414,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -3457,7 +3457,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -3500,7 +3500,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_page7_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_page7_bg.jpg`, width: 220, height: 124, posterId: 0, @@ -4026,7 +4026,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_fb.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_fb.png`, width: 150, height: 300, posterId: 0, @@ -4064,7 +4064,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_insta.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_insta.png`, width: 150, height: 150, posterId: 0, @@ -4102,7 +4102,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_yt.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_yt.png`, width: 150, height: 102, posterId: 0, @@ -4140,7 +4140,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/diy/diy_icon_tw.png`, + src: `${imageBaseUrl}assets/images/templates/diy/diy_icon_tw.png`, width: 151, height: 119, posterId: 0, diff --git a/assets/src/dashboard/templates/data/entertainment.js b/assets/src/dashboard/templates/data/entertainment.js new file mode 100644 index 000000000000..609dec42739e --- /dev/null +++ b/assets/src/dashboard/templates/data/entertainment.js @@ -0,0 +1,5312 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default function (imageBaseUrl) { + return { + version: 16, + pages: [ + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '17bad711-de31-4f09-8d30-f83ade1d6f2a', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 382, + height: 642, + scale: 270, + focalX: 41.91473010440877, + focalY: 20.55378631524039, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: 'ccb83cf6-b35b-41b1-bc5f-7f7383decc8a', + id: 'ce3de6dc-492d-47ab-aa0b-aa86cf1363cd', + x: 58, + y: 79, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page1_bg.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 528, + title: 'ent_page1_bg', + alt: 'ent_page1_bg', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 74, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.7, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'WEEKLY', + x: 92, + y: 0, + width: 292, + height: 51, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'c7a5dbe5-43f8-4510-8741-7e3900e3d487', + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 74, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.8, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'ENTERTAINMENT', + width: 626, + height: 59, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c7a5dbe5-43f8-4510-8741-7e3900e3d487', + id: '3970117d-3e7f-4eb0-94c6-a90873ba11c5', + x: -284, + y: 323.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 74, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.8, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'ENTERTAINMENT', + width: 626, + height: 59, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '3970117d-3e7f-4eb0-94c6-a90873ba11c5', + id: 'd3c1350e-1e2f-4f43-ae55-7fac7fd59932', + x: 101, + y: 278.5, + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 180, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 74, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.7, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'WEEKLY', + width: 292, + height: 51, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c7a5dbe5-43f8-4510-8741-7e3900e3d487', + id: '0b0ba715-00b7-4b12-8c51-2fa4035bca6b', + x: 58, + y: 609, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_logo.png`, + width: 150, + height: 146, + posterId: 0, + id: 542, + title: 'ent_icon_logo', + alt: 'ent_icon_logo', + local: false, + sizes: {}, + }, + x: 18, + y: 479, + width: 150, + height: 146, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '5090769d-8a55-4ef6-aff3-544872d653e4', + }, + ], + backgroundElementId: '17bad711-de31-4f09-8d30-f83ade1d6f2a', + type: 'page', + id: '04df622c-0d7f-4760-a06c-e88d9d9ac435', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '7a27a752-537d-4dcd-9677-5926437c0e19', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 59, + y: 94, + width: 381, + height: 628, + scale: 110, + focalX: 41.49743929933975, + focalY: 54.586814873401416, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '816de043-a84a-43af-af61-9a4d00a04e9b', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 529, + title: 'ent_page2_image1', + alt: 'ent_page2_image1', + local: false, + sizes: { + medium: { + file: 'ent_page2_image1-200x300.jpg', + width: 200, + height: 300, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-200x300.jpg`, + }, + large: { + file: 'ent_page2_image1-683x1024.jpg', + width: 683, + height: 1024, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-683x1024.jpg`, + }, + thumbnail: { + file: 'ent_page2_image1-150x150.jpg', + width: 150, + height: 150, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-150x150.jpg`, + }, + medium_large: { + file: 'ent_page2_image1-768x1152.jpg', + width: 768, + height: 1152, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-768x1152.jpg`, + }, + '1536x1536': { + file: 'ent_page2_image1-1024x1536.jpg', + width: 1024, + height: 1536, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-1024x1536.jpg`, + }, + '2048x2048': { + file: 'ent_page2_image1-1365x2048.jpg', + width: 1365, + height: 2048, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-1365x2048.jpg`, + }, + 'post-thumbnail': { + file: 'ent_page2_image1-1200x1800.jpg', + width: 1200, + height: 1800, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-1200x1800.jpg`, + }, + 'web-stories-poster-portrait': { + file: 'ent_page2_image1-696x928.jpg', + width: 696, + height: 928, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-696x928.jpg`, + }, + 'web-stories-poster-square': { + file: 'ent_page2_image1-928x928.jpg', + width: 928, + height: 928, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-928x928.jpg`, + }, + 'web-stories-poster-landscape': { + file: 'ent_page2_image1-928x696.jpg', + width: 928, + height: 696, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-928x696.jpg`, + }, + web_stories_thumbnail: { + file: 'ent_page2_image1-150x225.jpg', + width: 150, + height: 225, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-150x225.jpg`, + }, + full: { + file: 'ent_page2_image1-scaled.jpg', + width: 1707, + height: 2560, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page2_image1-scaled.jpg`, + }, + }, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 87, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 141, + g: 141, + b: 141, + }, + }, + letterSpacing: 0, + lineHeight: 0.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE TOP 10', + width: 483, + height: 21, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5bfb51c9-3d47-4a78-be2c-776db5143bc9', + id: '26df8c19-b913-441a-bff1-bf1b677cb159', + x: -21.5, + y: 639, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 87, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 199, + g: 196, + b: 196, + }, + }, + letterSpacing: 0, + lineHeight: 0.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE TOP 10', + width: 483, + height: 21, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5efefa69-c1d0-4331-905b-3936d866f0eb', + id: '5bfb51c9-3d47-4a78-be2c-776db5143bc9', + x: -21.5, + y: 610, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 87, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.8, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE TOP 10', + width: 483, + height: 69, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c7a5dbe5-43f8-4510-8741-7e3900e3d487', + id: '5efefa69-c1d0-4331-905b-3936d866f0eb', + x: -21.5, + y: 533, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'New Artists', + x: 67, + y: 33, + width: 204, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '74ea4aa6-1165-42cf-9849-90ab25e6b76e', + }, + ], + backgroundElementId: '7a27a752-537d-4dcd-9677-5926437c0e19', + backgroundOverlay: 'none', + type: 'page', + id: '9670b092-e11b-433e-aa8a-659e4ee982c9', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '03d49bf5-7b5e-4d7c-b09e-7c3ebde20c94', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 370, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 78, + g: 20, + b: 80, + a: 0.5, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'PINK', + width: 885, + height: 370, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '942add2b-e206-4802-bf78-64bd514ab574', + id: '741cf9c1-5c4e-400a-88d1-f8d85988d103', + x: -273, + y: 185, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 370, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 78, + g: 20, + b: 80, + a: 1, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'PINK', + width: 885, + height: 370, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '74ea4aa6-1165-42cf-9849-90ab25e6b76e', + id: '942add2b-e206-4802-bf78-64bd514ab574', + x: -314, + y: 172, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 184, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '"', + width: 81, + height: 184, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '74ea4aa6-1165-42cf-9849-90ab25e6b76e', + id: 'a8a8df4d-5750-4508-9698-69226f88a94f', + x: -1, + y: 0, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 58, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: "IS NOT\nJUST A\nCOLOR,\nIT'S AN\nATTITUDE!", + width: 296, + height: 283, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'a8a8df4d-5750-4508-9698-69226f88a94f', + id: '37fa4bdd-2898-4b95-bf2a-ae0d4e474f47', + x: 72, + y: 209, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 58, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 0, + b: 214, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'PINK', + width: 150, + height: 48, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '37fa4bdd-2898-4b95-bf2a-ae0d4e474f47', + id: '9de815af-b64d-4fda-966c-78f938df0f83', + x: 71, + y: 162, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '– Miley Cyrus', + width: 220, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '37fa4bdd-2898-4b95-bf2a-ae0d4e474f47', + id: '8bef4ffe-0a36-4444-ac4c-48b548217cb9', + x: 148, + y: 537, + }, + ], + backgroundElementId: '03d49bf5-7b5e-4d7c-b09e-7c3ebde20c94', + backgroundOverlay: 'none', + type: 'page', + id: '73e07f65-ae5a-4a85-8802-7ea5cfaa45b2', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '87fe44ec-dfa5-450c-8250-1859f8e4fe07', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 42, + y: -62, + width: 397, + height: 460, + scale: 100, + focalX: 50.04087693473734, + focalY: 55.96587929938168, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '7f6f6565-2e46-4ef5-964c-d6c44350416d', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page4_image1.jpg`, + width: 220, + height: 329, + posterId: 0, + id: 530, + title: 'ent_page4_image1', + alt: 'ent_page4_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 7, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 98, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'SPOTLIGHT', + width: 538, + height: 81, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '17fff8ec-3742-4837-bbcb-6f547744c46a', + id: '2ed3fa60-d97a-4641-b858-a024163090aa', + x: 136, + y: 350.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Toronto-based new rapper', + width: 291, + height: 64, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '8bef4ffe-0a36-4444-ac4c-48b548217cb9', + id: 'e23cd5e4-d0c9-436d-a1b1-b91dcf909373', + x: 42, + y: 436, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.43, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vel dui erat. Curabitur sit amet venenatis felis. In ac ornare lacus. Integer vitae lacus a lectus eleifend finibus.', + width: 355, + height: 80, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e23cd5e4-d0c9-436d-a1b1-b91dcf909373', + id: 'fc016a73-3d6f-43a3-8542-6de6651a2829', + x: 42, + y: 518, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 74, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'SPOTLIGHT', + width: 404, + height: 61, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e23cd5e4-d0c9-436d-a1b1-b91dcf909373', + id: '17fff8ec-3742-4837-bbcb-6f547744c46a', + x: -175, + y: 172, + }, + ], + backgroundElementId: '87fe44ec-dfa5-450c-8250-1859f8e4fe07', + backgroundOverlay: 'none', + type: 'page', + id: '582c5a25-6f17-4107-8e60-6ae4721c932f', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '923c69d0-84f6-4223-8f7e-8fd7f0d0363f', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 0, + b: 214, + }, + }, + isFill: false, + x: -1, + y: 0, + width: 52, + height: 289, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '07ca71a9-b60e-4373-8ee1-65be94c152f0', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 421, + height: 366, + scale: 110, + focalX: 42.70434047164958, + focalY: 45.387945387945386, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page5_image1.jpg`, + width: 220, + height: 212, + posterId: 0, + id: 531, + title: 'ent_page5_image1', + alt: 'ent_page5_image1', + local: false, + sizes: {}, + }, + basedOn: '7f6f6565-2e46-4ef5-964c-d6c44350416d', + id: '46058255-8bf6-4302-88eb-f9bd71304225', + x: 20, + y: 0, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'New Album Review', + width: 291, + height: 63, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e23cd5e4-d0c9-436d-a1b1-b91dcf909373', + id: 'fd434caf-7752-47d3-b41b-0a1048f116a8', + x: 39, + y: 396, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.43, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vel dui erat. Curabitur sit amet venenatis felis. In ac ornare lacus. Integer vitae lacus a lectus eleifend finibus.', + width: 355, + height: 75, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'fc016a73-3d6f-43a3-8542-6de6651a2829', + id: 'ff9a77cf-c942-4a04-84ea-d7134d97f360', + x: 39, + y: 488, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 82, + g: 82, + b: 82, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b6325820-9ffe-41d3-97e9-eb969ff9ef22', + id: '76c34633-deca-4d3b-ad89-9f8f787aabf3', + x: 300, + y: 598, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f8f943f-ca2c-4604-b9e7-d300c4b2b9d6', + id: '837db927-1ee6-4419-ace6-c59f83f0ca49', + x: 292, + y: 591.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 90, + height: 26, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'f2183556-61b5-46b5-b945-0c8cb1836be1', + id: 'ae3c19fa-620e-4b14-ad77-6da3f7f70912', + x: 295, + y: 594, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See More', + width: 91, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7cb85853-5318-455d-8f48-2f4056e66dac', + id: 'd61d1f44-9af9-4cdc-8912-caf2aa0c4993', + x: 292, + y: 600, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_white_star.png`, + width: 50, + height: 47, + posterId: 0, + id: 545, + title: 'ent_icon_white_star', + alt: 'ent_icon_white_star', + local: false, + sizes: [], + }, + x: 39, + y: 600, + width: 16, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '94012718-482e-48fd-87ad-9c7656a2699e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_white_star.png`, + width: 50, + height: 47, + posterId: 0, + id: 545, + title: 'ent_icon_white_star', + alt: 'ent_icon_white_star', + local: false, + sizes: [], + }, + width: 16, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '94012718-482e-48fd-87ad-9c7656a2699e', + id: '636dafd1-3a4f-4959-ba81-b3ebf414d144', + x: 56, + y: 600, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_white_star.png`, + width: 50, + height: 47, + posterId: 0, + id: 545, + title: 'ent_icon_white_star', + alt: 'ent_icon_white_star', + local: false, + sizes: [], + }, + width: 16, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '636dafd1-3a4f-4959-ba81-b3ebf414d144', + id: '2dec6f59-b7fc-4418-a28f-c2eaaf43aade', + x: 73, + y: 600, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_gray_star.png`, + width: 50, + height: 47, + posterId: 0, + id: 540, + title: 'ent_icon_gray_star', + alt: 'ent_icon_gray_star', + local: false, + sizes: [], + }, + x: 90, + y: 600, + width: 16, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '2ecfa984-9fb7-4964-a161-cad04ce58e27', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_gray_star.png`, + width: 50, + height: 47, + posterId: 0, + id: 540, + title: 'ent_icon_gray_star', + alt: 'ent_icon_gray_star', + local: false, + sizes: [], + }, + width: 16, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '2ecfa984-9fb7-4964-a161-cad04ce58e27', + id: 'c2e63832-aff6-4e25-9818-4890ca847afc', + x: 108, + y: 600, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 200], + [1, 200], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 600], + [1, 600], + [0, 700], + [1, 700], + [0, 800], + [1, 800], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 13, + fontStyle: 'italic', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '3 out of 5', + width: 80, + height: 12, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'fd434caf-7752-47d3-b41b-0a1048f116a8', + id: 'ed850d1c-3f8f-4154-ad72-a90d30dc836d', + x: 145, + y: 602, + }, + ], + backgroundElementId: '923c69d0-84f6-4223-8f7e-8fd7f0d0363f', + backgroundOverlay: 'none', + type: 'page', + id: '7494b712-c8d0-4189-a85c-32aa52a22032', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '3fb75a52-ac04-4979-9a26-c28e725c3ed3', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 397, + height: 288, + scale: 200, + focalX: 50.079165168465856, + focalY: 40.500179784097604, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 532, + title: 'ent_page6_image1', + alt: 'ent_page6_image1', + local: false, + sizes: { + medium: { + file: 'ent_page6_image1-200x300.jpg', + width: 200, + height: 300, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-200x300.jpg`, + }, + large: { + file: 'ent_page6_image1-683x1024.jpg', + width: 683, + height: 1024, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-683x1024.jpg`, + }, + thumbnail: { + file: 'ent_page6_image1-150x150.jpg', + width: 150, + height: 150, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-150x150.jpg`, + }, + medium_large: { + file: 'ent_page6_image1-768x1152.jpg', + width: 768, + height: 1152, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-768x1152.jpg`, + }, + '1536x1536': { + file: 'ent_page6_image1-1024x1536.jpg', + width: 1024, + height: 1536, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-1024x1536.jpg`, + }, + '2048x2048': { + file: 'ent_page6_image1-1365x2048.jpg', + width: 1365, + height: 2048, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-1365x2048.jpg`, + }, + 'post-thumbnail': { + file: 'ent_page6_image1-1200x1800.jpg', + width: 1200, + height: 1800, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-1200x1800.jpg`, + }, + 'web-stories-poster-portrait': { + file: 'ent_page6_image1-696x928.jpg', + width: 696, + height: 928, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-696x928.jpg`, + }, + 'web-stories-poster-square': { + file: 'ent_page6_image1-928x928.jpg', + width: 928, + height: 928, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-928x928.jpg`, + }, + 'web-stories-poster-landscape': { + file: 'ent_page6_image1-928x696.jpg', + width: 928, + height: 696, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-928x696.jpg`, + }, + web_stories_thumbnail: { + file: 'ent_page6_image1-150x225.jpg', + width: 150, + height: 225, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-150x225.jpg`, + }, + full: { + file: 'ent_page6_image1-scaled.jpg', + width: 1707, + height: 2560, + mime_type: 'image/jpeg', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_page6_image1-scaled.jpg`, + }, + }, + }, + basedOn: '7f6f6565-2e46-4ef5-964c-d6c44350416d', + id: '9bcf8de4-b888-486b-93cb-f27bc58c637d', + x: 42, + y: 131, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Summer Hits', + width: 291, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'fd434caf-7752-47d3-b41b-0a1048f116a8', + id: '883294a7-1863-4ef1-b3b3-05f4e99f027c', + x: 44, + y: 23, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.43, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit ...', + width: 355, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'ff9a77cf-c942-4a04-84ea-d7134d97f360', + id: '7807179c-ca03-44f5-b164-de59858ed73b', + x: 44, + y: 67, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See Full Story', + width: 291, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '883294a7-1863-4ef1-b3b3-05f4e99f027c', + id: 'b618a51e-2b45-43d5-9007-4c761ac4341b', + x: 75, + y: 616, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 84, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 141, + g: 141, + b: 141, + }, + }, + letterSpacing: 0, + lineHeight: 0.2, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'OUT NOW', + width: 412, + height: 16, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '26df8c19-b913-441a-bff1-bf1b677cb159', + id: '99964f56-048a-4e9c-bc0c-79ff8f9e32d5', + x: 43, + y: 426, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 84, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 199, + g: 196, + b: 196, + }, + }, + letterSpacing: 0, + lineHeight: 0.25, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'OUT NOW', + width: 412, + height: 21, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5bfb51c9-3d47-4a78-be2c-776db5143bc9', + id: '7134bc0d-5c49-40fb-ac4a-dac7dc45dccf', + x: 43, + y: 448, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 84, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.8, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'OUT NOW', + width: 412, + height: 67, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5efefa69-c1d0-4331-905b-3936d866f0eb', + id: '842c2576-5887-4eb2-a9ba-93a6eea67b7d', + x: 43, + y: 474, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 29, + height: 29, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '5f47cc92-4646-4e5c-90b4-0bd917564649', + id: '7b4ed123-0d08-4e68-9f25-ba2943c7c611', + x: 205.5, + y: 575.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_up_arrow.png`, + width: 150, + height: 69, + posterId: 0, + id: 544, + title: 'ent_icon_up_arrow', + alt: 'ent_icon_up_arrow', + local: false, + sizes: { + thumbnail: { + file: 'ent_icon_up_arrow-150x138.png', + width: 150, + height: 138, + mime_type: 'image/png', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_up_arrow-150x138.png`, + }, + web_stories_thumbnail: { + file: 'ent_icon_up_arrow-150x69.png', + width: 150, + height: 69, + mime_type: 'image/png', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_up_arrow-150x69.png`, + }, + full: { + file: 'ent_icon_up_arrow.png', + width: 299, + height: 138, + mime_type: 'image/png', + source_url: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_up_arrow.png`, + }, + }, + }, + x: 213, + y: 587, + width: 14, + height: 6, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'c8ed9d0e-4f8d-4549-80cc-a5b3dd9caa8e', + }, + ], + backgroundElementId: '3fb75a52-ac04-4979-9a26-c28e725c3ed3', + backgroundOverlay: 'none', + type: 'page', + id: 'fd60f532-b619-4162-8df9-d8241cc88e45', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: 'f1d3853d-aa0a-4bfc-8b40-d09c94153814', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 397, + height: 363, + scale: 110, + focalX: 49.89204749754657, + focalY: 62.22262102275575, + mask: { + type: 'rectangle', + }, + type: 'image', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page7_image1.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 533, + title: 'ent_page7_image1', + alt: 'ent_page7_image1', + local: false, + sizes: {}, + }, + basedOn: '9bcf8de4-b888-486b-93cb-f27bc58c637d', + id: '6314a691-d449-4d99-9f12-64a9c7e5b477', + x: 44, + y: 28, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 29, + height: 29, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '616bba21-8e69-4240-b8c6-e0fc6be575d2', + id: '5f47cc92-4646-4e5c-90b4-0bd917564649', + x: 205.5, + y: 575.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_up_arrow.png`, + width: 150, + height: 69, + posterId: 0, + id: 544, + title: 'ent_icon_up_arrow', + alt: 'ent_icon_up_arrow', + local: false, + sizes: {}, + }, + width: 14, + height: 6, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: 'c8ed9d0e-4f8d-4549-80cc-a5b3dd9caa8e', + id: '70a29ab7-cab6-4114-ab53-c9f2466ad803', + x: 213, + y: 587, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See Full Story', + width: 291, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'b618a51e-2b45-43d5-9007-4c761ac4341b', + id: 'b9dd5baa-4aec-4e63-a471-2ae0ce728f09', + x: 75, + y: 616, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'Behind the Scenes', + width: 291, + height: 63, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '883294a7-1863-4ef1-b3b3-05f4e99f027c', + id: '0fdf2c26-3138-41cf-ae7c-e508635ae6fe', + x: 44, + y: 403, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.43, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vel dui erat. Curabitur sit amet venenatis felis. In ac ornare lacus. Integer vitae lacus a lectus eleifend finibus.', + width: 355, + height: 75, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'ff9a77cf-c942-4a04-84ea-d7134d97f360', + id: 'b0d4ee54-76eb-4050-821d-5d11181b6a2c', + x: 42, + y: 478, + }, + ], + backgroundElementId: 'f1d3853d-aa0a-4bfc-8b40-d09c94153814', + backgroundOverlay: 'none', + type: 'page', + id: '877c2ef2-1faa-4a7b-9f5b-364dcfa3d856', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '777737d1-e79e-4f4e-9708-cf5a41962d77', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 0, + b: 214, + }, + }, + isFill: false, + width: 337, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '4e907f06-b5e0-4254-b469-8f0a505cd915', + id: '6403aa20-a02a-499d-832a-0982518395f5', + x: -2, + y: 503, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 0, + b: 214, + }, + }, + isFill: false, + width: 337, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '4e907f06-b5e0-4254-b469-8f0a505cd915', + id: '016efcac-89dc-4123-bec4-f7422f8babd6', + x: -2, + y: 335, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 0, + b: 214, + }, + }, + isFill: false, + x: -2, + y: 167, + width: 337, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '4e907f06-b5e0-4254-b469-8f0a505cd915', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: -2, + y: 177, + width: 370, + height: 128, + scale: 130, + focalX: 55.59746548940643, + focalY: 37.583736990182935, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '24deb6f3-2a32-4638-8702-7fc1157b9956', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page8_image1.jpg`, + width: 220, + height: 90, + posterId: 0, + id: 534, + title: 'ent_page8_image1', + alt: 'ent_page8_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 370, + height: 128, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: '24deb6f3-2a32-4638-8702-7fc1157b9956', + id: '84d3c738-e445-460d-96cc-e04f1184a554', + x: 0, + y: 513, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page8_image1.jpg`, + width: 220, + height: 90, + posterId: 0, + id: 534, + title: 'ent_page8_image1', + alt: 'ent_page8_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 370, + height: 128, + scale: 160, + focalX: 33.84652508661792, + focalY: 62.26218906628386, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: '84d3c738-e445-460d-96cc-e04f1184a554', + id: 'e53c70ed-d9e7-442b-ab62-0c236111e1db', + x: -2, + y: 345, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page8_image2.jpg`, + width: 220, + height: 147, + posterId: 0, + id: 535, + title: 'ent_page8_image2', + alt: 'ent_page8_image2', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: -12, + y: 274, + width: 275, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '5e2fc66f-1b58-43d2-8163-10840e09d440', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'MURDER MYSTERY', + width: 227, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '0fdf2c26-3138-41cf-ae7c-e508635ae6fe', + id: '2942198b-fcac-4d56-bc37-b5a16de50651', + x: 3, + y: 282, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 275, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5e2fc66f-1b58-43d2-8163-10840e09d440', + id: '036f33e7-ad2a-4952-8c2c-61ebe691abfc', + x: -7, + y: 441, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'DOCUMENTARY', + width: 205, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '2942198b-fcac-4d56-bc37-b5a16de50651', + id: '787be6c3-3d2d-4d08-9db1-801e2f1bf7f1', + x: 6, + y: 449, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 275, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5e2fc66f-1b58-43d2-8163-10840e09d440', + id: '8e1445ec-4fe2-4abe-b895-766808f3f092', + x: 0, + y: 610, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'POLITICAL BIOPIC', + width: 250, + height: 24, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '2942198b-fcac-4d56-bc37-b5a16de50651', + id: 'b2b65d7c-5689-4c40-a40e-5a9a628bc04e', + x: 7, + y: 618, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'This summer’s must-see blockbusters', + width: 245, + height: 96, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '0fdf2c26-3138-41cf-ae7c-e508635ae6fe', + id: '97bead85-66e2-4ea0-af98-8b1305d41884', + x: 48, + y: 40, + }, + ], + backgroundElementId: '777737d1-e79e-4f4e-9708-cf5a41962d77', + backgroundOverlay: 'none', + type: 'page', + id: '3d0dbbd3-70c0-4376-b1bf-c8015def1050', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: 'ffce028c-042d-4b3c-a489-3cc31162ed10', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 45, + y: 187, + width: 145, + height: 108, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '77770ec0-6e97-40bc-813b-1e0500033fc3', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page9_image1.jpg`, + width: 220, + height: 165, + posterId: 0, + id: 536, + title: 'ent_page9_image1', + alt: 'ent_page9_image1', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 145, + height: 108, + scale: 100, + focalX: 49.851188990309694, + focalY: 34.28571451955782, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: '77770ec0-6e97-40bc-813b-1e0500033fc3', + id: 'e279e4c7-f45a-4642-86ce-8c86644f7198', + x: 46, + y: 328, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page9_image2.jpg`, + width: 220, + height: 275, + posterId: 0, + id: 537, + title: 'ent_page9_image2', + alt: 'ent_page9_image2', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 145, + height: 108, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: 'e279e4c7-f45a-4642-86ce-8c86644f7198', + id: '78953cce-d23a-4bef-97e5-dec04f6a45a9', + x: 46, + y: 468, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_page9_image3.jpg`, + width: 220, + height: 147, + posterId: 0, + id: 538, + title: 'ent_page9_image3', + alt: 'ent_page9_image3', + local: false, + sizes: {}, + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_fb.png`, + width: 100, + height: 200, + posterId: 0, + id: 539, + title: 'ent_icon_fb', + alt: 'ent_icon_fb', + local: false, + sizes: {}, + }, + x: 98, + y: 605, + width: 15, + height: 30, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'd080d24a-fef1-4ee0-bef8-69131abeae47', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_insta.png`, + width: 100, + height: 100, + posterId: 0, + id: 541, + title: 'ent_icon_insta', + alt: 'ent_icon_insta', + local: false, + sizes: {}, + }, + x: 159, + y: 605, + width: 30, + height: 30, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '21632ad5-6811-42d7-98db-4aa4bc45a74b', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_yt.png`, + width: 100, + height: 68, + posterId: 0, + id: 546, + title: 'ent_icon_yt', + alt: 'ent_icon_yt', + local: false, + sizes: {}, + }, + x: 235, + y: 610, + width: 30, + height: 20, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '63b3d999-5d6c-4d78-857d-4f2e1de0e4ac', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}/assets/images/templates/ent/ent_icon_twtr.png`, + width: 100, + height: 79, + posterId: 0, + id: 543, + title: 'ent_icon_twtr', + alt: 'ent_icon_twtr', + local: false, + sizes: {}, + }, + x: 312, + y: 608, + width: 30, + height: 24, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'e98f7dca-7623-4586-b834-e1cceb3a77dd', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 80, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.83, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'MORE \nSTORIES', + width: 327, + height: 132, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '97bead85-66e2-4ea0-af98-8b1305d41884', + id: '4f12d40d-d1c9-488f-a4f8-48e81f141c27', + x: -5, + y: 20, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'OUR TOP PICKS \nFOR THE GRAMMY’S', + width: 155, + height: 28, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '97bead85-66e2-4ea0-af98-8b1305d41884', + id: 'c6c185a6-32d1-42ed-b376-5733abe06f55', + x: 222, + y: 198, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'MARVEL’S NEWEST SUPERHERO', + width: 155, + height: 25, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c6c185a6-32d1-42ed-b376-5733abe06f55', + id: '9379053f-520a-4138-9720-dd14e3115431', + x: 221, + y: 344, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 700, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'FIRST LOOK AT J-LO’S NEW MOVIE', + width: 155, + height: 25, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c6c185a6-32d1-42ed-b376-5733abe06f55', + id: '9300db37-ac48-4bfc-b91b-0ffb4da5f8d0', + x: 221, + y: 482, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 82, + g: 82, + b: 82, + }, + }, + isFill: false, + x: 228, + y: 243, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'b6325820-9ffe-41d3-97e9-eb969ff9ef22', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 220, + y: 236.5, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '3f8f943f-ca2c-4604-b9e7-d300c4b2b9d6', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 90, + height: 26, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f8f943f-ca2c-4604-b9e7-d300c4b2b9d6', + id: 'f2183556-61b5-46b5-b945-0c8cb1836be1', + x: 223, + y: 239, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See More', + width: 91, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c6c185a6-32d1-42ed-b376-5733abe06f55', + id: '7cb85853-5318-455d-8f48-2f4056e66dac', + x: 220, + y: 244, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 82, + g: 82, + b: 82, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'b6325820-9ffe-41d3-97e9-eb969ff9ef22', + id: 'dce1bd4c-b2f2-46eb-97ec-5791036f6681', + x: 228, + y: 390, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '3f8f943f-ca2c-4604-b9e7-d300c4b2b9d6', + id: '1cf31b4c-c1e0-43b8-b16f-0fe1df052a2e', + x: 221.5, + y: 385, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 90, + height: 26, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'f2183556-61b5-46b5-b945-0c8cb1836be1', + id: 'f3d5c179-2d4f-46c7-9d74-5d21f6bc4be1', + x: 224.5, + y: 387.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See More', + width: 91, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7cb85853-5318-455d-8f48-2f4056e66dac', + id: '8c1e025b-365c-4025-a965-0040228ebfc3', + x: 221.5, + y: 393.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 82, + g: 82, + b: 82, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'dce1bd4c-b2f2-46eb-97ec-5791036f6681', + id: '83c2ae02-a7fa-42f5-b873-9ea8ca89ae8f', + x: 228, + y: 527, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 96, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '1cf31b4c-c1e0-43b8-b16f-0fe1df052a2e', + id: '168b9b01-027e-4115-8f7e-2d47ff5f89ad', + x: 221.5, + y: 522, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + isFill: false, + width: 90, + height: 26, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: 'f3d5c179-2d4f-46c7-9d74-5d21f6bc4be1', + id: 'ff7be7d3-3ac3-4393-b3b3-27c2c8d19d5e', + x: 224.5, + y: 524.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Poppins', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [100, 200, 300, 400, 500, 600, 700, 800, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [0, 200], + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 100], + [1, 200], + [1, 300], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See More', + width: 91, + height: 14, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '8c1e025b-365c-4025-a965-0040228ebfc3', + id: '8a619e42-8416-43d3-a1ab-6767452594ec', + x: 221.5, + y: 530.5, + }, + ], + backgroundElementId: 'ffce028c-042d-4b3c-a489-3cc31162ed10', + backgroundOverlay: 'none', + type: 'page', + id: 'f4ea7828-e328-47e6-bf9d-a786ab192ee1', + }, + ], + autoAdvance: true, + defaultPageDuration: 7, + }; +} diff --git a/assets/src/dashboard/templates/data/fashion.js b/assets/src/dashboard/templates/data/fashion.js new file mode 100644 index 000000000000..d0b0017c5741 --- /dev/null +++ b/assets/src/dashboard/templates/data/fashion.js @@ -0,0 +1,4598 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default function (imageBaseUrl) { + return { + version: 15, + pages: [ + { + elements: [ + { + type: 'shape', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '95a99f3f-ccbd-4b61-b676-25a8dec8577b', + }, + { + type: 'image', + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + a: 0, + }, + }, + isFill: false, + x: 73, + y: 71, + width: 367, + height: 474, + scale: 170, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + id: '6978fba6-59ce-46bc-b862-92ce2b9795bb', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page1_hero.jpg`, + width: 220, + height: 297, + posterId: 0, + id: 32, + title: 'fashion_page1_hero', + alt: 'fashion_page1_hero', + local: false, + sizes: [], + }, + }, + { + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + type: 'text', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + fontWeight: 400, + fontSize: 99, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.95, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'FASHION\nON THE GO', + x: -190, + y: 251, + width: 610, + height: 188, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + id: 'a32c17e0-a46b-4821-bbdb-8dc064ac097d', + }, + { + type: 'image', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page1_logotype.png`, + width: 46, + height: 12, + posterId: 0, + id: 36, + title: 'fashion_page1_logotype', + alt: 'fashion_page1_logotype', + local: false, + sizes: [], + }, + x: 34, + y: 31, + width: 100, + height: 25, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + id: '96d690d8-4cc5-4807-b646-d89cf6025a9a', + }, + ], + backgroundElementId: '95a99f3f-ccbd-4b61-b676-25a8dec8577b', + type: 'page', + id: '1f51fc35-c1d6-46ea-9580-b38672124162', + }, + { + elements: [ + { + type: 'shape', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '6a6e5101-6d92-4c64-b1b6-124cf9cce0b1', + }, + { + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + type: 'text', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + "“IF YOU CAN'T BE BETTER THAN YOUR COMPETITION, JUST DRESS BETTER”", + x: 21, + y: 11, + width: 347, + height: 276, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + id: 'd773925c-2f95-44c0-9b0e-0743d45476f2', + }, + { + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + type: 'text', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 0, + g: 0, + b: 0, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '— ANNA\nWINTOUR', + width: 347, + height: 91, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + basedOn: 'd773925c-2f95-44c0-9b0e-0743d45476f2', + id: '61d23551-31e3-4aad-ac15-b8fc5e4be2ec', + x: 22, + y: 543, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page2_pinwheel_full.png`, + width: 220, + height: 220, + posterId: 0, + id: 84, + title: 'fashion_page2_pinwheel_full', + alt: 'fashion_page2_pinwheel_full', + local: false, + sizes: [], + }, + x: 220, + y: 281, + width: 271, + height: 271, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'd6798f2f-b78d-4a5a-9d83-13b84c950394', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page2_arrow.png`, + width: 31, + height: 24, + posterId: 0, + id: 83, + title: 'fashion_page2_arrow', + alt: 'fashion_page2_arrow', + local: false, + sizes: [], + }, + x: 333, + y: 399, + width: 45, + height: 35, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '99696270-286f-4c14-b062-aad2a021672a', + }, + ], + backgroundElementId: '6a6e5101-6d92-4c64-b1b6-124cf9cce0b1', + backgroundOverlay: 'none', + type: 'page', + id: 'cda99980-6722-4141-9616-25e523a98722', + }, + { + elements: [ + { + type: 'shape', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + id: '589a577a-fcac-49cf-964e-96e092ecf511', + }, + { + type: 'image', + opacity: 95, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 409, + height: 592, + scale: 170, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + id: '6cca5fcd-80d5-4181-b9e6-2979b80c2b99', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page3_bg.jpg`, + width: 220, + height: 261, + posterId: 0, + id: 53, + title: 'fashion_page3_bg', + alt: 'fashion_page3_bg', + local: false, + sizes: [], + }, + }, + { + type: 'shape', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + type: 'linear', + stops: [ + { + color: { + r: 33, + g: 33, + b: 33, + a: 0.75, + }, + position: 0, + }, + { + color: { + r: 33, + g: 33, + b: 33, + a: 0, + }, + position: 1, + }, + ], + rotation: 0.5, + }, + isFill: false, + x: 2, + y: 359, + width: 408, + height: 234, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + id: '6fc327a4-8e5d-48e8-8285-899267ec1183', + }, + { + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + type: 'text', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + fontWeight: 400, + fontSize: 173, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.93, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'S/S PREV\nIEW', + x: 52, + y: 1, + width: 493, + height: 480, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + id: '792955f1-699b-499a-bf65-d88b60d613af', + }, + { + font: { + service: 'fonts.google.com', + family: 'DM Sans', + fallbacks: ['sans-serif'], + }, + type: 'text', + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + fontWeight: 400, + fontSize: 18, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + letterSpacing: 0.02, + lineHeight: 1.22, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'PREVIEW THE LATEST LOOKS OF THE SEASON', + x: 61, + y: 509, + width: 240, + height: 42, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + id: '9bd1600f-5d9d-4beb-bdfb-817b118a8852', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 700, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See Full Story', + x: 140, + y: 617, + width: 160, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'd27e267e-5ee5-4b60-8c79-a9e3afa92d52', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 206, + y: 579, + width: 29, + height: 29, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: 'a2240997-ab2a-49ec-bafb-503d778122ca', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page3_arrow.png`, + width: 7, + height: 3, + posterId: 0, + id: 64, + title: 'fashion_page3_arrow', + alt: 'fashion_page3_arrow', + local: false, + sizes: [], + }, + x: 215.5, + y: 591.5, + width: 10, + height: 4, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '504deaac-9943-4878-984a-c22cc3efd7cd', + }, + ], + backgroundElementId: '589a577a-fcac-49cf-964e-96e092ecf511', + backgroundOverlay: 'none', + type: 'page', + id: '99ae5466-855b-4d84-9379-c401969ade38', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '178de0c9-6a64-450e-bc47-5f38e6884665', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LOUIS\nVUITTON', + x: 24, + y: 32, + width: 213, + height: 92, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '64b41c08-0817-4b98-aa55-0aaa9ea205e8', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: 0.02, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'A RICH HISTORY', + x: 24, + y: 134, + width: 174, + height: 16, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'a2a90d9e-2bce-44d3-95cd-77f33c55e144', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LV MONOGRAM CANVAS', + x: 130, + y: 243, + width: 281, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '8c45242f-9941-4fdc-b5de-3339a36585bf', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 120, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1896', + x: 181, + y: 112, + width: 237, + height: 156, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'acb2ccae-1faf-4192-bf28-19614324ed25', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 60, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1901', + x: 25, + y: 261, + width: 160, + height: 78, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'e6ee6ead-43e0-45a4-aeef-1bcb7c914fcd', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE STEAMER BAG', + x: 28, + y: 328, + width: 237, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '2c548d81-abe2-4f74-9292-9910382fb293', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 60, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1930', + width: 160, + height: 77, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e6ee6ead-43e0-45a4-aeef-1bcb7c914fcd', + id: '9156bf8e-3cdd-41fb-99c2-f6206f95ec1b', + x: 251, + y: 335, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE KEEPALL BAG', + width: 237, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '2c548d81-abe2-4f74-9292-9910382fb293', + id: '78ef1efb-0ca7-4067-9a38-a646987fd096', + x: 174, + y: 402, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE PAPILLION BAG', + width: 281, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '8c45242f-9941-4fdc-b5de-3339a36585bf', + id: 'f5593520-3bb3-49d6-bfbd-c9fe2af7ee51', + x: 25, + y: 536, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 120, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1966', + width: 237, + height: 156, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'acb2ccae-1faf-4192-bf28-19614324ed25', + id: 'a10cc4c5-ee6f-43dd-a391-ce72a14a43b7', + x: 25, + y: 405, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 60, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1985', + width: 160, + height: 77, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '9156bf8e-3cdd-41fb-99c2-f6206f95ec1b', + id: '8c739a98-8a17-45f2-988a-935fc4e5ec33', + x: 251, + y: 534, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'EPI LEATHER', + width: 237, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '78ef1efb-0ca7-4067-9a38-a646987fd096', + id: 'f17ac170-7428-4788-9f02-0a8704c1b8bf', + x: 174, + y: 601, + }, + ], + backgroundElementId: '178de0c9-6a64-450e-bc47-5f38e6884665', + backgroundOverlay: 'none', + type: 'page', + id: '22e2ed54-fa70-43c5-81e8-81ad672bd6ad', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: 'db53e416-f618-4f22-9043-0e05f05094e1', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 120, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'CARDIN —', + x: -257, + y: 319, + width: 624, + height: 156, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'bad4432f-b82b-4579-bb2d-d9858f7feec2', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 120, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + a: 0.35, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE HANDBAG', + width: 844, + height: 156, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'bad4432f-b82b-4579-bb2d-d9858f7feec2', + id: '875e2b60-1738-425a-8d7d-95f648bb3af6', + x: -367, + y: -376, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page5_handbag.png`, + width: 220, + height: 194, + posterId: 0, + id: 131, + title: 'fashion_page5_handbag', + alt: 'fashion_page5_handbag', + local: false, + sizes: [], + }, + x: 14, + y: 114, + width: 425, + height: 375, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'b5631bd3-6a40-4d43-b4b8-9f0b7c76a742', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + isFill: false, + x: 161, + y: 41, + width: 30, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: 'ba9ff730-74e6-478a-943e-8aa33851048d', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 22, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: 0.02, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'CARDIN', + x: 162, + y: 505, + width: 160, + height: 28, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'f19846b4-b351-4607-a8f9-c7e69ff7a6f3', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 22, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + letterSpacing: 0.02, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE HANDBAG\n$450', + width: 160, + height: 54, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'f19846b4-b351-4607-a8f9-c7e69ff7a6f3', + id: '61b42950-25ca-46cf-83bf-1e04cdc0cd94', + x: 162, + y: 532, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + letterSpacing: 0.08, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LEARN MORE', + width: 160, + height: 17, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '61b42950-25ca-46cf-83bf-1e04cdc0cd94', + id: '67777589-1517-4a00-9e95-11e262bf02dd', + x: 162, + y: 625, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page5_arrow.png`, + width: 8, + height: 4, + posterId: 0, + id: 143, + title: 'fashion_page5_arrow', + alt: 'fashion_page5_arrow', + local: false, + sizes: [], + }, + x: 272, + y: 628.5, + width: 14, + height: 7, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '87ae3605-e8e2-4d0f-8700-f84de19e4f01', + }, + ], + backgroundElementId: 'db53e416-f618-4f22-9043-0e05f05094e1', + backgroundOverlay: 'none', + type: 'page', + id: '06f93f32-41a9-4fcd-9d03-08ea29f591e4', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: 'dc3c80d7-8757-4231-826a-0d0c34f6e15e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TOP 5\nCOLLECTIONS\nTHIS SEASON', + x: 20, + y: 10, + width: 344, + height: 138, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '735394cc-0da6-479c-951d-14579f0766ef', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'RODARTE', + width: 229, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '735394cc-0da6-479c-951d-14579f0766ef', + id: 'ecfb31af-00a8-4533-b86a-e268e7b2a573', + x: 52, + y: 235, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'CHANEL', + width: 229, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'ecfb31af-00a8-4533-b86a-e268e7b2a573', + id: 'f40e6fac-12c9-4259-a5af-8de9d1fd1c64', + x: 160, + y: 314, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'RICK OWENS', + width: 275, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'f40e6fac-12c9-4259-a5af-8de9d1fd1c64', + id: 'dc565020-6358-44d9-9aac-a8eb90e76415', + x: 30, + y: 398, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'PRADA', + width: 159, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'dc565020-6358-44d9-9aac-a8eb90e76415', + id: 'd6fea8bd-8f0a-414b-a845-722d436a6d43', + x: 134, + y: 482, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'BALENCIAGA', + width: 300, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'd6fea8bd-8f0a-414b-a845-722d436a6d43', + id: '7726e175-d97f-42ed-bb2e-a581f4135332', + x: 88, + y: 561, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page6_pinwheel-outline.png`, + width: 28, + height: 28, + posterId: 0, + id: 158, + title: 'fashion_page6_pinwheel-outline', + alt: 'fashion_page6_pinwheel-outline', + local: false, + sizes: [], + }, + x: 238.5, + y: 207, + width: 55, + height: 55, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'b9736281-90de-4a2e-b813-7faddcba9dd9', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1', + width: 43, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '735394cc-0da6-479c-951d-14579f0766ef', + id: '3873fcaf-3c89-43dd-ba61-47bfed747b3e', + x: 244.5, + y: 215, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page6_pinwheel-outline.png`, + width: 28, + height: 28, + posterId: 0, + id: 158, + title: 'fashion_page6_pinwheel-outline', + alt: 'fashion_page6_pinwheel-outline', + local: false, + sizes: [], + }, + width: 55, + height: 55, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: 'b9736281-90de-4a2e-b813-7faddcba9dd9', + id: '9cc65a3f-90c8-4a6b-8994-2db0da03e2aa', + x: 125.5, + y: 290, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '2', + width: 25, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '3873fcaf-3c89-43dd-ba61-47bfed747b3e', + id: 'e614554a-4201-4bdd-9ced-c5fe97c758c8', + x: 140.5, + y: 297.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page6_pinwheel-outline.png`, + width: 28, + height: 28, + posterId: 0, + id: 158, + title: 'fashion_page6_pinwheel-outline', + alt: 'fashion_page6_pinwheel-outline', + local: false, + sizes: [], + }, + width: 55, + height: 55, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '9cc65a3f-90c8-4a6b-8994-2db0da03e2aa', + id: '1995d229-c3c4-4f57-8e19-dc65b333e87a', + x: 284.75, + y: 370.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '3', + width: 41, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e614554a-4201-4bdd-9ced-c5fe97c758c8', + id: '53f8e685-93b5-4971-bb76-03d52602eb47', + x: 291, + y: 378, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page6_pinwheel-outline.png`, + width: 28, + height: 28, + posterId: 0, + id: 158, + title: 'fashion_page6_pinwheel-outline', + alt: 'fashion_page6_pinwheel-outline', + local: false, + sizes: [], + }, + width: 55, + height: 55, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '1995d229-c3c4-4f57-8e19-dc65b333e87a', + id: '8368daee-25c0-47fa-a3f8-f1d087aff0ea', + x: 100.875, + y: 457.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '4', + width: 41, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '53f8e685-93b5-4971-bb76-03d52602eb47', + id: 'c8f479d6-429d-4ea4-93e8-ce8dfb37a5d5', + x: 107.875, + y: 465, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page6_pinwheel-outline.png`, + width: 28, + height: 28, + posterId: 0, + id: 158, + title: 'fashion_page6_pinwheel-outline', + alt: 'fashion_page6_pinwheel-outline', + local: false, + sizes: [], + }, + width: 55, + height: 55, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + basedOn: '8368daee-25c0-47fa-a3f8-f1d087aff0ea', + id: '797bc565-14f8-4635-aef9-d3aaa1d4f61f', + x: 335.9375, + y: 535.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 32, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '5', + width: 41, + height: 32, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '53f8e685-93b5-4971-bb76-03d52602eb47', + id: '175d2727-d683-4601-8405-f0a2ee56a654', + x: 343, + y: 544, + }, + ], + backgroundElementId: 'dc3c80d7-8757-4231-826a-0d0c34f6e15e', + backgroundOverlay: 'none', + type: 'page', + id: 'e39c480a-e017-4c6e-b48c-4dc1ef00f6ab', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '68541aa4-d532-4f23-b89e-7dc34882f49b', + }, + { + opacity: 90, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 1, + y: 0, + width: 424, + height: 388, + scale: 230, + focalX: 46.85572257958338, + focalY: 44.20206702632711, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '7ecf93c5-2286-495d-86e9-30cdf0bf32b3', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page7_hero.jpg`, + width: 220, + height: 274, + posterId: 0, + id: 196, + title: 'fashion_page7_hero', + alt: 'fashion_page7_hero', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'CARDIN', + width: 229, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'ecfb31af-00a8-4533-b86a-e268e7b2a573', + id: '9ce78e9e-7568-45de-bef3-1f9438a11568', + x: 22, + y: 417, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page7_pinwheel.png`, + width: 123, + height: 123, + posterId: 0, + id: 198, + title: 'fashion_page7_pinwheel', + alt: 'fashion_page7_pinwheel', + local: false, + sizes: [], + }, + x: 28.75, + y: 317, + width: 123, + height: 123, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'e03c6326-6b3a-4a11-b7f1-80decd115e08', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '1', + width: 91, + height: 72, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '735394cc-0da6-479c-951d-14579f0766ef', + id: '8c86903b-0df0-4c1e-a75a-23f1e79522b3', + x: 44.75, + y: 334.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 133, + g: 130, + b: 128, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam vel dui erat. Curabitur sit amet venenatis felis. In ac ornare lacus. Integer vitae lacus a lectus eleifend finibus. Proin malesuada maximus felis, non lacinia est porttitor ante eget scelerisque.', + x: 28, + y: 484, + width: 399, + height: 100, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '43b205d1-376b-4eaf-b493-e55d70f8703e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: 0.08, + lineHeight: 1.25, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LEARN MORE', + width: 160, + height: 17, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '67777589-1517-4a00-9e95-11e262bf02dd', + id: '46ec18b4-8061-467f-88a0-fb2abd494061', + x: 29, + y: 622, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page7_arrow.png`, + width: 8, + height: 4, + posterId: 0, + id: 203, + title: 'fashion_page7_arrow', + alt: 'fashion_page7_arrow', + local: false, + sizes: [], + }, + x: 137, + y: 627, + width: 14, + height: 7, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '08232848-6452-4121-8ffa-724c8e587687', + }, + ], + backgroundElementId: '68541aa4-d532-4f23-b89e-7dc34882f49b', + backgroundOverlay: 'none', + type: 'page', + id: '050444c7-6097-410f-8147-0c195b051a27', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: true, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'image', + id: 'a879eb64-3b0f-49f7-93de-e63359e6fb1a', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page8_bg.jpg`, + width: 220, + height: 291, + posterId: 0, + id: 207, + title: 'fashion_page8_bg', + alt: 'fashion_page8_bg', + local: false, + sizes: [], + }, + scale: 120, + focalX: 54.19913419913419, + focalY: 58.333333333333336, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: -90, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + service: 'fonts.google.com', + family: 'Playfair Display', + fallbacks: ['serif'], + }, + fontWeight: 400, + fontSize: 120, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'GET THE LOOK', + width: 897, + height: 156, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'bad4432f-b82b-4579-bb2d-d9858f7feec2', + id: 'faf0a8bb-fa80-40b7-b423-80b8bd4cd7a5', + x: -391, + y: 175, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + width: 22, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '06b9b1f3-3fdb-49b8-8efa-895081f2a0c6', + id: '833449da-699c-4fac-aa52-cefc6c713423', + x: 173, + y: 570, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + isFill: false, + x: 175, + y: 572, + width: 18, + height: 18, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: '06b9b1f3-3fdb-49b8-8efa-895081f2a0c6', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + width: 22, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '833449da-699c-4fac-aa52-cefc6c713423', + id: '5c3a77a7-f9c2-4bd5-bf8f-9d211c433af3', + x: 323, + y: 362, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + isFill: false, + width: 18, + height: 18, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '06b9b1f3-3fdb-49b8-8efa-895081f2a0c6', + id: '1273a089-3c1f-44e2-a992-ee1c74571ce0', + x: 325, + y: 364, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + width: 22, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '5c3a77a7-f9c2-4bd5-bf8f-9d211c433af3', + id: '67a25f3e-d47f-4b06-97e8-b315ef85e0e2', + x: 209, + y: 280, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + isFill: false, + width: 18, + height: 18, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '1273a089-3c1f-44e2-a992-ee1c74571ce0', + id: '0ece7509-bf1e-479d-a146-8be2be2f0cee', + x: 211, + y: 282, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + width: 22, + height: 22, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '67a25f3e-d47f-4b06-97e8-b315ef85e0e2', + id: 'f6443849-a102-4899-9be1-ba2651204712', + x: 164, + y: 136, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 108, + b: 74, + }, + }, + isFill: false, + width: 18, + height: 18, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '0ece7509-bf1e-479d-a146-8be2be2f0cee', + id: '15da37bf-3d0f-42d6-bce1-c5cd63d92603', + x: 166, + y: 138, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 94, + y: 473, + width: 179, + height: 82, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'e3507933-d80f-4ed2-b52a-02dea8a0779d', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'CARDIN', + x: 104, + y: 481, + width: 92, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'b141ca71-b4f8-474c-93d0-b2a0b439c83e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'The Everyday Boots', + width: 136, + height: 17, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'b141ca71-b4f8-474c-93d0-b2a0b439c83e', + id: '4c1eea44-cac5-45ba-bdc2-c6959c9edd76', + x: 104, + y: 501, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'DM Sans', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [400, 500, 700], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + ], + }, + fontWeight: 400, + fontSize: 14, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '$575', + width: 136, + height: 17, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '4c1eea44-cac5-45ba-bdc2-c6959c9edd76', + id: 'bed8c7dc-aa6d-4fb9-b87d-b7bcb5911f93', + x: 104, + y: 531, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page8_arrow.png`, + width: 5, + height: 5, + posterId: 0, + id: 219, + title: 'fashion_page8_arrow', + alt: 'fashion_page8_arrow', + local: false, + sizes: [], + }, + x: 253, + y: 537, + width: 10, + height: 10, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '4446cc97-cd5a-45ea-8232-3a53e1df3d86', + }, + ], + backgroundElementId: 'a879eb64-3b0f-49f7-93de-e63359e6fb1a', + backgroundOverlay: 'none', + type: 'page', + id: '9e35d123-c307-4474-abd6-bb80a265bb2a', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 236, + b: 227, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '55d761b2-f2ae-4878-8c07-57908d3ab5ca', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 62, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.98, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'MORE\nSTORIES', + width: 258, + height: 120, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '64b41c08-0817-4b98-aa55-0aaa9ea205e8', + id: '421f76ac-4c4d-4a44-ad77-622facf57db5', + x: 53, + y: 18, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 18, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'HOW TO PULL OFF THE\nALL DENIM LOOK', + width: 237, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '2c548d81-abe2-4f74-9292-9910382fb293', + id: '1043803d-625c-4b7a-a5ad-1cdecfbb1c62', + x: 161, + y: 206, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 18, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE BEST STREET\nSTYLES FROM NYFW', + width: 237, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '1043803d-625c-4b7a-a5ad-1cdecfbb1c62', + id: '87b77bd3-8bc3-44ca-af6f-661e45522a7c', + x: 161, + y: 312, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 18, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE LATEST TRENDS\nIN SNEAKER LAND', + width: 237, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '87b77bd3-8bc3-44ca-af6f-661e45522a7c', + id: '44e7aa5c-3fe0-43db-8d46-c0a4425eeb18', + x: 159, + y: 413, + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + isFill: false, + x: 55, + y: 513, + width: 330, + height: 1, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'c330044d-44a1-4cee-91b3-51f2147914d7', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 54, + y: 183, + width: 86, + height: 86, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + id: 'd01ae69c-43cc-455e-bb63-250642795201', + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_item1-cropped.png`, + width: 43, + height: 43, + posterId: 0, + id: 253, + title: 'fashion_page9_item1-cropped', + alt: 'fashion_page9_item1-cropped', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 86, + height: 86, + scale: 130, + focalX: 41.611295124593894, + focalY: 35.212817670108095, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: 'd01ae69c-43cc-455e-bb63-250642795201', + id: 'c77c788b-cba9-421d-8a9a-2e8b604e99b8', + x: 54, + y: 288, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_item2.jpg`, + width: 220, + height: 331, + posterId: 0, + id: 246, + title: 'fashion_page9_item2', + alt: 'fashion_page9_item2', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + width: 86, + height: 86, + scale: 220, + focalX: 53.074903314458275, + focalY: 63.289037207094864, + mask: { + type: 'rectangle', + }, + type: 'image', + basedOn: 'c77c788b-cba9-421d-8a9a-2e8b604e99b8', + id: 'ba68ba92-edcd-4c5c-a4fe-5263fc994e2b', + x: 53, + y: 393, + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_item3.jpg`, + width: 229, + height: 157, + posterId: 0, + id: 247, + title: 'fashion_page9_item3', + alt: 'fashion_page9_item3', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Playfair Display', + service: 'fonts.google.com', + fallbacks: ['serif'], + weights: [400, 500, 600, 700, 800, 900], + styles: ['regular', 'italic'], + variants: [ + [0, 400], + [0, 500], + [0, 600], + [0, 700], + [0, 800], + [0, 900], + [1, 400], + [1, 500], + [1, 600], + [1, 700], + [1, 800], + [1, 900], + ], + }, + fontWeight: 400, + fontSize: 24, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 33, + g: 33, + b: 33, + }, + }, + letterSpacing: -0.01, + lineHeight: 1.1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'FOLLOW US', + width: 237, + height: 26, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '1043803d-625c-4b7a-a5ad-1cdecfbb1c62', + id: '40a6fb22-daa4-4d78-8cdc-5ce6547d1d64', + x: 102, + y: 535, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_facebook.png`, + width: 24, + height: 24, + posterId: 0, + id: 239, + title: 'fashion_page9_facebook', + alt: 'fashion_page9_facebook', + local: false, + sizes: [], + }, + x: 96, + y: 574, + width: 48, + height: 48, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '38462ba6-4406-458f-8f0e-88fa6558e712', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_instagram.png`, + width: 24, + height: 24, + posterId: 0, + id: 240, + title: 'fashion_page9_instagram', + alt: 'fashion_page9_instagram', + local: false, + sizes: [], + }, + x: 162, + y: 574, + width: 48, + height: 48, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'd4c4986d-35cf-414c-8047-8697a4e03446', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_youtube.png`, + width: 24, + height: 24, + posterId: 0, + id: 242, + title: 'fashion_page9_youtube', + alt: 'fashion_page9_youtube', + local: false, + sizes: [], + }, + x: 229, + y: 574, + width: 48, + height: 48, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'e13df6ed-9e36-42f5-a614-54faaf827097', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fashion/fashion_page9_twitter.png`, + width: 24, + height: 24, + posterId: 0, + id: 241, + title: 'fashion_page9_twitter', + alt: 'fashion_page9_twitter', + local: false, + sizes: [], + }, + x: 296, + y: 573, + width: 48, + height: 48, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '372a14f4-bc2c-493c-92e7-f7aa5d470289', + }, + ], + backgroundElementId: '55d761b2-f2ae-4878-8c07-57908d3ab5ca', + backgroundOverlay: 'none', + type: 'page', + id: 'a9ed1626-720d-4b66-bfbd-40441c83390c', + }, + ], + autoAdvance: true, + defaultPageDuration: 7, + }; +} diff --git a/assets/src/dashboard/templates/data/fitness.js b/assets/src/dashboard/templates/data/fitness.js new file mode 100644 index 000000000000..6cf93ae858a0 --- /dev/null +++ b/assets/src/dashboard/templates/data/fitness.js @@ -0,0 +1,3139 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export default function (imageBaseUrl) { + return { + version: 15, + pages: [ + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 180, + g: 17, + b: 31, + }, + }, + isFill: false, + x: 10.73767307309668, + y: 73.81953888501366, + width: 146.66666666666666, + height: 146.66666666666666, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '40b3440b-fdf1-4969-8469-4af6e9d49cb2', + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 1, + y: 0, + width: 442, + height: 660, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'image', + id: 'df4e89d1-b851-4c57-bfde-3503ce086e71', + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page1_bg-alt.png`, + width: 188, + height: 334, + posterId: 0, + id: 260, + title: 'fitness_page1_bg-alt', + alt: 'fitness_page1_bg-alt', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page1_logo.png`, + width: 123, + height: 38, + posterId: 0, + id: 262, + title: 'fitness_page1_logo', + alt: 'fitness_page1_logo', + local: false, + sizes: [], + }, + x: 1, + y: 1, + width: 123, + height: 38, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '7b8c59d0-c5ed-47a9-81c6-24fd8f63d772', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 190, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.03, + lineHeight: 0.74, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'NO\nDAYS\nOFF', + x: 21, + y: 240, + width: 417, + height: 420, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '9fc66e49-85dd-477d-8bb9-30ae259bc7fe', + }, + ], + backgroundElementId: '40b3440b-fdf1-4969-8469-4af6e9d49cb2', + type: 'page', + id: '2572f4a4-bb11-47e2-9f77-b8371aeabe49', + backgroundOverlay: 'none', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '65bb259e-6b72-4009-af27-3c19ee9d34e7', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page2_figure-formatted.png`, + width: 220, + height: 350, + posterId: 0, + id: 265, + title: 'fitness_page2_figure-formatted', + alt: 'fitness_page2_figure-formatted', + local: false, + sizes: [], + }, + x: 0, + y: 0, + width: 440, + height: 639, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'f7bd8df7-6f90-47c8-8c88-9c9db3183eea', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 42, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.03, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + '“DON’T BE AFRAID TO GIVE UP THE GOOD\n TO GO FOR THE GREAT.”', + x: 80, + y: 461, + width: 303, + height: 126, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: 'e44d4b12-221b-4fbf-96be-8a3c81e26f7c', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '-STEVE PREFONTAINE', + width: 303, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'e44d4b12-221b-4fbf-96be-8a3c81e26f7c', + id: '7fc0fd6e-ce7e-4be9-925c-c30a39d7cfeb', + x: 148, + y: 600, + }, + ], + backgroundElementId: '65bb259e-6b72-4009-af27-3c19ee9d34e7', + backgroundOverlay: 'none', + type: 'page', + id: '708fbc22-f0ca-40ec-9a57-21d9eb0be6b7', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'image', + id: 'dd764483-9e29-4aab-bb58-445d1f043d1a', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page3_bg.jpg`, + width: 220, + height: 330, + posterId: 0, + id: 270, + title: 'fitness_page3_bg', + alt: 'fitness_page3_bg', + local: false, + sizes: [], + }, + scale: 150, + focalX: 50, + focalY: 46.89484126984126, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 190, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 207, + g: 19, + b: 35, + }, + }, + letterSpacing: -0.03, + lineHeight: 0.74, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'RUN', + width: 301, + height: 140, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '9fc66e49-85dd-477d-8bb9-30ae259bc7fe', + id: '8e351291-f603-459c-883b-66ba0d036694', + x: 69, + y: 52, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.74, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'LIKE YOU\nMEAN IT', + width: 427, + height: 104, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '8e351291-f603-459c-883b-66ba0d036694', + id: 'b4571f66-24f3-4908-8c18-8ac378735fb9', + x: 12, + y: 159, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Hind', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 136, + g: 136, + b: 136, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Our lead editor shares tips on\nhow to best start running', + x: 119, + y: 279, + width: 202, + height: 40, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '002f9bfc-2f7a-45b0-b900-78251f2746f1', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Roboto', + weights: [100, 300, 400, 500, 700, 900], + styles: ['italic', 'regular'], + variants: [ + [0, 100], + [1, 100], + [0, 300], + [1, 300], + [0, 400], + [1, 400], + [0, 500], + [1, 500], + [0, 700], + [1, 700], + [0, 900], + [1, 900], + ], + fallbacks: ['Helvetica Neue', 'Helvetica', 'sans-serif'], + service: 'fonts.google.com', + }, + fontWeight: 700, + fontSize: 15, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'See Full Story', + x: 140, + y: 632, + width: 160, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '12bef1fe-a18d-4b9f-aa0b-fd718228e0fd', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 205.5, + y: 589, + width: 29, + height: 29, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: 'b4e310b4-3fde-429f-83c6-1d1a9893d358', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page3_arrow.png`, + width: 7, + height: 3, + posterId: 0, + id: 64, + title: 'fitness_page3_arrow', + alt: 'fitness_page3_arrow', + local: false, + sizes: [], + }, + x: 213, + y: 600.5, + width: 14, + height: 6, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '7119765c-0597-4546-9b2b-77d88ab1fe33', + }, + ], + backgroundElementId: 'dd764483-9e29-4aab-bb58-445d1f043d1a', + backgroundOverlay: 'none', + type: 'page', + id: '2dbd86ab-dab1-4142-996a-5932a57797b3', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 180, + g: 17, + b: 31, + }, + }, + isFill: false, + x: 39.995329496521926, + y: 1.7158732016761635, + width: 146.66666666666666, + height: 146.66666666666666, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '0ed58ad7-eed6-4f1a-80a5-7ce92991677f', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 180, + g: 17, + b: 31, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: false, + type: 'image', + id: '13b1c45d-c6e8-4e59-acde-67f4b2b6ef0c', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page4_bg.jpg`, + width: 220, + height: 288, + posterId: 0, + id: 278, + title: 'fitness_page4_bg', + alt: 'fitness_page4_bg', + local: false, + sizes: [], + }, + scale: 100, + focalX: 49.97721576668946, + focalY: 33.03397382344752, + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: 0, + y: 0, + width: 441, + height: 660, + scale: 160, + focalX: 47.07792207792208, + focalY: 34.523809523809526, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '5ff30b79-b3f9-4e31-aa8d-f62c4e302aec', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page4_bg.jpg`, + width: 220, + height: 288, + posterId: 0, + id: 278, + title: 'fitness_page4_bg', + alt: 'fitness_page4_bg', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 500, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.3, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '5', + x: 175, + y: -81, + width: 251, + height: 650, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + id: '37864aea-dc9e-42c6-bcf2-c1fbc93de1b1', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.74, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TOP\nDISTANCE\nRUNNING\nSHOES', + width: 332, + height: 207, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '9fc66e49-85dd-477d-8bb9-30ae259bc7fe', + id: '7470ab2a-4a73-49aa-aaf5-74cfafb51adb', + x: 20, + y: 325, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Hind', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Hit the trails with the latest and greatest\nlightweight mid-distance shoes for the season.', + width: 182, + height: 80, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '002f9bfc-2f7a-45b0-b900-78251f2746f1', + id: '9832faa6-2c7e-43eb-b75a-b68051da626a', + x: 20, + y: 544, + }, + ], + backgroundElementId: '0ed58ad7-eed6-4f1a-80a5-7ce92991677f', + backgroundOverlay: 'none', + type: 'page', + id: '97a844aa-4ff5-4875-aac0-682bd51b087e', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 180, + g: 17, + b: 31, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'image', + id: '19e74314-7e11-49dd-ab9f-42155fb93091', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page5_bg.jpg`, + width: 229, + height: 157, + posterId: 0, + id: 283, + title: 'fitness_page5_bg', + alt: 'fitness_page5_bg', + local: false, + sizes: [], + }, + scale: 100, + focalX: 50, + focalY: 45.45454545454545, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.74, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'NIKE FLYKNIT FREE RN', + width: 332, + height: 106, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7470ab2a-4a73-49aa-aaf5-74cfafb51adb', + id: '09d35ab8-76f0-4252-838d-4eb54a99c2b3', + x: 23, + y: 20, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Hind', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Ideal for runs up to 3 miles, the Nike Free RN Flyknit delivers a lightweight design and breathable slip so you can get in and hit your stride.', + width: 196, + height: 120, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '9832faa6-2c7e-43eb-b75a-b68051da626a', + id: '73bdf015-c3f1-41fb-bc24-79a1b881d914', + x: 23, + y: 143, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 500, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 0.9, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '3', + width: 251, + height: 450, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '37864aea-dc9e-42c6-bcf2-c1fbc93de1b1', + id: 'a849f15a-ed9b-4589-abe0-c19da7abfcf4', + x: 219, + y: 283, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + isFill: false, + x: 22.5, + y: 275, + width: 90, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: '5ae621aa-0b73-4f4f-888b-ba3787156bf0', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'BUY NOW', + width: 80, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7fc0fd6e-ce7e-4be9-925c-c30a39d7cfeb', + id: 'd4bdc028-59aa-4adc-8762-570676202dc3', + x: 27.5, + y: 280.5, + }, + ], + backgroundElementId: '19e74314-7e11-49dd-ab9f-42155fb93091', + backgroundOverlay: 'none', + type: 'page', + id: '52b7935e-e379-4596-bfbc-3b1ea5ca3503', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'image', + id: '05e35040-f294-42da-b67d-dfd39327b2d8', + resource: { + type: 'image', + mimeType: 'image/jpeg', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page6_bg.jpg`, + width: 220, + height: 165, + posterId: 0, + id: 288, + title: 'fitness_page6_bg', + alt: 'fitness_page6_bg', + local: false, + sizes: [], + }, + scale: 100, + focalX: 50, + focalY: 50, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page6_watch.png`, + width: 220, + height: 341, + posterId: 0, + id: 289, + title: 'fitness_page6_watch', + alt: 'fitness_page6_watch', + local: false, + sizes: [], + }, + x: 28, + y: -1, + width: 385, + height: 597, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'd5858b35-c78c-41e7-9f57-69fa65cf2af7', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 207, + g: 19, + b: 35, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.9, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'THE BEST', + width: 269, + height: 64, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '09d35ab8-76f0-4252-838d-4eb54a99c2b3', + id: '885a6a97-1fb8-4303-a27c-151395801c5b', + x: 29, + y: 22, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.9, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'RUNNING', + width: 269, + height: 64, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '885a6a97-1fb8-4303-a27c-151395801c5b', + id: 'd67e50e4-6a13-49d7-ad18-0594b3a7a493', + x: 29, + y: 77, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 207, + g: 19, + b: 35, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.9, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TECH', + width: 269, + height: 64, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '885a6a97-1fb8-4303-a27c-151395801c5b', + id: 'b2f87955-3372-4dde-bab0-08727084ec46', + x: 28, + y: 132, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 46, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'NIKE + APPLE WATCH', + width: 429, + height: 46, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7470ab2a-4a73-49aa-aaf5-74cfafb51adb', + id: 'c432d288-5b66-4c40-8141-060cc29f9ee6', + x: 5, + y: 518, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Hind', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'Whether you’re an elite athlete or you just want to track your daily activity, Apple Watch Series 5 helps you train smarter to get results faster.', + width: 328, + height: 80, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '73bdf015-c3f1-41fb-bc24-79a1b881d914', + id: 'ae9a760a-a4e8-4759-8407-001b3eb243f3', + x: 56, + y: 564, + }, + ], + backgroundElementId: '05e35040-f294-42da-b67d-dfd39327b2d8', + backgroundOverlay: 'none', + type: 'page', + id: '95ae824a-7060-46cb-a8d2-8d6a721ad1ff', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 180, + g: 17, + b: 31, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '1613baad-b2a9-42e7-b0a6-d2f4c6941f65', + }, + { + opacity: 10, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 51, + g: 51, + b: 51, + }, + }, + isFill: false, + x: -1, + y: 0, + width: 441, + height: 661, + scale: 100, + focalX: 49.87042306278489, + focalY: 57.73748786913331, + mask: { + type: 'rectangle', + }, + type: 'image', + id: '073e252f-7c5c-40cb-b8e9-f30a999019db', + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page7_bg-formatted.png`, + width: 188, + height: 334, + posterId: 0, + id: 296, + title: 'fitness_page7_bg-formatted', + alt: 'fitness_page7_bg-formatted', + local: false, + sizes: [], + }, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 105, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.01, + lineHeight: 0.74, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'GEAR\nUP', + width: 332, + height: 154, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '09d35ab8-76f0-4252-838d-4eb54a99c2b3', + id: '5d25ecb0-3056-4597-ba08-44e958de6069', + x: 18, + y: 30, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 386, + y: 115.5, + width: 15, + height: 15, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: '99e621b0-4ee7-4bce-bec7-cbfd08abc354', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + a: 0.4, + }, + }, + isFill: false, + x: 378, + y: 107.5, + width: 31, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + id: '45a6292b-610f-489f-9743-959c2549e2bf', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 15, + height: 15, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '99e621b0-4ee7-4bce-bec7-cbfd08abc354', + id: 'cf1d4db0-d5d9-47a2-ba70-85618df23959', + x: 228, + y: 248.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + a: 0.4, + }, + }, + isFill: false, + width: 31, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: '45a6292b-610f-489f-9743-959c2549e2bf', + id: 'e1ab85f5-bb82-4514-b8e1-379a03bcc6ea', + x: 220, + y: 240.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + width: 15, + height: 15, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: 'cf1d4db0-d5d9-47a2-ba70-85618df23959', + id: '0584e281-ec48-468c-84ad-c6bc6be4855a', + x: 295, + y: 585.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + a: 0.4, + }, + }, + isFill: false, + width: 31, + height: 31, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'circle', + }, + type: 'shape', + basedOn: 'e1ab85f5-bb82-4514-b8e1-379a03bcc6ea', + id: '16d3451c-1da5-4ec7-9290-82a26679081c', + x: 287, + y: 577.5, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 80, + y: 299, + width: 270, + height: 222, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'e31d98c0-1694-41d8-b228-a56103a79868', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 45, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + isFill: false, + x: 220, + y: 286, + width: 30, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + id: 'd87539da-f753-46fb-9ade-e28d37967cbe', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'BEATS X IN-EAR HEADPHONES', + width: 229, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'c432d288-5b66-4c40-8141-060cc29f9ee6', + id: '0884aabc-ca1e-4b37-a87b-68a6fbf377db', + x: 99, + y: 325, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Hind', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 16, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + letterSpacing: 0, + lineHeight: 1.25, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: + 'With up to 8 hours of battery life and Fast Fuel charging, these earphones let you experience authentic, clear sound throughout your day.', + width: 215, + height: 94, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'ae9a760a-a4e8-4759-8407-001b3eb243f3', + id: 'f24e628b-4f66-4815-9511-ac9adc04ace4', + x: 113, + y: 351, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundColor: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + isFill: false, + width: 90, + height: 30, + scale: 100, + focalX: 50, + focalY: 50, + mask: { + type: 'rectangle', + }, + type: 'shape', + basedOn: '5ae621aa-0b73-4f4f-888b-ba3787156bf0', + id: '9c6adc30-8758-4905-ae43-683e36001a81', + x: 112.5, + y: 472, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 400, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'center', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'BUY NOW', + width: 80, + height: 19, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: 'd4bdc028-59aa-4adc-8762-570676202dc3', + id: '998771ef-0336-4f5e-bdea-fcc6a8491b58', + x: 117.5, + y: 477.5, + }, + ], + backgroundElementId: '1613baad-b2a9-42e7-b0a6-d2f4c6941f65', + backgroundOverlay: 'none', + type: 'page', + id: 'a30a33fc-d687-4e94-8a2c-0f9546a21efe', + }, + { + elements: [ + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundColor: { + color: { + r: 26, + g: 26, + b: 26, + }, + }, + isFill: false, + x: 1, + y: 1, + width: 1, + height: 1, + mask: { + type: 'rectangle', + }, + isBackground: true, + type: 'shape', + id: '641d6b05-3e8c-4c1e-8e57-b1d1a528c119', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_offsetblock.png`, + width: 94, + height: 76, + posterId: 0, + id: 311, + title: 'fitness_page8_offsetblock', + alt: 'fitness_page8_offsetblock', + local: false, + sizes: [], + }, + x: 220, + y: 483, + width: 220, + height: 177, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'e8734d09-cc5d-4cdc-9353-72bcc0ab982f', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_story3-formatted.png`, + width: 95, + height: 97, + posterId: 0, + id: 310, + title: 'fitness_page8_story3-formatted', + alt: 'fitness_page8_story3-formatted', + local: false, + sizes: [], + }, + x: -2, + y: 412, + width: 221, + height: 224, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'ffd4e077-5c4c-4baa-bc7f-56f09b29d0d6', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_story1-formatted.png`, + width: 95, + height: 97, + posterId: 0, + id: 308, + title: 'fitness_page8_story1-formatted', + alt: 'fitness_page8_story1-formatted', + local: false, + sizes: [], + }, + x: 0, + y: 125, + width: 218, + height: 223, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '90f9d543-7728-49f6-a241-965847b7b4ab', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_story2-formatted.png`, + width: 188, + height: 97, + posterId: 0, + id: 309, + title: 'fitness_page8_story2-formatted', + alt: 'fitness_page8_story2-formatted', + local: false, + sizes: [], + }, + x: -3, + y: 237, + width: 441, + height: 227, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'f8f33b69-fab8-40e2-ac67-e1dc8bb5f8ca', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 207, + g: 19, + b: 35, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'MORE', + width: 332, + height: 72, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '5d25ecb0-3056-4597-ba08-44e958de6069', + id: '7447a2ba-a336-4618-beb2-8423ebc1ddc3', + x: 12, + y: 14, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 72, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: -0.01, + lineHeight: 1, + textAlign: 'initial', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'STORIES', + width: 332, + height: 72, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '7447a2ba-a336-4618-beb2-8423ebc1ddc3', + id: 'cc3ac70e-1175-4b75-92eb-26f4656f4811', + x: 11, + y: 67, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'SWEAT RESISTENT\nEARBUDS', + width: 146, + height: 40, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '0884aabc-ca1e-4b37-a87b-68a6fbf377db', + id: '96fea994-b71e-442f-b611-16d73f520016', + x: 257, + y: 168, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: '10 BEST CORE\nTRAINING EXERCISES', + width: 146, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '96fea994-b71e-442f-b611-16d73f520016', + id: '9b117b87-0269-4c4b-9e0d-8cdbcb0df89a', + x: 34, + y: 339, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'left', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'TRIATHLON TRAINING\nIN THE SUMMER', + width: 146, + height: 38, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '96fea994-b71e-442f-b611-16d73f520016', + id: '03c44ea1-7ec8-4bdf-8e49-bb43ba83b2f3', + x: 256, + y: 463, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: false, + backgroundTextMode: 'NONE', + bold: false, + font: { + family: 'Teko', + service: 'fonts.google.com', + fallbacks: ['sans-serif'], + weights: [300, 400, 500, 600, 700], + styles: ['regular'], + variants: [ + [0, 300], + [0, 400], + [0, 500], + [0, 600], + [0, 700], + ], + }, + fontWeight: 600, + fontSize: 20, + fontStyle: 'normal', + backgroundColor: { + color: { + r: 196, + g: 196, + b: 196, + }, + }, + color: { + color: { + r: 255, + g: 255, + b: 255, + }, + }, + letterSpacing: 0, + lineHeight: 1, + textAlign: 'right', + textDecoration: 'none', + padding: { + vertical: 0, + horizontal: 0, + locked: true, + }, + content: 'SOCIAL', + width: 146, + height: 20, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + type: 'text', + basedOn: '03c44ea1-7ec8-4bdf-8e49-bb43ba83b2f3', + id: '0e34db64-1382-4541-83a6-fe86d3c86cd9', + x: 50, + y: 624, + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_Facebook.png`, + width: 6, + height: 11, + posterId: 0, + id: 316, + title: 'fitness_page8_Facebook', + alt: 'fitness_page8_Facebook', + local: false, + sizes: [], + }, + x: 264, + y: 616.5, + width: 12, + height: 22, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '4e5771f8-d164-4b47-a32c-ff483070b6d4', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_Instagram.png`, + width: 12, + height: 12, + posterId: 0, + id: 317, + title: 'fitness_page8_Instagram', + alt: 'fitness_page8_Instagram', + local: false, + sizes: [], + }, + x: 294, + y: 616.5, + width: 22, + height: 22, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'beff317a-ee85-4b5e-90d0-ae9099b66a48', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_Youtube.png`, + width: 13, + height: 9, + posterId: 0, + id: 319, + title: 'fitness_page8_Youtube', + alt: 'fitness_page8_Youtube', + local: false, + sizes: [], + }, + x: 337, + y: 620, + width: 22, + height: 15, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: 'fdfa9c93-2b0e-4fb4-9c72-16bb641b7a0e', + }, + { + opacity: 100, + flip: { + vertical: false, + horizontal: false, + }, + rotationAngle: 0, + lockAspectRatio: true, + scale: 100, + focalX: 50, + focalY: 50, + isFill: false, + resource: { + type: 'image', + mimeType: 'image/png', + src: `${imageBaseUrl}assets/images/templates/fitness/fitness_page8_Twitter.png`, + width: 12, + height: 10, + posterId: 0, + id: 318, + title: 'fitness_page8_Twitter', + alt: 'fitness_page8_Twitter', + local: false, + sizes: [], + }, + x: 379, + y: 617.5, + width: 24, + height: 20, + mask: { + type: 'rectangle', + name: 'Rectangle', + path: 'M 0,0 1,0 1,1 0,1 0,0 Z', + ratio: 1, + }, + type: 'image', + id: '6304b398-11fe-4ad6-ac0a-33e5154e7f6a', + }, + ], + backgroundElementId: '641d6b05-3e8c-4c1e-8e57-b1d1a528c119', + backgroundOverlay: 'none', + type: 'page', + id: 'd65c11a1-5f71-48ff-8579-85d53f18d461', + }, + ], + autoAdvance: true, + defaultPageDuration: 7, + }; +} diff --git a/assets/src/dashboard/templates/data/travel.js b/assets/src/dashboard/templates/data/travel.js index 4f57de739095..6af272d46d09 100644 --- a/assets/src/dashboard/templates/data/travel.js +++ b/assets/src/dashboard/templates/data/travel.js @@ -35,7 +35,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page1_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page1_bg.jpg`, width: 220, height: 147, poster: '', @@ -204,7 +204,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page1_logo.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page1_logo.png`, width: 47, height: 15, poster: '', @@ -472,7 +472,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page2_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page2_image1.jpg`, width: 220, height: 331, poster: '', @@ -730,7 +730,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page3_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page3_image1.jpg`, width: 220, height: 275, poster: '', @@ -765,7 +765,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page4_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page4_bg.jpg`, width: 220, height: 147, poster: '', @@ -1017,7 +1017,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_up_arrow.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_up_arrow.png`, width: 150, height: 69, posterId: 0, @@ -1062,7 +1062,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page5_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page5_bg.jpg`, width: 220, height: 149, poster: '', @@ -1254,7 +1254,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page5_image1.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page5_image1.png`, width: 137, height: 52, poster: '', @@ -1375,7 +1375,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_up_arrow.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_up_arrow.png`, width: 150, height: 69, posterId: 0, @@ -1421,7 +1421,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page6_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page6_bg.jpg`, width: 220, height: 124, poster: '', @@ -1619,7 +1619,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page6_image1.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page6_image1.png`, width: 220, height: 371, poster: '', @@ -1891,7 +1891,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page7_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page7_image1.jpg`, width: 220, height: 330, poster: '', @@ -1926,7 +1926,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page8_bg.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page8_bg.jpg`, width: 220, height: 147, poster: '', @@ -2864,7 +2864,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page9_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page9_image1.jpg`, width: 220, height: 165, poster: '', @@ -2907,7 +2907,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page9_image2.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page9_image2.jpg`, width: 220, height: 124, poster: '', @@ -2950,7 +2950,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page9_image3.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page9_image3.jpg`, width: 220, height: 151, poster: '', @@ -2993,7 +2993,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page9_image4.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page9_image4.jpg`, width: 220, height: 147, poster: '', @@ -3036,7 +3036,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page9_image5.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page9_image5.jpg`, width: 220, height: 330, poster: '', @@ -3377,7 +3377,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page10_image1.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page10_image1.jpg`, width: 220, height: 112, poster: '', @@ -3416,7 +3416,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page10_image2.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page10_image2.jpg`, width: 220, height: 83, poster: '', @@ -3459,7 +3459,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/jpeg', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_page10_image3.jpg`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_page10_image3.jpg`, width: 220, height: 124, poster: '', @@ -3491,7 +3491,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_fb.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_fb.png`, width: 150, height: 300, posterId: 0, @@ -3529,7 +3529,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_insta.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_insta.png`, width: 150, height: 156, posterId: 0, @@ -3567,7 +3567,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_yt.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_yt.png`, width: 150, height: 106, posterId: 0, @@ -3605,7 +3605,7 @@ export default function (imageBaseUrl) { resource: { type: 'image', mimeType: 'image/png', - src: `${imageBaseUrl}/assets/images/templates/travel/travel_icon_tw.png`, + src: `${imageBaseUrl}assets/images/templates/travel/travel_icon_tw.png`, width: 150, height: 121, posterId: 0, diff --git a/assets/src/dashboard/templates/index.js b/assets/src/dashboard/templates/index.js index 11502c10e708..7194f695d00b 100644 --- a/assets/src/dashboard/templates/index.js +++ b/assets/src/dashboard/templates/index.js @@ -17,13 +17,21 @@ /** * Internal dependencies */ -import getTravelStoryData from './data/travel'; +import getCookingStoryData from './data/cooking'; import getDIYStoryData from './data/diy'; +import getEntertainmentStoryData from './data/entertainment'; +import getFashionStoryData from './data/fashion'; +import getFitnessStoryData from './data/fitness'; +import getTravelStoryData from './data/travel'; export default function (config) { const { pluginDir } = config; - const travelStoryData = getTravelStoryData(pluginDir); + const cookingStoryData = getCookingStoryData(pluginDir); const diyStoryData = getDIYStoryData(pluginDir); + const entertainmentData = getEntertainmentStoryData(pluginDir); + const fashionStoryData = getFashionStoryData(pluginDir); + const fitnessStoryData = getFitnessStoryData(pluginDir); + const travelStoryData = getTravelStoryData(pluginDir); const globalConfig = { createdBy: 'Google Web Stories', @@ -61,7 +69,7 @@ export default function (config) { ], description: 'Maecenas ultrices tortor nibh, eu consequat magna maximus non. Quisque nec tellus lacus.', - pages: travelStoryData.pages, + pages: cookingStoryData.pages, }, { id: 3, @@ -93,7 +101,7 @@ export default function (config) { ], description: 'Nam a tellus tortor. Aenean non mi porta quam feugiat vehicula in a lectus. Suspendisse eget justo ac quam.', - pages: travelStoryData.pages, + pages: entertainmentData.pages, }, { id: 5, @@ -109,7 +117,7 @@ export default function (config) { ], description: 'Duis auctor libero vel dui tincidunt, at mattis nisi placerat. Nam id lacinia lectus.', - pages: travelStoryData.pages, + pages: fashionStoryData.pages, }, { id: 6, @@ -123,7 +131,7 @@ export default function (config) { ], description: 'Quisque dignissim urna id lectus ultricies blandit. Cras laoreet pharetra lectus. Nunc mollis suscipit feugiat.', - pages: travelStoryData.pages, + pages: fitnessStoryData.pages, }, { id: 7, diff --git a/assets/src/dashboard/types.js b/assets/src/dashboard/types.js index d78c0296d37a..edd9ef2673a8 100644 --- a/assets/src/dashboard/types.js +++ b/assets/src/dashboard/types.js @@ -32,4 +32,30 @@ export const StoryPropType = PropTypes.shape({ modified: PropTypes.object, }); +export const TagPropType = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired, + count: PropTypes.number.isRequired, +}); + +export const CategoryPropType = PropTypes.shape({ + id: PropTypes.number.isRequired, + parent: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired, + count: PropTypes.number.isRequired, +}); + +export const UserPropType = PropTypes.shape({ + id: PropTypes.number.isRequired, + name: PropTypes.string.isRequired, + slug: PropTypes.string.isRequired, + avatar_urls: PropTypes.object, +}); + export const StoriesPropType = PropTypes.arrayOf(StoryPropType).isRequired; +export const TagsPropType = PropTypes.objectOf(TagPropType).isRequired; +export const CategoriesPropType = PropTypes.objectOf(CategoryPropType) + .isRequired; +export const UsersPropType = PropTypes.objectOf(UserPropType).isRequired; diff --git a/assets/src/dashboard/utils/usePagePreviewSize.js b/assets/src/dashboard/utils/usePagePreviewSize.js index 2e2a992715c0..a441674ac62d 100644 --- a/assets/src/dashboard/utils/usePagePreviewSize.js +++ b/assets/src/dashboard/utils/usePagePreviewSize.js @@ -35,7 +35,7 @@ const getCurrentBp = () => const sizeFromWidth = (width) => ({ width, - height: PAGE_RATIO * width, + height: width / PAGE_RATIO, }); export default function usePagePreviewSize(options = {}) { diff --git a/assets/src/edit-story/app/font/actions/useLoadFontFiles.js b/assets/src/edit-story/app/font/actions/useLoadFontFiles.js index 3f9975b2cbca..774a9cdd2b4e 100644 --- a/assets/src/edit-story/app/font/actions/useLoadFontFiles.js +++ b/assets/src/edit-story/app/font/actions/useLoadFontFiles.js @@ -54,7 +54,7 @@ function useLoadFontFiles() { async ({ font: { family, service, variants }, fontWeight, - fontStyle, + isItalic, }) => { if (!family || service !== 'fonts.google.com') { return null; @@ -62,6 +62,7 @@ function useLoadFontFiles() { const handle = cleanForSlug(family); const elementId = `${handle}-css`; + const fontStyle = isItalic ? 'italic' : 'normal'; const fontFaceSet = ` ${fontStyle || ''} ${fontWeight || ''} 0 '${family}' `.trim(); diff --git a/assets/src/edit-story/app/history/historyProvider.js b/assets/src/edit-story/app/history/historyProvider.js index 3c0b18e6afb3..ccd8c6ef66b9 100644 --- a/assets/src/edit-story/app/history/historyProvider.js +++ b/assets/src/edit-story/app/history/historyProvider.js @@ -18,7 +18,7 @@ * External dependencies */ import PropTypes from 'prop-types'; -import { useEffect } from 'react'; +import { useCallback, useEffect, useState, useRef } from 'react'; /** * Internal dependencies @@ -40,23 +40,37 @@ function HistoryProvider({ children, size }) { versionNumber, } = useHistoryReducer(size); + const [hasNewChanges, setHasNewChanges] = useState(false); const setPreventUnload = usePreventWindowUnload(); + // The version number for the initially loaded (saved) state is 1. + const savedVersionNumber = useRef(1); useEffect(() => { - setPreventUnload('history', versionNumber - 1 > 0); - + setPreventUnload('history', hasNewChanges); return () => setPreventUnload('history', false); - }, [setPreventUnload, versionNumber]); + }, [setPreventUnload, hasNewChanges]); + + useEffect(() => { + setHasNewChanges(versionNumber !== savedVersionNumber.current); + }, [versionNumber]); + + const resetNewChanges = useCallback(() => { + // When new changes are saved, let's track which version was saved. + savedVersionNumber.current = versionNumber; + setHasNewChanges(false); + }, [versionNumber]); const state = { state: { replayState, + hasNewChanges, canUndo: offset < historyLength - 1, canRedo: offset > 0, }, actions: { appendToHistory, clearHistory, + resetNewChanges, undo, redo, }, diff --git a/assets/src/edit-story/app/history/useHistoryReducer.js b/assets/src/edit-story/app/history/useHistoryReducer.js index 0e4ec4425b9f..415ef4f62ede 100644 --- a/assets/src/edit-story/app/history/useHistoryReducer.js +++ b/assets/src/edit-story/app/history/useHistoryReducer.js @@ -63,6 +63,7 @@ const reducer = (size) => (state, { type, payload }) => { case REPLAY: return { ...state, + versionNumber: state.versionNumber + (state.offset - payload), replayState: state.entries[payload], }; diff --git a/assets/src/edit-story/app/index.js b/assets/src/edit-story/app/index.js index 507528068d76..8bab4c11ff53 100644 --- a/assets/src/edit-story/app/index.js +++ b/assets/src/edit-story/app/index.js @@ -35,6 +35,7 @@ import { GlobalStyle as ModalGlobalStyle } from '../components/modal'; import { GlobalStyle as CalendarGlobalStyle } from '../components/form/dateTime/calendarStyle'; import { useDropTargets, DropTargetsProvider } from '../components/dropTargets'; import { useTransform, TransformProvider } from '../components/transform'; +import AutoSaveHandler from '../components/autoSaveHandler'; import { useHistory, HistoryProvider } from './history'; import { useAPI, APIProvider } from './api'; import { useConfig, ConfigProvider } from './config'; @@ -54,6 +55,7 @@ function App({ config }) { + diff --git a/assets/src/edit-story/app/media/mediaProvider.js b/assets/src/edit-story/app/media/mediaProvider.js index 49d7c8c62f16..35dbae93ed3d 100644 --- a/assets/src/edit-story/app/media/mediaProvider.js +++ b/assets/src/edit-story/app/media/mediaProvider.js @@ -145,7 +145,7 @@ function MediaProvider({ children }) { const generatePoster = useCallback(() => { const looper = async () => { await media.reduce((accumulatorPromise, el) => { - return accumulatorPromise.then(() => processor(el)); + return accumulatorPromise.then(() => el && processor(el)); }, Promise.resolve()); }; if (media) { diff --git a/assets/src/edit-story/app/media/test/useUploadMedia.js b/assets/src/edit-story/app/media/test/useUploadMedia.js new file mode 100644 index 000000000000..6372da48aefa --- /dev/null +++ b/assets/src/edit-story/app/media/test/useUploadMedia.js @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { renderHook, act } from '@testing-library/react-hooks'; + +/** + * Internal dependencies + */ +import useUploadMedia from '../useUploadMedia'; + +jest.mock('../../uploader', () => ({ + useUploader: jest.fn(() => ({ + uploadFile: jest.fn(), + })), +})); + +jest.mock('../../snackbar', () => ({ + useSnackbar: () => { + return { + showSnackbar: jest.fn(), + }; + }, +})); + +jest.mock('../../config', () => ({ + useConfig: jest.fn(() => ({ + allowedMimeTypes: { + image: [], + video: [], + }, + })), +})); + +jest.mock('../../../app/media/utils'); +import { getResourceFromLocalFile } from '../../../app/media/utils'; + +describe('useUploadMedia', () => { + it('should only setMedia for files supported by getResourceFromLocalFile', async () => { + const supportedLocalResource = ['image', 'video']; + + const media = [ + { type: 'image/jpeg', src: 'image1.jpg' }, + { type: 'image/jpeg', src: 'image2.jpg' }, + { type: 'image/jpeg', src: 'image3.jpg' }, + ]; + const newFiles = [ + { type: 'video/mp4', src: 'video1.mp4' }, + { type: 'text/plain', src: 'text.txt' }, // Unsupported File Format + { type: 'image/jpeg', src: 'image5.jpg' }, + ]; + + const pagingNum = 1; + const mediaType = undefined; + const setMedia = jest.fn(); + const fetchMedia = jest.fn(); + + const expectedSetMediaArgs = [ + ...media, + ...newFiles.filter(({ type }) => supportedLocalResource.includes(type)), + ]; + + // Simple implementation of getResourceFromLocalFile that will return + // null on unsupported resources. + getResourceFromLocalFile.mockImplementation((e) => { + if (supportedLocalResource.includes(e.type)) { + return e; + } else { + return null; + } + }); + + const { result } = renderHook(() => + useUploadMedia({ + media, + pagingNum, + mediaType, + fetchMedia, + setMedia, + }) + ); + + const { uploadMedia } = result.current; + + await act(() => uploadMedia(newFiles)); + + expect(setMedia).toHaveBeenCalledTimes(1); + + const setMediaArgs = setMedia.mock.calls[0][0].media; + + // Should have skipped every unsupported file. + expect(setMediaArgs).toIncludeSameMembers(expectedSetMediaArgs); + }); +}); diff --git a/assets/src/edit-story/app/media/useUploadMedia/useUploadMedia.js b/assets/src/edit-story/app/media/useUploadMedia/useUploadMedia.js index 5be8cbf542de..ddda32adaa7a 100644 --- a/assets/src/edit-story/app/media/useUploadMedia/useUploadMedia.js +++ b/assets/src/edit-story/app/media/useUploadMedia/useUploadMedia.js @@ -23,7 +23,6 @@ import { useCallback, useState } from 'react'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies @@ -36,9 +35,10 @@ import { getResourceFromAttachment, } from '../../../app/media/utils'; import usePreventWindowUnload from '../../../utils/usePreventWindowUnload'; +import createError from '../../../utils/createError'; function useUploadMedia({ media, pagingNum, mediaType, fetchMedia, setMedia }) { - const { uploadFile } = useUploader(); + const { uploadFile, isValidType } = useUploader(); const { showSnackbar } = useSnackbar(); const { allowedFileTypes } = useConfig(); const [isUploading, setIsUploading] = useState(false); @@ -51,12 +51,29 @@ function useUploadMedia({ media, pagingNum, mediaType, fetchMedia, setMedia }) { setIsUploading(true); setPreventUnload('upload', true); + files.reverse().map((file) => { + if (!isValidType(file)) { + /* translators: %s is a list of allowed file extensions. */ + const message = sprintf( + /* translators: %s: list of allowed file types. */ + __('Please choose only %s to upload.', 'web-stories'), + allowedFileTypes.join( + /* translators: delimiter used in a list */ + __(', ', 'web-stories') + ) + ); + + throw createError('ValidError', file.name, message); + } + }); + localFiles = await Promise.all( files.reverse().map(async (file) => ({ localResource: await getResourceFromLocalFile(file), file, })) ); + localFiles = localFiles.filter((f) => f.localResource != null); if (onLocalFile) { localFiles = localFiles.map(({ localResource, file }) => { @@ -77,19 +94,7 @@ function useUploadMedia({ media, pagingNum, mediaType, fetchMedia, setMedia }) { setIsUploading(false); showSnackbar({ - message: createInterpolateElement( - sprintf( - /* translators: %s: list of allowed file types. */ - __('Please choose only %s to upload.', 'web-stories'), - allowedFileTypes.join( - /* translators: delimiter used in a list */ - __(', ', 'web-stories') - ) - ), - { - b: , - } - ), + message: e.message, }); return; } @@ -140,6 +145,7 @@ function useUploadMedia({ media, pagingNum, mediaType, fetchMedia, setMedia }) { pagingNum, mediaType, uploadFile, + isValidType, setPreventUnload, ] ); diff --git a/assets/src/edit-story/app/story/actions/useSaveStory.js b/assets/src/edit-story/app/story/actions/useSaveStory.js index 2db7ff68c743..434c462c3f16 100644 --- a/assets/src/edit-story/app/story/actions/useSaveStory.js +++ b/assets/src/edit-story/app/story/actions/useSaveStory.js @@ -32,8 +32,8 @@ import { useAPI } from '../../api'; import { useConfig } from '../../config'; import useRefreshPostEditURL from '../../../utils/useRefreshPostEditURL'; import { useSnackbar } from '../../snackbar'; -import usePreventWindowUnload from '../../../utils/usePreventWindowUnload'; import getStoryPropsToSave from '../utils/getStoryPropsToSave'; +import { useHistory } from '../../history'; /** * Custom hook to save story. @@ -48,10 +48,12 @@ function useSaveStory({ storyId, pages, story, updateStory }) { const { actions: { saveStoryById }, } = useAPI(); + const { + actions: { resetNewChanges }, + } = useHistory(); const { metadata } = useConfig(); const { showSnackbar } = useSnackbar(); const [isSaving, setIsSaving] = useState(false); - const setPreventUnload = usePreventWindowUnload(); const refreshPostEditURL = useRefreshPostEditURL(storyId); @@ -79,7 +81,7 @@ function useSaveStory({ storyId, pages, story, updateStory }) { }) .finally(() => { setIsSaving(false); - setPreventUnload('history', false); + resetNewChanges(); }); }, [ @@ -91,7 +93,7 @@ function useSaveStory({ storyId, pages, story, updateStory }) { updateStory, refreshPostEditURL, showSnackbar, - setPreventUnload, + resetNewChanges, ] ); diff --git a/assets/src/edit-story/app/story/effects/useHistoryEntry.js b/assets/src/edit-story/app/story/effects/useHistoryEntry.js index 4b34eef31aaa..f8675eb9495a 100644 --- a/assets/src/edit-story/app/story/effects/useHistoryEntry.js +++ b/assets/src/edit-story/app/story/effects/useHistoryEntry.js @@ -25,7 +25,7 @@ import { useEffect, useRef } from 'react'; import { useHistory } from '../../'; // Record any change to core variables in history (history will know if it's a replay) -function useHistoryEntry({ story, current, pages, selection, capabilities }) { +function useHistoryEntry({ story, current, pages, selection }) { const { actions: { appendToHistory }, } = useHistory(); @@ -43,9 +43,8 @@ function useHistoryEntry({ story, current, pages, selection, capabilities }) { current: currentPageIndexRef.current, selection: selectedElementIdsRef.current, pages, - capabilities, }); - }, [capabilities, story, pages, appendToHistory]); + }, [story, pages, appendToHistory]); } export default useHistoryEntry; diff --git a/assets/src/edit-story/app/story/effects/useHistoryReplay.js b/assets/src/edit-story/app/story/effects/useHistoryReplay.js index ec8f03cd584c..bb23a1d500bc 100644 --- a/assets/src/edit-story/app/story/effects/useHistoryReplay.js +++ b/assets/src/edit-story/app/story/effects/useHistoryReplay.js @@ -32,13 +32,12 @@ function useHistoryReplay({ restore }) { if (!replayState) { return; } - const { current, pages, selection, story, capabilities } = replayState; + const { current, pages, selection, story } = replayState; restore({ pages, current, story, selection, - capabilities, }); }, [restore, replayState]); } diff --git a/assets/src/edit-story/app/story/effects/useLoadStory.js b/assets/src/edit-story/app/story/effects/useLoadStory.js index 4b3d4d7cdf0f..0746d62dce56 100644 --- a/assets/src/edit-story/app/story/effects/useLoadStory.js +++ b/assets/src/edit-story/app/story/effects/useLoadStory.js @@ -26,18 +26,6 @@ import { useAPI, useHistory } from '../../'; import { createPage } from '../../../elements'; import { migrate } from '../../../migration'; -/** - * Get the permission by checking for fields in the REST API. - * - * @param {Object} post Current post object. - * @param {Object} post._links Embed links - * @param {string} field Requested field. - * @return {boolean} If user has capability, defaults to false. - */ -const getPerm = (post, field) => { - return Boolean(post?._links?.[field]); -}; - // When ID is set, load story from API. function useLoadStory({ storyId, shouldLoad, restore }) { const { @@ -123,17 +111,12 @@ function useLoadStory({ storyId, shouldLoad, restore }) { defaultPageDuration: storyData?.defaultPageDuration, }; - const hasPublishAction = getPerm(post, 'wp:action-publish'); - const hasAssignAuthorAction = getPerm(post, 'wp:action-assign-author'); - - const capabilities = { hasPublishAction, hasAssignAuthorAction }; // TODO read current page and selection from deeplink? restore({ pages, story, selection: [], current: null, // will be set to first page by `restore` - capabilities, }); }); } diff --git a/assets/src/edit-story/app/story/storyProvider.js b/assets/src/edit-story/app/story/storyProvider.js index 5fb052def7f9..028cb86a64ee 100644 --- a/assets/src/edit-story/app/story/storyProvider.js +++ b/assets/src/edit-story/app/story/storyProvider.js @@ -36,7 +36,7 @@ import useAutoSave from './actions/useAutoSave'; function StoryProvider({ storyId, children }) { const { - state: { pages, current, selection, story, capabilities }, + state: { pages, current, selection, story }, api, internal: { restore }, } = useStoryReducer(); @@ -89,7 +89,7 @@ function StoryProvider({ storyId, children }) { useLoadStory({ restore, shouldLoad, storyId }); // These effects send updates to and restores state from history. - useHistoryEntry({ pages, current, selection, story, capabilities }); + useHistoryEntry({ pages, current, selection, story }); useHistoryReplay({ restore }); // Ensure all pages have a background element at all times @@ -128,7 +128,6 @@ function StoryProvider({ storyId, children }) { selectedElements, hasSelection, story, - capabilities, meta: { isSaving: isSaving || isAutoSaving, }, diff --git a/assets/src/edit-story/app/story/useStoryReducer/actions.js b/assets/src/edit-story/app/story/useStoryReducer/actions.js index f8f07f0e5f39..a57396fbca95 100644 --- a/assets/src/edit-story/app/story/useStoryReducer/actions.js +++ b/assets/src/edit-story/app/story/useStoryReducer/actions.js @@ -160,16 +160,10 @@ export const exposedActions = { }; // Internal actions -const restore = (dispatch) => ({ - pages, - selection, - current, - story, - capabilities, -}) => +const restore = (dispatch) => ({ pages, selection, current, story }) => dispatch({ type: types.RESTORE, - payload: { pages, selection, current, story, capabilities }, + payload: { pages, selection, current, story }, }); export const internalActions = { diff --git a/assets/src/edit-story/app/story/useStoryReducer/reducers/restore.js b/assets/src/edit-story/app/story/useStoryReducer/reducers/restore.js index 85138af142e0..ae49c521771e 100644 --- a/assets/src/edit-story/app/story/useStoryReducer/reducers/restore.js +++ b/assets/src/edit-story/app/story/useStoryReducer/reducers/restore.js @@ -28,13 +28,12 @@ * @param {Object} payload New state to set. * @return {Object} New state */ -function restore(state, { pages, current, selection, story, capabilities }) { +function restore(state, { pages, current, selection, story }) { if (!Array.isArray(pages) || pages.length === 0) { return state; } const newStory = typeof story === 'object' ? story : {}; - const newCapabilities = typeof capabilities === 'object' ? capabilities : {}; const newCurrent = pages.some(({ id }) => id === current) ? current : pages[0].id; @@ -45,7 +44,6 @@ function restore(state, { pages, current, selection, story, capabilities }) { current: newCurrent, selection: newSelection, story: newStory, - capabilities: newCapabilities, }; } diff --git a/assets/src/edit-story/app/story/useStoryReducer/test/restore.js b/assets/src/edit-story/app/story/useStoryReducer/test/restore.js index 42ab1fe986cc..a52a22cf97ed 100644 --- a/assets/src/edit-story/app/story/useStoryReducer/test/restore.js +++ b/assets/src/edit-story/app/story/useStoryReducer/test/restore.js @@ -34,7 +34,6 @@ describe('restore', () => { selection: [], current: '123', story: {}, - capabilities: {}, }); }); @@ -90,7 +89,6 @@ describe('restore', () => { current: '111', selection: ['222'], story: { a: 1 }, - capabilities: { b: 2 }, }); expect(result).toStrictEqual({ @@ -98,7 +96,6 @@ describe('restore', () => { selection: [], current: null, story: {}, - capabilities: {}, }); }); @@ -114,7 +111,6 @@ describe('restore', () => { selection: [], current: null, story: {}, - capabilities: {}, }); }); @@ -129,7 +125,6 @@ describe('restore', () => { current: '222', selection: ['333'], story: { a: 1 }, - capabilities: { b: 2 }, }); // And validate that it is non-initial. @@ -137,7 +132,6 @@ describe('restore', () => { expect(stateWithContent.current).not.toBeNull(); expect(stateWithContent.selection).not.toHaveLength(0); expect(Object.keys(stateWithContent.story)).not.toHaveLength(0); - expect(Object.keys(stateWithContent.capabilities)).not.toHaveLength(0); // Then override by restoring to a new state. const pages = [{ id: '123' }]; @@ -148,7 +142,6 @@ describe('restore', () => { selection: [], current: '123', story: {}, - capabilities: {}, }); }); }); diff --git a/assets/src/edit-story/app/story/useStoryReducer/useStoryReducer.js b/assets/src/edit-story/app/story/useStoryReducer/useStoryReducer.js index 499500abc859..1c7375a6727f 100644 --- a/assets/src/edit-story/app/story/useStoryReducer/useStoryReducer.js +++ b/assets/src/edit-story/app/story/useStoryReducer/useStoryReducer.js @@ -30,7 +30,6 @@ const INITIAL_STATE = { current: null, selection: [], story: {}, - capabilities: {}, }; /** diff --git a/assets/src/edit-story/app/uploader/test/useUploader.js b/assets/src/edit-story/app/uploader/test/useUploader.js new file mode 100644 index 000000000000..da6d66ab44fe --- /dev/null +++ b/assets/src/edit-story/app/uploader/test/useUploader.js @@ -0,0 +1,85 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies + */ +import { renderHook } from '@testing-library/react-hooks'; + +/** + * Internal dependencies + */ +import ConfigContext from '../../config/context'; +import useUploader from '../useUploader'; + +function setup(args, refreshLibrary = false) { + const configValue = { + api: {}, + allowedMimeTypes: { + image: ['image/png', 'image/jpeg', 'image/jpg', 'image/gif'], + video: ['video/mp4'], + }, + allowedFileTypes: ['png', 'jpeg', 'jpg', 'gif', 'mp4'], + maxUpload: 104857600, + capabilities: { + hasUploadMediaAction: true, + }, + ...args, + }; + const wrapper = (params) => ( + + {params.children} + + ); + const { result } = renderHook(() => useUploader(refreshLibrary), { wrapper }); + return { + uploadFile: result.current.uploadFile, + isValidType: result.current.isValidType, + }; +} + +describe('useUploader', () => { + it('throws an error when user does not have upload permissions', () => { + const { uploadFile } = setup({ + capabilities: { + hasUploadMediaAction: false, + }, + }); + + expect(() => { + uploadFile({}); + }).toThrow('Sorry, you are unable to upload files.'); + }); + it('user uploads a to large file', () => { + const { uploadFile } = setup({ + maxUpload: 2000000, + }); + expect(() => { + uploadFile({ size: 3000000 }); + }).toThrow( + 'Your file is 3MB and the upload limit is 2MB. Please resize and try again!' + ); + }); + it('user uploads an invalid file', () => { + const { uploadFile } = setup({}); + expect(() => { + uploadFile({ size: 20000, type: 'application/pdf' }); + }).toThrow('Please choose only png, jpeg, jpg, gif, mp4 to upload.'); + }); + it('isValidType is given an inavlid file', () => { + const { isValidType } = setup({}); + expect(isValidType({ type: 'application/pdf' })).toBe(false); + }); +}); diff --git a/assets/src/edit-story/app/uploader/useUploader.js b/assets/src/edit-story/app/uploader/useUploader.js index e6f6b8c460dd..a0ec1e8010ca 100644 --- a/assets/src/edit-story/app/uploader/useUploader.js +++ b/assets/src/edit-story/app/uploader/useUploader.js @@ -23,7 +23,6 @@ import { useCallback } from 'react'; * WordPress dependencies */ import { __, sprintf } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; /** * Internal dependencies @@ -31,6 +30,7 @@ import { createInterpolateElement } from '@wordpress/element'; import { useAPI } from '../../app/api'; import { useConfig } from '../config'; import { useMedia } from '../media'; +import createError from '../../utils/createError'; function useUploader(refreshLibrary = true) { const { @@ -47,6 +47,7 @@ function useUploader(refreshLibrary = true) { video: allowedVideoMimeTypes, }, allowedFileTypes, + capabilities: { hasUploadMediaAction }, } = useConfig(); const allowedMimeTypes = [...allowedImageMimeTypes, ...allowedVideoMimeTypes]; @@ -67,14 +68,21 @@ function useUploader(refreshLibrary = true) { ); const uploadFile = (file) => { - // TODO Add permission check here, see Gutenberg's userCan function. - if (!fileSizeCheck(file)) { - const sizeError = new Error(); - sizeError.name = 'SizeError'; - sizeError.file = file.name; - sizeError.isUserError = true; + if (!hasUploadMediaAction) { + const message = __( + 'Sorry, you are unable to upload files.', + 'web-stories' + ); + const permissionError = createError( + 'PermissionError', + file.name, + message + ); - sizeError.message = sprintf( + throw permissionError; + } + if (!fileSizeCheck(file)) { + const message = sprintf( /* translators: first %s is the file size in MB and second %s is the upload file limit in MB */ __( 'Your file is %1$sMB and the upload limit is %2$sMB. Please resize and try again!', @@ -83,29 +91,24 @@ function useUploader(refreshLibrary = true) { bytesToMB(file.size), bytesToMB(maxUpload) ); + const sizeError = createError('SizeError', file.name, message); + throw sizeError; } if (!isValidType(file)) { - const validError = new Error(); - validError.isUserError = true; - validError.name = 'ValidError'; - validError.file = file.name; - /* translators: %s is a list of allowed file extensions. */ - validError.message = createInterpolateElement( - sprintf( - /* translators: %s: list of allowed file types. */ - __('Please choose only %s to upload.', 'web-stories'), - allowedFileTypes.join( - /* translators: delimiter used in a list */ - __(', ', 'web-stories') - ) - ), - { - b: , - } + const message = sprintf( + /* translators: %s: list of allowed file types. */ + __('Please choose only %s to upload.', 'web-stories'), + allowedFileTypes.join( + /* translators: delimiter used in a list */ + __(', ', 'web-stories') + ) ); + + const validError = createError('ValidError', file.name, message); + throw validError; } diff --git a/assets/src/edit-story/components/autoSaveHandler/index.js b/assets/src/edit-story/components/autoSaveHandler/index.js new file mode 100644 index 000000000000..18906c360984 --- /dev/null +++ b/assets/src/edit-story/components/autoSaveHandler/index.js @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useEffect } from 'react'; + +/** + * Internal dependencies + */ +import { useConfig, useHistory, useStory } from '../../app'; + +function AutoSaveHandler() { + const { autoSaveInterval } = useConfig(); + const { + state: { hasNewChanges }, + } = useHistory(); + const { + state: { + story: { status }, + }, + actions: { saveStory }, + } = useStory(); + + const isDraft = 'draft' === status; + + // If autoSaveInterval is set to 0 or not defined, don't. + if (!autoSaveInterval) { + return null; + } + + useEffect(() => { + // @todo The isDraft check is temporary to ensure only draft gets auto-saved, + // until the logic for other statuses has been decided. + if (!isDraft || !hasNewChanges) { + return undefined; + } + let timeout = setTimeout(() => { + saveStory(); + timeout = null; + }, autoSaveInterval * 1000); + + return () => { + timeout && clearTimeout(timeout); + }; + }, [autoSaveInterval, isDraft, saveStory, status, hasNewChanges]); + + return null; +} + +export default AutoSaveHandler; diff --git a/assets/src/edit-story/components/autoSaveHandler/test/autoSaveHandler.js b/assets/src/edit-story/components/autoSaveHandler/test/autoSaveHandler.js new file mode 100644 index 000000000000..004aea1484fa --- /dev/null +++ b/assets/src/edit-story/components/autoSaveHandler/test/autoSaveHandler.js @@ -0,0 +1,89 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { render } from '@testing-library/react'; +import React from 'react'; + +/** + * Internal dependencies + */ +import HistoryContext from '../../../app/history/context'; +import StoryContext from '../../../app/story/context'; +import ConfigContext from '../../../app/config/context'; +import AutoSaveHandler from '../index'; + +function setup({ + hasNewChanges = true, + status = 'draft', + autoSaveInterval = 0.1, +}) { + const saveStory = jest.fn(); + const historyContextValue = { state: { hasNewChanges } }; + const configValue = { + autoSaveInterval, + }; + const storyContextValue = { + state: { + story: { status }, + }, + actions: { saveStory }, + }; + render( + + + + + + + + ); + return { + saveStory, + }; +} + +jest.useFakeTimers(); + +describe('AutoSaveHandler', () => { + it('should trigger saving in case of a draft', () => { + const { saveStory } = setup({}); + jest.runAllTimers(); + expect(saveStory).toHaveBeenCalledTimes(1); + }); + + it('should not trigger saving in case of not having new changes', () => { + const { saveStory } = setup({ hasNewChanges: false }); + jest.runAllTimers(); + expect(saveStory).toHaveBeenCalledTimes(0); + }); + + it('should not trigger saving in case of a non-draft post', () => { + const { saveStory } = setup({ hasNewChanges: true, status: 'publish' }); + jest.runAllTimers(); + expect(saveStory).toHaveBeenCalledTimes(0); + }); + + it('should not trigger saving when interval is 0', () => { + const { saveStory } = setup({ + autoSaveInterval: 0, + }); + jest.runAllTimers(); + expect(saveStory).toHaveBeenCalledTimes(0); + }); +}); diff --git a/assets/src/edit-story/components/canvas/canvasProvider.js b/assets/src/edit-story/components/canvas/canvasProvider.js index 842003998b6b..29cb7f0abe0a 100644 --- a/assets/src/edit-story/components/canvas/canvasProvider.js +++ b/assets/src/edit-story/components/canvas/canvasProvider.js @@ -24,10 +24,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'; * Internal dependencies */ import { useStory } from '../../app'; -import { - DEFAULT_EDITOR_PAGE_WIDTH, - DEFAULT_EDITOR_PAGE_HEIGHT, -} from '../../constants'; +import { PAGE_WIDTH, PAGE_RATIO } from '../../constants'; import { UnitsProvider } from '../../units'; import useEditingElement from './useEditingElement'; import useCanvasSelectionCopyPaste from './useCanvasSelectionCopyPaste'; @@ -38,8 +35,8 @@ function CanvasProvider({ children }) { const lastSelectedElementId = useRef(null); const [pageSize, setPageSize] = useState({ - width: DEFAULT_EDITOR_PAGE_WIDTH, - height: DEFAULT_EDITOR_PAGE_HEIGHT, + width: PAGE_WIDTH, + height: PAGE_WIDTH / PAGE_RATIO, }); const [pageContainer, setPageContainer] = useState(null); diff --git a/assets/src/edit-story/components/canvas/carousel/index.js b/assets/src/edit-story/components/canvas/carousel/index.js index 2287e8fe95a3..f00046be8caf 100644 --- a/assets/src/edit-story/components/canvas/carousel/index.js +++ b/assets/src/edit-story/components/canvas/carousel/index.js @@ -64,7 +64,7 @@ const CAROUSEL_BOTTOM_SCROLL_MARGIN = 8; const Wrapper = styled.div` position: relative; display: grid; - grid: 'prev-navigation carousel next-navigation menu' auto / 53px 1fr 53px 53px; + grid: 'space prev-navigation carousel next-navigation menu' auto / 53px 53px 1fr 53px 53px; background-color: ${({ theme }) => theme.colors.bg.v1}; color: ${({ theme }) => theme.colors.fg.v1}; width: 100%; @@ -320,6 +320,7 @@ function Carousel() { return ( <> + - {currentPage && - currentPage.elements.map(({ id, ...rest }) => { - if (editingElement === id) { - return null; - } - return ( - - ); - })} + {currentPage + ? currentPage.elements.map(({ id, ...rest }) => { + if (editingElement === id) { + return null; + } + return ( + + ); + }) + : null} ); diff --git a/assets/src/edit-story/components/canvas/layout.js b/assets/src/edit-story/components/canvas/layout.js index b03842ab31f7..277f06403b2f 100644 --- a/assets/src/edit-story/components/canvas/layout.js +++ b/assets/src/edit-story/components/canvas/layout.js @@ -18,13 +18,16 @@ * External dependencies */ import styled from 'styled-components'; +import PropTypes from 'prop-types'; +import { forwardRef } from 'react'; /** * Internal dependencies */ import { - DEFAULT_EDITOR_PAGE_WIDTH, - DEFAULT_EDITOR_PAGE_HEIGHT, + FULLBLEED_RATIO, + PAGE_RATIO, + ALLOWED_EDITOR_PAGE_WIDTHS, HEADER_HEIGHT, PAGE_NAV_WIDTH, } from '../../constants'; @@ -63,18 +66,6 @@ const MIN_CAROUSEL_HEIGHT = const MAX_CAROUSEL_HEIGHT = MAX_CAROUSEL_THUMB_HEIGHT + CAROUSEL_VERTICAL_PADDING * 2; -const LARGE_EDITOR_PAGE_SIZE = [ - DEFAULT_EDITOR_PAGE_WIDTH, - DEFAULT_EDITOR_PAGE_HEIGHT, -]; -const MEDIUM_EDITOR_PAGE_SIZE = [280, 420]; -const SMALL_EDITOR_PAGE_SIZE = [240, 360]; -const ALLOWED_PAGE_SIZES = [ - LARGE_EDITOR_PAGE_SIZE, - MEDIUM_EDITOR_PAGE_SIZE, - SMALL_EDITOR_PAGE_SIZE, -]; - // @todo: the menu height is not responsive const Layer = styled.div` ${pointerEventsCss} @@ -91,14 +82,17 @@ const Layer = styled.div` grid: 'head head head head head ' ${HEADER_HEIGHT}px '. . . . . ' minmax(16px, 1fr) - '. prev page next . ' var(--page-height-px) + '. prev page next . ' var( + --fullbleed-height-px + ) '. . menu . . ' ${MENU_HEIGHT}px '. . . . . ' 1fr 'carousel carousel carousel carousel carousel' minmax( ${MIN_CAROUSEL_HEIGHT}px, ${MAX_CAROUSEL_HEIGHT}px ) - / 1fr ${PAGE_NAV_WIDTH}px var(--page-width-px) ${PAGE_NAV_WIDTH}px 1fr; + / 1fr ${PAGE_NAV_WIDTH}px var(--fullbleed-width-px) + ${PAGE_NAV_WIDTH}px 1fr; `; const Area = styled.div` @@ -114,7 +108,44 @@ const Area = styled.div` // Page area is not `overflow:hidden` by default to allow different clipping // mechanisms. -const PageArea = styled(Area).attrs({ area: 'page', overflowAllowed: true })``; +const PageAreaFullbleedContainer = styled(Area).attrs({ + area: 'page', + overflowAllowed: false, +})` + display: flex; + justify-content: center; + align-items: center; + background-color: ${({ theme }) => theme.colors.fg.v1}; +`; + +const PageAreaSafeZone = styled.div` + width: 100%; + height: var(--page-height-px); + overflow: visible; + position: relative; +`; + +const PageAreaDangerZone = styled.div` + pointer-events: none; + position: absolute; + background-image: repeating-linear-gradient( + -45deg, + transparent 0 10px, + black 10px 20px + ); + opacity: 0.05; + width: 100%; + height: calc((var(--fullbleed-height-px) - var(--page-height-px)) / 2); + z-index: 1; +`; + +const PageAreaDangerZoneTop = styled(PageAreaDangerZone)` + top: 0; +`; + +const PageAreaDangerZoneBottom = styled(PageAreaDangerZone)` + bottom: 0; +`; const HeadArea = styled(Area).attrs({ area: 'head', overflowAllowed: false })``; @@ -150,16 +181,11 @@ function useLayoutParams(containerRef) { const maxHeight = height - HEADER_HEIGHT - MENU_HEIGHT - MIN_CAROUSEL_HEIGHT; - // Find the first size that fits within the [maxWidth, maxHeight]. - let bestSize = ALLOWED_PAGE_SIZES[ALLOWED_PAGE_SIZES.length - 1]; - for (let i = 0; i < ALLOWED_PAGE_SIZES.length; i++) { - const size = ALLOWED_PAGE_SIZES[i]; - if (size[0] <= maxWidth && size[1] <= maxHeight) { - bestSize = size; - break; - } - } - setPageSize({ width: bestSize[0], height: bestSize[1] }); + let bestSize = + ALLOWED_EDITOR_PAGE_WIDTHS.find( + (size) => size <= maxWidth && size / FULLBLEED_RATIO <= maxHeight + ) || ALLOWED_EDITOR_PAGE_WIDTHS[ALLOWED_EDITOR_PAGE_WIDTHS.length - 1]; + setPageSize({ width: bestSize, height: bestSize / PAGE_RATIO }); }); } @@ -170,9 +196,30 @@ function useLayoutParamsCssVars() { return { '--page-width-px': `${pageSize.width}px`, '--page-height-px': `${pageSize.height}px`, + '--fullbleed-width-px': `${pageSize.width}px`, + '--fullbleed-height-px': `${pageSize.width / FULLBLEED_RATIO}px`, }; } +const PageArea = forwardRef(({ children, showDangerZone }, ref) => { + return ( + + {children} + {showDangerZone && ( + <> + + + + )} + + ); +}); + +PageArea.propTypes = { + children: PropTypes.node, + showDangerZone: PropTypes.bool, +}; + export { Layer, PageArea, diff --git a/assets/src/edit-story/components/canvas/pagemenu/index.js b/assets/src/edit-story/components/canvas/pagemenu/index.js index 69fec42a4108..6cf4b816abad 100644 --- a/assets/src/edit-story/components/canvas/pagemenu/index.js +++ b/assets/src/edit-story/components/canvas/pagemenu/index.js @@ -148,7 +148,7 @@ function PageMenu() { )} - + { if (color) { load(color); + } else { + // If no color given, load solid black + load(createSolid(0, 0, 0)); } }, [color, load]); diff --git a/assets/src/edit-story/components/form/color/color.js b/assets/src/edit-story/components/form/color/color.js index 562a76f39149..4d840bf7a74f 100644 --- a/assets/src/edit-story/components/form/color/color.js +++ b/assets/src/edit-story/components/form/color/color.js @@ -64,7 +64,7 @@ function ColorInput({ } ColorInput.propTypes = { - value: PatternPropType, + value: PropTypes.oneOfType([PatternPropType, PropTypes.string]), hasGradient: PropTypes.bool, hasOpacity: PropTypes.bool, onChange: PropTypes.func.isRequired, diff --git a/assets/src/edit-story/components/form/color/colorPreview.js b/assets/src/edit-story/components/form/color/colorPreview.js index 1fc65f09c7aa..4f201115604e 100644 --- a/assets/src/edit-story/components/form/color/colorPreview.js +++ b/assets/src/edit-story/components/form/color/colorPreview.js @@ -172,7 +172,7 @@ function ColorPreview({ diff --git a/assets/src/edit-story/components/form/color/opacityPreview.js b/assets/src/edit-story/components/form/color/opacityPreview.js index 21d24f097af4..135dba69feef 100644 --- a/assets/src/edit-story/components/form/color/opacityPreview.js +++ b/assets/src/edit-story/components/form/color/opacityPreview.js @@ -31,6 +31,7 @@ import { _x, __ } from '@wordpress/i18n'; */ import { PatternPropType } from '../../../types'; import useFocusAndSelect from '../../../utils/useFocusAndSelect'; +import { MULTIPLE_VALUE } from '../'; import getPreviewText from './getPreviewText'; import getPreviewOpacity from './getPreviewOpacity'; import ColorBox from './colorBox'; @@ -47,7 +48,8 @@ const Input = styled(ColorBox).attrs({ `; function OpacityPreview({ value, onChange }) { - const hasPreviewText = Boolean(getPreviewText(value)); + const hasPreviewText = + value !== MULTIPLE_VALUE && Boolean(getPreviewText(value)); const postfix = _x('%', 'Percentage', 'web-stories'); const [inputValue, setInputValue] = useState(''); const ref = useRef(); @@ -91,7 +93,7 @@ function OpacityPreview({ value, onChange }) { } OpacityPreview.propTypes = { - value: PatternPropType, + value: PropTypes.oneOfType([PatternPropType, PropTypes.string]), onChange: PropTypes.func.isRequired, }; diff --git a/assets/src/edit-story/components/header/buttons.js b/assets/src/edit-story/components/header/buttons.js index 418fa78d9220..7b8c479c8e1f 100644 --- a/assets/src/edit-story/components/header/buttons.js +++ b/assets/src/edit-story/components/header/buttons.js @@ -29,7 +29,7 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import addQueryArgs from '../../utils/addQueryArgs'; -import { useStory, useMedia, useConfig } from '../../app'; +import { useStory, useMedia, useConfig, useHistory } from '../../app'; import useRefreshPostEditURL from '../../utils/useRefreshPostEditURL'; import { Outline, Plain, Primary } from '../button'; import CircularProgress from '../circularProgress'; @@ -223,6 +223,9 @@ function Update() { const { state: { isUploading }, } = useMedia(); + const { + state: { hasNewChanges }, + } = useHistory(); let text; switch (status) { @@ -238,7 +241,7 @@ function Update() { return ( saveStory({ status: 'draft' })} - isDisabled={isSaving || isUploading} + isDisabled={isSaving || isUploading || !hasNewChanges} > {text} diff --git a/assets/src/edit-story/components/inspector/document/publish/index.js b/assets/src/edit-story/components/inspector/document/publish/index.js index 7d98eb55d5fb..b0ad94a2c1a9 100644 --- a/assets/src/edit-story/components/inspector/document/publish/index.js +++ b/assets/src/edit-story/components/inspector/document/publish/index.js @@ -96,12 +96,11 @@ function PublishPanel() { state: { meta: { isSaving }, story: { author, date, featuredMediaUrl, publisherLogoUrl }, - capabilities, }, actions: { updateStory }, } = useStory(); - const { timeFormat } = useConfig(); + const { timeFormat, capabilities } = useConfig(); const [showDatePicker, setShowDatePicker] = useState(false); const dateTimeNode = useRef(); diff --git a/assets/src/edit-story/components/inspector/document/status.js b/assets/src/edit-story/components/inspector/document/status.js index 3189a38b54d7..a4e6b447f713 100644 --- a/assets/src/edit-story/components/inspector/document/status.js +++ b/assets/src/edit-story/components/inspector/document/status.js @@ -32,6 +32,7 @@ import { Row, TextInput, HelperText, Button, RadioGroup } from '../../form'; import { useStory } from '../../../app/story'; import useInspector from '../useInspector'; import { SimplePanel } from '../../panels/panel'; +import { useConfig } from '../../../app/config'; const BoxedTextInput = styled(TextInput)` padding: 6px 6px; @@ -51,11 +52,12 @@ function StatusPanel() { const { state: { story: { status, password }, - capabilities, }, actions: { updateStory, deleteStory }, } = useStory(); + const { capabilities } = useConfig(); + useEffect(() => { loadStatuses(); loadUsers(); diff --git a/assets/src/edit-story/components/inspector/document/test/publish.js b/assets/src/edit-story/components/inspector/document/test/publish.js index fa053f778c83..f8e95f92236d 100644 --- a/assets/src/edit-story/components/inspector/document/test/publish.js +++ b/assets/src/edit-story/components/inspector/document/test/publish.js @@ -22,6 +22,7 @@ import { fireEvent } from '@testing-library/react'; /** * Internal dependencies */ +import ConfigContext from '../../../../app/config/context'; import StoryContext from '../../../../app/story/context'; import InspectorContext from '../../../inspector/context'; import PublishPanel from '../publish'; @@ -42,21 +43,25 @@ function setupPanel( featuredMediaUrl: '', publisherLogoUrl: '', }, - capabilities, }, actions: { updateStory }, }; + + const config = { capabilities }; const inspectorContextValue = { state: { users: [{ value: 'foo' }, { value: 'bar' }], }, }; + const { getByText, getByRole, queryByText } = renderWithTheme( - - - - - + + + + + + + ); return { getByText, diff --git a/assets/src/edit-story/components/inspector/document/test/status.js b/assets/src/edit-story/components/inspector/document/test/status.js index 0f1ab37052ae..9b54558edc2c 100644 --- a/assets/src/edit-story/components/inspector/document/test/status.js +++ b/assets/src/edit-story/components/inspector/document/test/status.js @@ -23,6 +23,7 @@ import { fireEvent } from '@testing-library/react'; */ import StoryContext from '../../../../app/story/context'; import InspectorContext from '../../../inspector/context'; +import ConfigContext from '../../../../app/config/context'; import StatusPanel from '../status'; import { renderWithTheme } from '../../../../testUtils'; @@ -36,10 +37,10 @@ function setupPanel( const loadStatuses = jest.fn(); const loadUsers = jest.fn(); + const config = { timeFormat: 'g:i a', capabilities }; const storyContextValue = { state: { story: { status: 'draft', password: '' }, - capabilities, }, actions: { updateStory, deleteStory }, }; @@ -62,11 +63,13 @@ function setupPanel( state: { statuses }, }; const { getByText, queryByText } = renderWithTheme( - - - - - + + + + + + + ); return { getByText, diff --git a/assets/src/edit-story/components/library/panes/media/mediaPane.js b/assets/src/edit-story/components/library/panes/media/mediaPane.js index e1863947c1bc..9f8881542ea6 100644 --- a/assets/src/edit-story/components/library/panes/media/mediaPane.js +++ b/assets/src/edit-story/components/library/panes/media/mediaPane.js @@ -17,6 +17,7 @@ /** * External dependencies */ +import { rgba } from 'polished'; import { useCallback, useRef } from 'react'; import styled from 'styled-components'; @@ -104,6 +105,19 @@ const Inner = styled.div` / 1fr; `; +const Loading = styled.div` + grid-column: 1 / span 2; + margin-bottom: 16px; + text-align: center; + padding: 8px 80px; + background-color: ${({ theme }) => rgba(theme.colors.bg.v0, 0.4)}; + border-radius: 100px; + margin-top: auto; + font-size: ${({ theme }) => theme.fonts.label.size}; + line-height: ${({ theme }) => theme.fonts.label.lineHeight}; + font-weight: 500; +`; + const FILTERS = [ { filter: '', name: __('All', 'web-stories') }, { filter: 'image', name: __('Images', 'web-stories') }, @@ -296,7 +310,11 @@ function MediaPane(props) { /> ))} - {hasMore &&
{'Loading...'}
} + {hasMore && ( + + {__('Loading…', 'web-stories')} + + )} )} diff --git a/assets/src/edit-story/components/library/panes/text/textPane.js b/assets/src/edit-story/components/library/panes/text/textPane.js index b0df064a8873..5c23bfb802e1 100644 --- a/assets/src/edit-story/components/library/panes/text/textPane.js +++ b/assets/src/edit-story/components/library/panes/text/textPane.js @@ -42,9 +42,11 @@ const PRESETS = [ { id: 'heading', title: __('Heading', 'web-stories'), - content: __('Heading', 'web-stories'), + content: `${__( + 'Heading', + 'web-stories' + )}`, fontSize: dataFontEm(2), - fontWeight: 700, font: { family: 'Open Sans', service: 'fonts.google.com', @@ -53,9 +55,11 @@ const PRESETS = [ { id: 'subheading', title: __('Subheading', 'web-stories'), - content: __('Subheading', 'web-stories'), + content: `${__( + 'Subheading', + 'web-stories' + )}`, fontSize: dataFontEm(1.5), - fontWeight: 600, font: { family: 'Open Sans', service: 'fonts.google.com', @@ -69,7 +73,6 @@ const PRESETS = [ 'web-stories' ), fontSize: dataFontEm(1.1), - fontWeight: 400, font: { family: 'Roboto', service: 'fonts.google.com', diff --git a/assets/src/edit-story/components/library/test/_utils/index.js b/assets/src/edit-story/components/library/test/_utils/index.js index 36d540c62573..f4847da68991 100644 --- a/assets/src/edit-story/components/library/test/_utils/index.js +++ b/assets/src/edit-story/components/library/test/_utils/index.js @@ -37,6 +37,9 @@ export async function arrange({ mediaResponse = [] }) { video: ['video/mp4'], }, allowedFileTypes: ['png', 'jpeg', 'jpg', 'gif', 'mp4'], + capabilities: { + hasUploadMediaAction: true, + }, }; const getMediaPromise = Promise.resolve({ data: mediaResponse, diff --git a/assets/src/edit-story/components/library/text/fontPreview.js b/assets/src/edit-story/components/library/text/fontPreview.js index 2053aef9d761..6e96b08eea47 100644 --- a/assets/src/edit-story/components/library/text/fontPreview.js +++ b/assets/src/edit-story/components/library/text/fontPreview.js @@ -26,10 +26,10 @@ import { useEffect } from 'react'; * Internal dependencies */ import { useFont } from '../../../app'; -import { DEFAULT_EDITOR_PAGE_HEIGHT, PAGE_HEIGHT } from '../../../constants'; +import { ALLOWED_EDITOR_PAGE_WIDTHS, PAGE_WIDTH } from '../../../constants'; import { FontPropType } from '../../../types'; -const PREVIEW_EM_SCALE = DEFAULT_EDITOR_PAGE_HEIGHT / PAGE_HEIGHT; +const PREVIEW_EM_SCALE = ALLOWED_EDITOR_PAGE_WIDTHS[0] / PAGE_WIDTH; const Preview = styled.button` position: relative; diff --git a/assets/src/edit-story/components/mediaPicker/test/useMediaPicker.js b/assets/src/edit-story/components/mediaPicker/test/useMediaPicker.js new file mode 100644 index 000000000000..bb27a9156972 --- /dev/null +++ b/assets/src/edit-story/components/mediaPicker/test/useMediaPicker.js @@ -0,0 +1,70 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * External dependencies + */ +import { renderHook } from '@testing-library/react-hooks'; + +/** + * Internal dependencies + */ +import ConfigContext from '../../../app/config/context'; +import useMediaPicker from '../useMediaPicker'; +import SnackbarContext from '../../../app/snackbar/context'; + +jest.mock('../../../app/snackbar', () => ({ + useSnackbar: () => { + return { + showSnackbar: jest.fn(), + }; + }, +})); + +function setup(args) { + const configValue = { + capabilities: { + hasUploadMediaAction: true, + }, + ...args, + }; + const wrapper = (params) => ( + + {params.children} + + ); + const onSelect = jest.fn(); + const onClose = jest.fn(); + const { result } = renderHook(() => useMediaPicker({ onSelect, onClose }), { + wrapper, + }); + return { + openMediaPicker: result.current, + }; +} + +describe('useMediaPicker', () => { + it('user unable to upload', () => { + const { openMediaPicker } = setup({ + capabilities: { + hasUploadMediaAction: false, + }, + }); + const evt = { + preventDefault: jest.fn(), + }; + expect(openMediaPicker(evt)).toBe(false); + }); +}); diff --git a/assets/src/edit-story/components/mediaPicker/useMediaPicker.js b/assets/src/edit-story/components/mediaPicker/useMediaPicker.js index b26612d2daeb..c772d409efbd 100644 --- a/assets/src/edit-story/components/mediaPicker/useMediaPicker.js +++ b/assets/src/edit-story/components/mediaPicker/useMediaPicker.js @@ -28,6 +28,8 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import useMedia from '../../app/media/useMedia'; +import { useConfig } from '../../app/config'; +import { useSnackbar } from '../../app/snackbar'; export default function useMediaPicker({ title = __('Upload to Story', 'web-stories'), @@ -40,6 +42,10 @@ export default function useMediaPicker({ const { actions: { uploadVideoPoster }, } = useMedia(); + const { + capabilities: { hasUploadMediaAction }, + } = useConfig(); + const { showSnackbar } = useSnackbar(); useEffect(() => { // Work around that forces default tab as upload tab. wp.media.controller.Library.prototype.defaults.contentUserSetting = false; @@ -57,6 +63,17 @@ export default function useMediaPicker({ }, [uploadVideoPoster]); const openMediaPicker = (evt) => { + // If a user does not have the rights to upload to the media library, do not show the media picker. + if (!hasUploadMediaAction) { + const message = __( + 'Sorry, you are unable to upload files.', + 'web-stories' + ); + showSnackbar({ message }); + evt.preventDefault(); + return false; + } + // Create the media frame. const fileFrame = wp.media({ title, @@ -83,6 +100,9 @@ export default function useMediaPicker({ fileFrame.open(); evt.preventDefault(); + + // Might be useful to return the media frame here. + return fileFrame; }; return openMediaPicker; diff --git a/assets/src/edit-story/components/panels/alignment/alignment.js b/assets/src/edit-story/components/panels/alignment/alignment.js index a7c68afde704..a3ae6cfe886e 100644 --- a/assets/src/edit-story/components/panels/alignment/alignment.js +++ b/assets/src/edit-story/components/panels/alignment/alignment.js @@ -45,10 +45,7 @@ import getCommonValue from '../utils/getCommonValue'; import getBoundRect, { calcRotatedObjectPositionAndSize, } from '../utils/getBoundRect'; -import { - DEFAULT_EDITOR_PAGE_WIDTH, - DEFAULT_EDITOR_PAGE_HEIGHT, -} from '../../../constants'; +import { PAGE_WIDTH, PAGE_HEIGHT } from '../../../constants'; import useAlignment from './useAlignment'; const ElementRow = styled.div` @@ -109,10 +106,10 @@ const alignmentButtonIds = [ const PAGE_RECT = { startX: 0, startY: 0, - endX: DEFAULT_EDITOR_PAGE_WIDTH, - endY: DEFAULT_EDITOR_PAGE_HEIGHT, - width: DEFAULT_EDITOR_PAGE_WIDTH, - height: DEFAULT_EDITOR_PAGE_HEIGHT, + endX: PAGE_WIDTH, + endY: PAGE_HEIGHT, + width: PAGE_WIDTH, + height: PAGE_HEIGHT, }; function ElementAlignmentPanel({ selectedElements, pushUpdate }) { diff --git a/assets/src/edit-story/components/panels/backgroundDisplay/dialogContent.js b/assets/src/edit-story/components/panels/backgroundDisplay/dialogContent.js deleted file mode 100644 index 3ceae32f59e3..000000000000 --- a/assets/src/edit-story/components/panels/backgroundDisplay/dialogContent.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import styled from 'styled-components'; -import { rgba } from 'polished'; - -/** - * WordPress dependencies - */ -import { __, sprintf } from '@wordpress/i18n'; -import { createInterpolateElement } from '@wordpress/element'; - -/** - * Internal dependencies - */ -import { useConfig } from '../../../app/config'; -import { PAGE_WIDTH, PAGE_HEIGHT } from '../../../constants'; - -const DialogSection = styled.div` - padding: 0px 16px; - min-width: 260px; - - h3 { - margin: 0px; - font-family: ${({ theme }) => theme.fonts.body1.family}; - font-size: ${({ theme }) => theme.fonts.body1.size}; - line-height: ${({ theme }) => theme.fonts.body1.lineHeight}; - letter-spacing: ${({ theme }) => theme.fonts.body1.letterSpacing}; - font-weight: bold; - color: ${({ theme }) => rgba(theme.colors.fg.v0, 0.68)}; - } - - b { - font-weight: bold; - } - - p { - font-family: ${({ theme }) => theme.fonts.body1.family}; - font-size: ${({ theme }) => theme.fonts.body1.size}; - line-height: ${({ theme }) => theme.fonts.body1.lineHeight}; - letter-spacing: ${({ theme }) => theme.fonts.body1.letterSpacing}; - } - - mark { - background-color: ${({ theme }) => theme.colors.textHighlight}; - } -`; - -const DialogSeparator = styled.div` - margin-right: 42px; -`; - -const Row = styled.div` - display: flex; - flex-direction: row; - align-items: center; - margin-bottom: 16px; - justify-content: space-between; - flex: 1; -`; - -function BackgroundDisplayDialogContent() { - const { pluginDir } = useConfig(); - - return ( - <> - - {sprintf( - /* translators: %s: page width x height */ - __( - 'Your story is created at one aspect ratio (%s). When published, users will view your stories using phones with aspect ratios that may differ widely.', - 'web-stories' - ), - `${PAGE_WIDTH} x ${PAGE_HEIGHT}` - )} - - - - {__('Fit - -

{__('Fit to device', 'web-stories')}

-

- {__( - 'All background images in your stories will be scaled to fill the screen for all mobile devices.', - 'web-stories' - )} -

-

- {__( - 'Use this option if resizing the background image is not an issue.', - 'web-stories' - )} -

-
- - {__('Do - -

{__('Do not fit', 'web-stories')}

-

- {createInterpolateElement( - __( - 'Your story will be centered vertically on the device. The page’s background color will cover any additional space. For better visual results, set the page background color to match the color of the background image.', - 'web-stories' - ), - { - b: , - mark: , - } - )} -

-

- {__( - 'Use this option if you need elements to be precisely aligned to the background.', - 'web-stories' - )} -

-
-
- - ); -} - -export default BackgroundDisplayDialogContent; diff --git a/assets/src/edit-story/components/panels/backgroundDisplay/index.js b/assets/src/edit-story/components/panels/backgroundDisplay/index.js deleted file mode 100644 index f97a5a581f5d..000000000000 --- a/assets/src/edit-story/components/panels/backgroundDisplay/index.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import PropTypes from 'prop-types'; -import { useState, useCallback } from 'react'; -import { rgba } from 'polished'; - -/** - * WordPress dependencies - */ -import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import { Switch, Row } from '../../form'; -import { SimplePanel } from '../panel'; -import { Note } from '../shared'; -import Dialog from '../../dialog'; -import { Plain } from '../../button'; -import theme from '../../../theme'; -import BackgroundDisplayDialogContent from './dialogContent'; - -function BackgroundDisplayPanel({ selectedElements, pushUpdate }) { - const { isFullbleedBackground } = selectedElements[0]; - - // Informational dialog - const [infoDialogOpen, setInfoDialogOpen] = useState(false); - const openDialog = useCallback(() => setInfoDialogOpen(true), []); - const closeDialog = useCallback(() => setInfoDialogOpen(false), []); - - return ( - - - openDialog()}> - {__( - 'How I want my story displayed on devices with a different aspect ratio?', - 'web-stories' - )} - - - pushUpdate({ isFullbleedBackground: value }, true)} - /> - closeDialog()}> - {__('Ok, got it', 'web-stories')} - - } - style={{ - overlay: { - background: rgba(theme.colors.bg.v11, 0.6), - }, - }} - > - - - - ); -} - -BackgroundDisplayPanel.propTypes = { - selectedElements: PropTypes.array.isRequired, - pushUpdate: PropTypes.func.isRequired, -}; - -export default BackgroundDisplayPanel; diff --git a/assets/src/edit-story/components/panels/index.js b/assets/src/edit-story/components/panels/index.js index c13f2569b387..38732e5fdd9b 100644 --- a/assets/src/edit-story/components/panels/index.js +++ b/assets/src/edit-story/components/panels/index.js @@ -27,7 +27,6 @@ import ShapeStylePanel from './shapeStyle'; import SizePositionPanel from './sizePosition'; import TextStylePanel from './textStyle'; import VideoAccessibilityPanel from './videoAccessibility'; -import BackgroundDisplayPanel from './backgroundDisplay'; import NoSelectionPanel from './noSelection'; import ElementAlignmentPanel from './alignment'; import VideoOptionsPanel from './videoOptions'; @@ -35,7 +34,6 @@ import StylePresetPanel from './stylePreset'; export { default as LayerPanel } from './layer'; const BACKGROUND_SIZE_POSITION = 'backgroundSizePosition'; -const BACKGROUND_DISPLAY = 'backgroundDisplay'; const BACKGROUND_OVERLAY = 'backgroundOverlay'; const STYLE_PRESETS = 'stylePresets'; const IMAGE_ACCESSIBILITY = 'imageAccessibility'; @@ -54,7 +52,6 @@ export const PanelTypes = { STYLE_PRESETS, // Display presets as the first panel for elements. ELEMENT_ALIGNMENT, BACKGROUND_SIZE_POSITION, - BACKGROUND_DISPLAY, BACKGROUND_OVERLAY, SIZE_POSITION, SHAPE_STYLE, @@ -84,7 +81,6 @@ export function getPanels(elements) { if (isBackground) { const panels = [ { type: BACKGROUND_OVERLAY, Panel: BackgroundOverlayPanel }, - { type: BACKGROUND_DISPLAY, Panel: BackgroundDisplayPanel }, ]; // If the selected element's type is video / image , display accessibility panel, too. if ('shape' === elements[0].type) { @@ -128,9 +124,6 @@ export function getPanels(elements) { return { type, Panel: StylePresetPanel }; case LAYER_STYLE: return { type, Panel: LayerStylePanel }; - case BACKGROUND_DISPLAY: - // Only display when isBackground. - return null; case BACKGROUND_OVERLAY: // Only display when isBackground. return null; diff --git a/assets/src/edit-story/components/panels/stylePreset/panel.js b/assets/src/edit-story/components/panels/stylePreset/panel.js index 844566df9fc5..fd82881b84fd 100644 --- a/assets/src/edit-story/components/panels/stylePreset/panel.js +++ b/assets/src/edit-story/components/panels/stylePreset/panel.js @@ -17,14 +17,20 @@ /** * External dependencies */ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useRef, useEffect, useState } from 'react'; /** * Internal dependencies */ import { useStory } from '../../../app/story'; import stripHTML from '../../../utils/stripHTML'; -import { Panel } from './../panel'; +import objectWithout from '../../../utils/objectWithout'; +import { Panel } from '../panel'; +import useRichTextFormatting from '../textStyle/useRichTextFormatting'; +import { + COLOR_PRESETS_PER_ROW, + STYLE_PRESETS_PER_ROW, +} from '../../../constants'; import { getShapePresets, getTextPresets } from './utils'; import PresetsHeader from './header'; import Presets from './presets'; @@ -119,16 +125,38 @@ function StylePresetPanel() { ] ); + const extraPropsToAdd = useRef(null); + const miniPushUpdate = useCallback( + (updater) => { + updateElementsById({ + elementIds: selectedElementIds, + properties: (oldProps) => ({ + ...updater(oldProps), + ...extraPropsToAdd.current, + }), + }); + extraPropsToAdd.current = null; + }, + [selectedElementIds, updateElementsById] + ); + + const { + handlers: { handleSetColor }, + } = useRichTextFormatting(selectedElements, miniPushUpdate); + const handleApplyPreset = useCallback( (preset) => { if (isText) { // @todo Determine this in a better way. // Only style presets have background text mode set. const isStylePreset = preset.backgroundTextMode !== undefined; - updateElementsById({ - elementIds: selectedElementIds, - properties: isStylePreset ? { ...preset } : { color: preset }, - }); + if (isStylePreset) { + extraPropsToAdd.current = objectWithout(preset, ['color']); + handleSetColor(preset.color); + } else { + extraPropsToAdd.current = null; + handleSetColor(preset); + } } else { updateElementsById({ elementIds: selectedElementIds, @@ -136,7 +164,7 @@ function StylePresetPanel() { }); } }, - [isText, selectedElementIds, updateElementsById] + [isText, handleSetColor, selectedElementIds, updateElementsById] ); const colorPresets = isText ? textColors : fillColors; @@ -163,11 +191,24 @@ function StylePresetPanel() { } }; - // @Todo confirm initial height. + const rowHeight = 35; + + // Assume at least 2 rows if there's at least 1 preset: + // One for presets, one for the label. + const colorRows = + colorPresets.length > 0 + ? Math.max(2, colorPresets.length / COLOR_PRESETS_PER_ROW) + : 0; + const styleRows = + textStyles.length > 0 + ? Math.max(2, textStyles.length / STYLE_PRESETS_PER_ROW) + : 0; + const initialHeight = (colorRows + styleRows) * rowHeight; + return ( diff --git a/assets/src/edit-story/components/panels/stylePreset/test/panel.js b/assets/src/edit-story/components/panels/stylePreset/test/panel.js index 8d1f1ee30e3a..3f05c7c7d7fa 100644 --- a/assets/src/edit-story/components/panels/stylePreset/test/panel.js +++ b/assets/src/edit-story/components/panels/stylePreset/test/panel.js @@ -28,6 +28,7 @@ import { BACKGROUND_TEXT_MODE } from '../../../../constants'; import { getShapePresets, getTextPresets } from '../utils'; import { renderWithTheme } from '../../../../testUtils'; import { TEXT_ELEMENT_DEFAULT_FONT } from '../../../../app/font/defaultFonts'; + jest.mock('../utils'); function setupPanel(extraStylePresets, extraStateProps) { @@ -91,7 +92,6 @@ describe('Panels/StylePreset', () => { }, }; const STYLE_PRESET = { - color: TEST_COLOR_2, backgroundTextMode: BACKGROUND_TEXT_MODE.FILL, backgroundColor: TEST_COLOR, }; @@ -149,13 +149,13 @@ describe('Panels/StylePreset', () => { expect(newEditButton).toBeDefined(); }); - it('should add a text color preset', () => { + it('should add a text color preset if other text styles are default or missing', () => { const extraStateProps = { selectedElements: [ { id: '1', type: 'text', - color: [TEST_COLOR_2], + content: 'Content', backgroundTextMode: BACKGROUND_TEXT_MODE.NONE, font: TEXT_ELEMENT_DEFAULT_FONT, }, @@ -193,6 +193,7 @@ describe('Panels/StylePreset', () => { { id: '1', type: 'text', + content: 'Content', ...STYLE_PRESET, }, ], @@ -204,7 +205,7 @@ describe('Panels/StylePreset', () => { getTextPresets.mockImplementation(() => { return { - textStyles: [STYLE_PRESET], + textStyles: [{ color: TEST_COLOR_2, ...STYLE_PRESET }], }; }); @@ -217,7 +218,7 @@ describe('Panels/StylePreset', () => { stylePresets: { textColors: [], fillColors: [], - textStyles: [STYLE_PRESET], + textStyles: [{ color: TEST_COLOR_2, ...STYLE_PRESET }], }, }, }); @@ -365,10 +366,17 @@ describe('Panels/StylePreset', () => { expect(updateElementsById).toHaveBeenCalledTimes(1); expect(updateElementsById).toHaveBeenCalledWith({ elementIds: ['1'], - properties: { - color: TEST_COLOR, - }, + properties: expect.any(Function), }); + const updaterFunction = updateElementsById.mock.calls[0][0].properties; + const partiallyBlueContent = { + content: 'Hello World', + }; + const updatedContent = updaterFunction(partiallyBlueContent); + const expectedContent = { + content: 'Hello World', + }; + expect(updatedContent).toStrictEqual(expectedContent); }); }); }); diff --git a/assets/src/edit-story/components/panels/stylePreset/test/utils.js b/assets/src/edit-story/components/panels/stylePreset/test/utils.js index a42d9a8097cb..ac9c3b8a9162 100644 --- a/assets/src/edit-story/components/panels/stylePreset/test/utils.js +++ b/assets/src/edit-story/components/panels/stylePreset/test/utils.js @@ -24,6 +24,7 @@ import { getTextPresets, } from '../utils'; import { BACKGROUND_TEXT_MODE } from '../../../../constants'; +import objectWithout from '../../../../utils/objectWithout'; import { TEXT_ELEMENT_DEFAULT_FONT } from '../../../../app/font/defaultFonts'; describe('Panels/StylePreset/utils', () => { @@ -164,12 +165,13 @@ describe('Panels/StylePreset/utils', () => { vertical: 0, horizontal: 0, }, - color: TEST_COLOR, + content: 'Content', }, { type: 'text', x: 30, - ...stylePreset, + content: 'Content', + ...objectWithout(stylePreset, ['color']), }, ]; const stylePresets = { @@ -185,6 +187,64 @@ describe('Panels/StylePreset/utils', () => { expect(presets).toStrictEqual(expected); }); + it('should ignore text color presets for multi-color text fields', () => { + const elements = [ + { + type: 'text', + backgroundTextMode: BACKGROUND_TEXT_MODE.NONE, + font: TEXT_ELEMENT_DEFAULT_FONT, + content: + 'OK', + }, + ]; + const stylePresets = { + textStyles: [], + textColors: [], + fillColors: [], + }; + const expected = { + textColors: [], + textStyles: [], + }; + const presets = getTextPresets(elements, stylePresets); + expect(presets).toStrictEqual(expected); + }); + + it('should use black color when adding text style preset for multi-color text fields', () => { + const stylePreset = { + ...STYLE_PRESET, + font: { + family: 'Foo', + fallbacks: ['Bar'], + }, + }; + const elements = [ + { + type: 'text', + x: 30, + content: + 'OK', + ...objectWithout(stylePreset, ['color']), + }, + ]; + const stylePresets = { + textStyles: [], + textColors: [], + fillColors: [], + }; + const expected = { + textColors: [], + textStyles: [ + { + ...stylePreset, + color: { color: { r: 0, g: 0, b: 0 } }, + }, + ], + }; + const presets = getTextPresets(elements, stylePresets); + expect(presets).toStrictEqual(expected); + }); + it('should not consider existing presets as new', () => { const stylePreset = { ...STYLE_PRESET, @@ -199,7 +259,7 @@ describe('Panels/StylePreset/utils', () => { backgroundTextMode: BACKGROUND_TEXT_MODE.NONE, font: TEXT_ELEMENT_DEFAULT_FONT, foo: 'bar', - color: TEST_COLOR, + content: 'Content', padding: { vertical: 0, horizontal: 0, @@ -208,7 +268,8 @@ describe('Panels/StylePreset/utils', () => { { type: 'text', x: 30, - ...stylePreset, + content: 'Content', + ...objectWithout(stylePreset, ['color']), }, ]; const stylePresets = { diff --git a/assets/src/edit-story/components/panels/stylePreset/utils.js b/assets/src/edit-story/components/panels/stylePreset/utils.js index f33266f77c69..bab4d2a27ef5 100644 --- a/assets/src/edit-story/components/panels/stylePreset/utils.js +++ b/assets/src/edit-story/components/panels/stylePreset/utils.js @@ -17,11 +17,15 @@ /** * Internal dependencies */ +import isPatternEqual from '../../../utils/isPatternEqual'; +import convertToCSS from '../../../utils/convertToCSS'; import generatePatternStyles from '../../../utils/generatePatternStyles'; import { generateFontFamily } from '../../../elements/text/util'; import { BACKGROUND_TEXT_MODE } from '../../../constants'; -import convertToCSS from '../../../utils/convertToCSS'; +import createSolid from '../../../utils/createSolid'; import objectPick from '../../../utils/objectPick'; +import { MULTIPLE_VALUE } from '../../form'; +import { getHTMLInfo } from '../../richText/htmlManipulation'; import { TEXT_ELEMENT_DEFAULT_FONT } from '../../../app/font/defaultFonts'; export function findMatchingColor(color, stylePresets, isText) { @@ -29,11 +33,9 @@ export function findMatchingColor(color, stylePresets, isText) { ? stylePresets.textColors : stylePresets.fillColors; const patternType = isText ? 'color' : 'background'; - const toAdd = generatePatternStyles(color, patternType); - return colorsToMatch.find((value) => { - const existing = generatePatternStyles(value, patternType); - return Object.keys(toAdd).every((key) => existing[key] === toAdd[key]); - }); + return colorsToMatch.find((value) => + isPatternEqual(value, color, patternType) + ); } export function findMatchingStylePreset(preset, stylePresets) { @@ -74,17 +76,25 @@ export function getTextPresets(elements, stylePresets) { return { textColors: elements .filter((text) => !hasStylePreset(text)) - .map(({ color }) => color) + .map(({ content }) => getHTMLInfo(content).color) + .filter((color) => color !== MULTIPLE_VALUE) .filter((color) => !findMatchingColor(color, stylePresets, true)), textStyles: elements .filter((text) => hasStylePreset(text)) .map((text) => { - return objectPick(text, [ - 'color', - 'backgroundColor', - 'backgroundTextMode', - 'font', - ]); + const extractedColor = getHTMLInfo(text.content).color; + const color = + extractedColor === MULTIPLE_VALUE + ? createSolid(0, 0, 0) + : extractedColor; + return { + color, + ...objectPick(text, [ + 'backgroundColor', + 'backgroundTextMode', + 'font', + ]), + }; }) .filter((preset) => !findMatchingStylePreset(preset, stylePresets)), }; diff --git a/assets/src/edit-story/components/panels/test/backgroundDisplay.js b/assets/src/edit-story/components/panels/test/backgroundDisplay.js deleted file mode 100644 index e339b34e57c4..000000000000 --- a/assets/src/edit-story/components/panels/test/backgroundDisplay.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 Google LLC - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * External dependencies - */ -import { fireEvent } from '@testing-library/react'; - -/** - * Internal dependencies - */ -import BackgroundDisplayPanel from '../backgroundDisplay'; -import { renderPanel } from './_utils'; - -function setupPanel(isFullbleedBackground = undefined) { - const selectedElements = [{ isFullbleedBackground }]; - const { getByText, pushUpdate } = renderPanel( - BackgroundDisplayPanel, - selectedElements - ); - - const onLabelEl = getByText('Fit to device'); - const offLabelEl = getByText('Do not format'); - - return { - enable: () => fireEvent.click(onLabelEl), - disable: () => fireEvent.click(offLabelEl), - pushUpdate, - }; -} - -describe('BackgroundDisplayPanel', () => { - it('should disable fullbleed', () => { - const { disable, pushUpdate } = setupPanel(true); - - disable(); - - expect(pushUpdate).toHaveBeenCalledWith( - { - isFullbleedBackground: false, - }, - true - ); - }); - - it('should enable fullbleed', () => { - const { enable, pushUpdate } = setupPanel(false); - - enable(); - - expect(pushUpdate).toHaveBeenCalledWith( - { - isFullbleedBackground: true, - }, - true - ); - }); -}); diff --git a/assets/src/edit-story/components/panels/test/textStyle.js b/assets/src/edit-story/components/panels/test/textStyle.js index 7dde43aaaa39..523f60c4f1e5 100644 --- a/assets/src/edit-story/components/panels/test/textStyle.js +++ b/assets/src/edit-story/components/panels/test/textStyle.js @@ -25,6 +25,7 @@ import { act, fireEvent } from '@testing-library/react'; */ import TextStyle from '../textStyle'; import FontContext from '../../../app/font/context'; +import RichTextContext from '../../richText/context'; import { calculateTextHeight } from '../../../utils/textMeasurements'; import calcRotatedResizeOffset from '../../../utils/calcRotatedResizeOffset'; import DropDown from '../../form/dropDown'; @@ -88,7 +89,11 @@ function Wrapper({ children }) { }, }} > - {children} + + {children} + ); } @@ -112,7 +117,6 @@ describe('Panels/TextStyle', () => { id: '1', textAlign: 'normal', fontSize: 30, - fontWeight: 400, font: { family: 'ABeeZee', }, @@ -431,7 +435,6 @@ describe('Panels/TextStyle', () => { ], fallbacks: ['fallback1'], }, - fontWeight: 400, }, true ); @@ -440,7 +443,14 @@ describe('Panels/TextStyle', () => { it('should select font weight', async () => { const { pushUpdate } = renderTextStyle([textElement]); await act(() => controls['font.weight'].onChange('300')); - expect(pushUpdate).toHaveBeenCalledWith({ fontWeight: 300 }, true); + const updatingFunction = pushUpdate.mock.calls[0][0]; + const resultOfUpdating = updatingFunction({ content: 'Hello world' }); + expect(resultOfUpdating).toStrictEqual( + { + content: 'Hello world', + }, + true + ); }); it('should select font size', async () => { @@ -477,27 +487,43 @@ describe('Panels/TextStyle', () => { const { getByTestId, pushUpdate } = renderTextStyle([textElement]); const input = getByTestId('text.letterSpacing'); fireEvent.change(input, { target: { value: '150' } }); - expect(pushUpdate).toHaveBeenCalledWith({ letterSpacing: 1.5 }); + const updatingFunction = pushUpdate.mock.calls[0][0]; + const resultOfUpdating = updatingFunction({ content: 'Hello world' }); + expect(resultOfUpdating).toStrictEqual( + { + content: 'Hello world', + }, + true + ); }); it('should set letterSpacing to empty', () => { const { getByTestId, pushUpdate } = renderTextStyle([textElement]); const input = getByTestId('text.letterSpacing'); fireEvent.change(input, { target: { value: '' } }); - expect(pushUpdate).toHaveBeenCalledWith({ letterSpacing: '' }); + const updatingFunction = pushUpdate.mock.calls[0][0]; + const resultOfUpdating = updatingFunction({ + content: 'Hello world', + }); + expect(resultOfUpdating).toStrictEqual( + { + content: 'Hello world', + }, + true + ); }); }); describe('ColorControls', () => { - it('should render no color', () => { + it('should render default black color', () => { renderTextStyle([textElement]); - expect(controls['text.color'].value).toBeNull(); + expect(controls['text.color'].value).toStrictEqual(createSolid(0, 0, 0)); }); it('should render a color', () => { const textWithColor = { ...textElement, - color: createSolid(255, 0, 0), + content: 'Hello world', }; renderTextStyle([textWithColor]); expect(controls['text.color'].value).toStrictEqual( @@ -508,20 +534,26 @@ describe('Panels/TextStyle', () => { it('should set color', () => { const { pushUpdate } = renderTextStyle([textElement]); act(() => controls['text.color'].onChange(createSolid(0, 255, 0))); - expect(pushUpdate).toHaveBeenCalledWith( - { color: createSolid(0, 255, 0) }, + const updatingFunction = pushUpdate.mock.calls[0][0]; + const resultOfUpdating = updatingFunction({ + content: 'Hello world', + }); + expect(resultOfUpdating).toStrictEqual( + { + content: 'Hello world', + }, true ); }); - it('should set color with multi selection, same values', () => { + it('should detect color with multi selection, same values', () => { const textWithColor1 = { ...textElement, - color: createSolid(0, 0, 255), + content: 'Hello world', }; const textWithColor2 = { ...textElement, - color: createSolid(0, 0, 255), + content: 'Hello world', }; renderTextStyle([textWithColor1, textWithColor2]); expect(controls['text.color'].value).toStrictEqual( @@ -532,11 +564,11 @@ describe('Panels/TextStyle', () => { it('should set color with multi selection, different values', () => { const textWithColor1 = { ...textElement, - color: createSolid(255, 0, 0), + content: 'Hello world', }; const textWithColor2 = { ...textElement, - color: createSolid(0, 255, 0), + content: 'Hello world', }; renderTextStyle([textWithColor1, textWithColor2]); expect(controls['text.color'].value).toStrictEqual(MULTIPLE_VALUE); diff --git a/assets/src/edit-story/components/panels/textStyle/color.js b/assets/src/edit-story/components/panels/textStyle/color.js index 1e9e1518906a..7d16ace3adbb 100644 --- a/assets/src/edit-story/components/panels/textStyle/color.js +++ b/assets/src/edit-story/components/panels/textStyle/color.js @@ -37,6 +37,7 @@ import { Color, Label, Row, ToggleButton } from '../../form'; import { useKeyDownEffect } from '../../keyboard'; import { useCommonColorValue, getCommonValue } from '../utils'; import getColorPickerActions from '../utils/getColorPickerActions'; +import useRichTextFormatting from './useRichTextFormatting'; const FillRow = styled(Row)` align-items: flex-start; @@ -79,7 +80,6 @@ const BUTTONS = [ ]; function ColorControls({ selectedElements, pushUpdate }) { - const color = useCommonColorValue(selectedElements, 'color'); const backgroundColor = useCommonColorValue( selectedElements, 'backgroundColor' @@ -90,6 +90,11 @@ function ColorControls({ selectedElements, pushUpdate }) { ); const fillRow = useRef(); + const { + textInfo: { color }, + handlers: { handleSetColor }, + } = useRichTextFormatting(selectedElements, pushUpdate); + useKeyDownEffect( fillRow, ['left', 'right'], @@ -113,14 +118,7 @@ function ColorControls({ selectedElements, pushUpdate }) { - pushUpdate( - { - color: value, - }, - true - ) - } + onChange={handleSetColor} colorPickerActions={getColorPickerActions} /> diff --git a/assets/src/edit-story/components/panels/textStyle/font.js b/assets/src/edit-story/components/panels/textStyle/font.js index 3426c92ff01e..cd514bc57e74 100644 --- a/assets/src/edit-story/components/panels/textStyle/font.js +++ b/assets/src/edit-story/components/panels/textStyle/font.js @@ -34,6 +34,7 @@ import { PAGE_HEIGHT } from '../../../constants'; import { useFont } from '../../../app/font'; import { getCommonValue } from '../utils'; import objectPick from '../../../utils/objectPick'; +import useRichTextFormatting from './useRichTextFormatting'; import getFontWeights from './getFontWeights'; const Space = styled.div` @@ -51,7 +52,11 @@ function FontControls({ selectedElements, pushUpdate }) { ({ font }) => font?.family ); const fontSize = getCommonValue(selectedElements, 'fontSize'); - const fontWeight = getCommonValue(selectedElements, 'fontWeight'); + + const { + textInfo: { fontWeight, isItalic }, + handlers: { handleSelectFontWeight }, + } = useRichTextFormatting(selectedElements, pushUpdate); const { state: { fonts }, @@ -83,26 +88,18 @@ function FontControls({ selectedElements, pushUpdate }) { 'variants', ]), }; - const { weights } = fontObj; - // Find the nearest font weight from the available font weight list - const newFontWeight = weights.reduce((a, b) => - Math.abs(parseInt(b) - fontWeight) < - Math.abs(parseInt(a) - fontWeight) - ? b - : a - ); + await maybeEnqueueFontStyle( - selectedElements.map((e) => ({ - ...e, + selectedElements.map(() => ({ font: newFont, - fontWeight: newFontWeight, + isItalic, + fontWeight, })) ); pushUpdate( { font: newFont, - fontWeight: parseInt(newFontWeight), }, true ); @@ -116,17 +113,18 @@ function FontControls({ selectedElements, pushUpdate }) { { - const newFontWeight = parseInt(value); await maybeEnqueueFontStyle( - selectedElements.map((e) => ({ - ...e, - fontWeight: newFontWeight, + selectedElements.map(({ font }) => ({ + font, + isItalic, + fontWeight: parseInt(value), })) ); - pushUpdate({ fontWeight: newFontWeight }, true); + handleSelectFontWeight(value); }} /> diff --git a/assets/src/edit-story/components/panels/textStyle/textStyle.js b/assets/src/edit-story/components/panels/textStyle/textStyle.js index f7d791965183..a048ef2e0821 100644 --- a/assets/src/edit-story/components/panels/textStyle/textStyle.js +++ b/assets/src/edit-story/components/panels/textStyle/textStyle.js @@ -41,6 +41,7 @@ import { ReactComponent as ItalicIcon } from '../../../icons/italic_icon.svg'; import { ReactComponent as UnderlineIcon } from '../../../icons/underline_icon.svg'; import { getCommonValue } from '../utils'; import { useFont } from '../../../app/font'; +import useRichTextFormatting from './useRichTextFormatting'; const BoxedNumeric = styled(Numeric)` padding: 6px 6px; @@ -66,11 +67,17 @@ function StylePanel({ selectedElements, pushUpdate }) { actions: { maybeEnqueueFontStyle }, } = useFont(); const textAlign = getCommonValue(selectedElements, 'textAlign'); - const letterSpacing = getCommonValue(selectedElements, 'letterSpacing'); const lineHeight = getCommonValue(selectedElements, 'lineHeight'); - const fontStyle = getCommonValue(selectedElements, 'fontStyle'); - const textDecoration = getCommonValue(selectedElements, 'textDecoration'); - const bold = getCommonValue(selectedElements, 'bold'); + + const { + textInfo: { isBold, isItalic, isUnderline, letterSpacing, fontWeight }, + handlers: { + handleClickBold, + handleClickItalic, + handleClickUnderline, + handleSetLetterSpacing, + }, + } = useRichTextFormatting(selectedElements, pushUpdate); return ( <> @@ -87,18 +94,10 @@ function StylePanel({ selectedElements, pushUpdate }) { } symbol="%" - onChange={(value) => - pushUpdate({ - letterSpacing: typeof value === 'number' ? value / 100 : value, - }) - } + onChange={handleSetLetterSpacing} /> @@ -132,35 +131,33 @@ function StylePanel({ selectedElements, pushUpdate }) { /> } - value={bold === true} + value={isBold} iconWidth={9} iconHeight={10} - onChange={(value) => pushUpdate({ bold: value }, true)} + onChange={handleClickBold} /> } - value={fontStyle === 'italic'} + value={isItalic} iconWidth={10} iconHeight={10} onChange={async (value) => { - const newFontStyle = value ? 'italic' : 'normal'; await maybeEnqueueFontStyle( - selectedElements.map((e) => ({ - ...e, - fontStyle: newFontStyle, + selectedElements.map(({ font }) => ({ + font, + isItalic: value, + fontWeight, })) ); - pushUpdate({ fontStyle: newFontStyle }, true); + handleClickItalic(value); }} /> } - value={textDecoration === 'underline'} + value={isUnderline} iconWidth={8} iconHeight={21} - onChange={(value) => - pushUpdate({ textDecoration: value ? 'underline' : 'none' }, true) - } + onChange={handleClickUnderline} /> diff --git a/assets/src/edit-story/components/panels/textStyle/useRichTextFormatting.js b/assets/src/edit-story/components/panels/textStyle/useRichTextFormatting.js new file mode 100644 index 000000000000..7e277f26e5d2 --- /dev/null +++ b/assets/src/edit-story/components/panels/textStyle/useRichTextFormatting.js @@ -0,0 +1,142 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useMemo, useCallback } from 'react'; + +/** + * Internal dependencies + */ +import isPatternEqual from '../../../utils/isPatternEqual'; +import useRichText from '../../richText/useRichText'; +import { + getHTMLFormatters, + getHTMLInfo, +} from '../../richText/htmlManipulation'; +import { MULTIPLE_VALUE } from '../../form'; + +/** + * Equality function for *primitives and color patterns* only. + * + * @param {any} a First value to compare + * @param {any} b Second value to compare + * @return {boolean} True if equal + */ +function isEqual(a, b) { + // patterns are truthy objects with either a type or a color attribute. + // Note: `null` is a falsy object, that would cause an error if first + // check is removed. + const isPattern = a && typeof a === 'object' && (a.type || a.color); + return !isPattern ? a === b : isPatternEqual(a, b); +} + +/** + * A function to gather the text info for multiple elements into a single + * one. + * + * The text info object contains a number of values that can be either + * primitives or a color object. + * + * If any two objects for the same key have different values, return + * `MULTIPLE_VALUE`. Uses `isEqual` to determine this equality.` + * + * @param {Object} reduced Currently reduced object from previous elements + * - will be empty for first object + * @param {Object} info Info about current object + * @return {Object} Combination of object as described. + */ +function reduceWithMultiple(reduced, info) { + return Object.fromEntries( + Object.keys(info).map((key) => { + const wasMultiple = reduced[key] === MULTIPLE_VALUE; + const hadValue = typeof reduced[key] !== 'undefined'; + const areDifferent = hadValue && !isEqual(reduced[key], info[key]); + if (wasMultiple || areDifferent) { + return [key, MULTIPLE_VALUE]; + } + return [key, info[key]]; + }) + ); +} + +function useRichTextFormatting(selectedElements, pushUpdate) { + const { + state: { hasCurrentEditor, selectionInfo }, + actions: { selectionActions }, + } = useRichText(); + + const textInfo = useMemo(() => { + if (hasCurrentEditor) { + return selectionInfo; + } + + // loop over all elements, find info for content and reduce to common value + // (setting MULTIPLE_VALUE appropriately) + return selectedElements + .map(({ content }) => content) + .map(getHTMLInfo) + .reduce(reduceWithMultiple, {}); + }, [hasCurrentEditor, selectionInfo, selectedElements]); + + const push = useCallback( + (updater, ...args) => + pushUpdate( + ({ content }) => ({ content: updater(content, ...args) }), + true + ), + [pushUpdate] + ); + + const handlers = useMemo(() => { + if (hasCurrentEditor) { + return { + // This particular function ignores the flag argument. + // Bold for inline selection has its own logic for + // determining proper resulting bold weight + handleClickBold: () => selectionActions.toggleBoldInSelection(), + // All these keep their arguments: + handleSelectFontWeight: selectionActions.setFontWeightInSelection, + handleClickItalic: selectionActions.toggleItalicInSelection, + handleClickUnderline: selectionActions.toggleUnderlineInSelection, + handleSetLetterSpacing: selectionActions.setLetterSpacingInSelection, + handleSetColor: selectionActions.setColorInSelection, + }; + } + + const htmlFormatters = getHTMLFormatters(); + + return { + handleClickBold: (flag) => push(htmlFormatters.toggleBold, flag), + handleSelectFontWeight: (weight) => + push(htmlFormatters.setFontWeight, weight), + handleClickItalic: (flag) => push(htmlFormatters.toggleItalic, flag), + handleClickUnderline: (flag) => + push(htmlFormatters.toggleUnderline, flag), + handleSetLetterSpacing: (letterSpacing) => + push(htmlFormatters.setLetterSpacing, letterSpacing), + handleSetColor: (color) => push(htmlFormatters.setColor, color), + }; + }, [hasCurrentEditor, selectionActions, push]); + + return { + textInfo, + handlers, + }; +} + +export default useRichTextFormatting; diff --git a/assets/src/edit-story/components/richText/context.js b/assets/src/edit-story/components/richText/context.js new file mode 100644 index 000000000000..7797a50aab8d --- /dev/null +++ b/assets/src/edit-story/components/richText/context.js @@ -0,0 +1,24 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { createContext } from 'react'; + +const RichTextContext = createContext({ state: {}, actions: {} }); + +export default RichTextContext; diff --git a/assets/src/edit-story/components/richText/customConstants.js b/assets/src/edit-story/components/richText/customConstants.js new file mode 100644 index 000000000000..996021c120fb --- /dev/null +++ b/assets/src/edit-story/components/richText/customConstants.js @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const NONE = 'NONE'; +export const ITALIC = 'CUSTOM-ITALIC'; +export const UNDERLINE = 'CUSTOM-UNDERLINE'; +export const WEIGHT = 'CUSTOM-WEIGHT'; +export const COLOR = 'CUSTOM-COLOR'; +export const LETTERSPACING = 'CUSTOM-LETTERSPACING'; diff --git a/assets/src/edit-story/components/richText/customExport.js b/assets/src/edit-story/components/richText/customExport.js new file mode 100644 index 000000000000..9671e28ec037 --- /dev/null +++ b/assets/src/edit-story/components/richText/customExport.js @@ -0,0 +1,56 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { stateToHTML } from 'draft-js-export-html'; + +/** + * Internal dependencies + */ +import formatters from './formatters'; + +function inlineStyleFn(styles) { + const inlineCSS = formatters.reduce( + (css, { stylesToCSS }) => ({ ...css, ...stylesToCSS(styles) }), + {} + ); + + if (Object.keys(inlineCSS).length === 0) { + return null; + } + + return { + element: 'span', + style: inlineCSS, + }; +} + +function exportHTML(editorState) { + if (!editorState) { + return null; + } + + const html = stateToHTML(editorState.getCurrentContent(), { + inlineStyleFn, + defaultBlockTag: null, + }); + + return html.replace(/
/g, '').replace(/ $/, ''); +} + +export default exportHTML; diff --git a/assets/src/edit-story/components/richText/customImport.js b/assets/src/edit-story/components/richText/customImport.js new file mode 100644 index 000000000000..87dc321c0b24 --- /dev/null +++ b/assets/src/edit-story/components/richText/customImport.js @@ -0,0 +1,52 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { stateFromHTML } from 'draft-js-import-html'; + +/** + * Internal dependencies + */ +import getValidHTML from '../../utils/getValidHTML'; +import formatters from './formatters'; + +function customInlineFn(element, { Style }) { + const styleStrings = formatters + .map(({ elementToStyle }) => elementToStyle(element)) + .filter((style) => Boolean(style)); + + if (styleStrings.length === 0) { + return null; + } + + return Style(styleStrings); +} + +function importHTML(html) { + const htmlWithBreaks = (html || '') + // Re-insert manual line-breaks for empty lines + .replace(/\n(?=\n)/g, '\n
') + .split('\n') + .map((s) => { + return `

${getValidHTML(s)}

`; + }) + .join(''); + return stateFromHTML(htmlWithBreaks, { customInlineFn }); +} + +export default importHTML; diff --git a/assets/src/edit-story/components/richText/customInlineDisplay.js b/assets/src/edit-story/components/richText/customInlineDisplay.js new file mode 100644 index 000000000000..8e75fb6297a4 --- /dev/null +++ b/assets/src/edit-story/components/richText/customInlineDisplay.js @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import formatters from './formatters'; +import { fauxStylesToCSS } from './fauxSelection'; + +function customInlineDisplay(styles) { + const stylesToCSSConverters = [ + ...formatters.map(({ stylesToCSS }) => stylesToCSS), + fauxStylesToCSS, + ]; + + return stylesToCSSConverters.reduce( + (css, stylesToCSS) => ({ ...css, ...stylesToCSS(styles) }), + {} + ); +} + +export default customInlineDisplay; diff --git a/assets/src/edit-story/components/richText/draftUtils.js b/assets/src/edit-story/components/richText/draftUtils.js new file mode 100644 index 000000000000..356c9a05b85d --- /dev/null +++ b/assets/src/edit-story/components/richText/draftUtils.js @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Ignore reason: This is lifted from elsewhere - a combo of these basically: + * + * https://github.com/webdeveloperpr/draft-js-custom-styles/blob/f3e6b533905de8eee6da54f9727b5e5803d53fc4/src/index.js#L8-L52 + * https://github.com/facebook/draft-js/issues/602#issuecomment-584676405 + */ +/* istanbul ignore next */ + +/** + * This returns *an array of sets of styles* for all currently selected + * characters. + * + * If you have the following states with html tags representing styles + * and [] representing selection),you get the following returns: + * + * + * input: Hel[lo w]orld + * output: [Set(), Set(), Set(), Set()] + * + * input: Hel[lo w]orld + * output: [Set("BOLD"), Set("BOLD"), Set(), Set()] + * + * input: Hel[lo w]orld + * output: [Set("BOLD"), Set("BOLD", "ITALIC"), Set(), Set("UNDERLINE")] + * + * + * @param {Object} editorState The current state of the editor including + * selection + * @return {Array.>} list of sets of styles as described + */ +export function getAllStyleSetsInSelection(editorState) { + const styleSets = []; + const contentState = editorState.getCurrentContent(); + const selection = editorState.getSelection(); + let key = selection.getStartKey(); + let startOffset = selection.getStartOffset(); + const endKey = selection.getEndKey(); + const endOffset = selection.getEndOffset(); + let hasMoreRounds = true; + while (hasMoreRounds) { + hasMoreRounds = key !== endKey; + const block = contentState.getBlockForKey(key); + const offsetEnd = hasMoreRounds ? block.getLength() : endOffset; + const characterList = block.getCharacterList(); + for ( + let offsetIndex = startOffset; + offsetIndex < offsetEnd; + offsetIndex++ + ) { + styleSets.push(characterList.get(offsetIndex).getStyle()); + } + if (!hasMoreRounds) { + break; + } + key = contentState.getKeyAfter(key); + startOffset = 0; + } + + return styleSets; +} diff --git a/assets/src/edit-story/components/richText/editor.js b/assets/src/edit-story/components/richText/editor.js new file mode 100644 index 000000000000..47385b1cf830 --- /dev/null +++ b/assets/src/edit-story/components/richText/editor.js @@ -0,0 +1,101 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { Editor } from 'draft-js'; +import PropTypes from 'prop-types'; +import { useEffect, useRef, useImperativeHandle, forwardRef } from 'react'; + +/** + * Internal dependencies + */ +import useUnmount from '../../utils/useUnmount'; +import useRichText from './useRichText'; +import customInlineDisplay from './customInlineDisplay'; + +function RichTextEditor({ content, onChange }, ref) { + const editorRef = useRef(null); + const { + state: { editorState }, + actions: { + setStateFromContent, + updateEditorState, + getHandleKeyCommand, + getContentFromState, + clearState, + }, + } = useRichText(); + + // Load state from parent when content changes + useEffect(() => { + setStateFromContent(content); + }, [setStateFromContent, content]); + + // Push updates to parent when state changes + useEffect(() => { + if (!editorState) { + return; + } + const newContent = getContentFromState(editorState); + if (newContent) { + onChange(newContent); + } + }, [onChange, getContentFromState, editorState]); + + const hasEditorState = Boolean(editorState); + + // On unmount, clear state in provider + useUnmount(clearState); + + // Allow parent to focus editor and access main node + useImperativeHandle( + ref, + () => ({ + focus: () => editorRef.current?.focus?.(), + getNode: () => editorRef.current?.editorContainer, + }), + [] + ); + + if (!hasEditorState) { + return null; + } + + // Handle basic key commands such as bold, italic and underscore. + const handleKeyCommand = getHandleKeyCommand(); + + return ( + + ); +} + +const RichTextEditorWithRef = forwardRef(RichTextEditor); + +RichTextEditor.propTypes = { + content: PropTypes.string.isRequired, + onChange: PropTypes.func.isRequired, +}; + +export default RichTextEditorWithRef; diff --git a/assets/src/edit-story/components/richText/fauxSelection.js b/assets/src/edit-story/components/richText/fauxSelection.js new file mode 100644 index 000000000000..f749416310c4 --- /dev/null +++ b/assets/src/edit-story/components/richText/fauxSelection.js @@ -0,0 +1,126 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useEffect, useState } from 'react'; +import { EditorState, Modifier } from 'draft-js'; + +const FAUX_SELECTION = 'CUSTOM-FAUX'; + +function isEqualSelectionIgnoreFocus(a, b) { + if (!a || !b) { + return false; + } + const aWithoutFocus = a.serialize().replace(/has focus: .*$/i, ''); + const bWithoutFocus = b.serialize().replace(/has focus: .*$/i, ''); + return aWithoutFocus === bWithoutFocus; +} + +/** + * A hook to properly set and remove faux selection style. + * + * If current selection in editor is unfocused, set faux style on current selection + * else, if current selection in editor is focused, remove faux style from entire editor + * + * @param {Object} editorState Current editor state + * @param {Function} setEditorState Callback to update current editor state + * @return {void} + */ +export function useFauxSelection(editorState, setEditorState) { + const [fauxSelection, setFauxSelection] = useState(null); + useEffect(() => { + if (!editorState) { + setFauxSelection(null); + return; + } + const content = editorState.getCurrentContent(); + const currentSelection = editorState.getSelection(); + const isFocused = currentSelection.getHasFocus(); + const hasSelectionChanged = !isEqualSelectionIgnoreFocus( + fauxSelection, + currentSelection + ); + const hasFauxSelection = Boolean(fauxSelection); + + if (!isFocused && !hasFauxSelection) { + // Get new content with style applied to selection + const contentWithFaux = Modifier.applyInlineStyle( + content, + currentSelection, + FAUX_SELECTION + ); + + // Push to get a new state + const stateWithFaux = EditorState.push( + editorState, + contentWithFaux, + 'change-inline-style' + ); + + // Save that as the next editor state + setEditorState(stateWithFaux); + + // And remember what we marked + setFauxSelection(currentSelection); + } + + if (isFocused && hasSelectionChanged && hasFauxSelection) { + setEditorState((oldEditorState) => { + try { + // Get new content with style removed from old selection + const contentWithoutFaux = Modifier.removeInlineStyle( + oldEditorState.getCurrentContent(), + fauxSelection, + FAUX_SELECTION + ); + + // Push to get a new state + const stateWithoutFaux = EditorState.push( + oldEditorState, + contentWithoutFaux, + 'change-inline-style' + ); + + // Force selection + const selectedState = EditorState.forceSelection( + stateWithoutFaux, + oldEditorState.getSelection() + ); + + // Save that as the next editor state + return selectedState; + } catch (e) { + // If the component has unmounted/remounted, some of the above might throw + // if so, just ignore it and return old state + return oldEditorState; + } + }); + + // And forget that we ever marked anything + setFauxSelection(null); + } + }, [fauxSelection, editorState, setEditorState]); +} + +export function fauxStylesToCSS(styles) { + const hasFauxSelection = styles.includes(FAUX_SELECTION); + if (!hasFauxSelection) { + return null; + } + return { backgroundColor: 'rgba(169, 169, 169, 0.7)' }; +} diff --git a/assets/src/edit-story/components/richText/formatters/color.js b/assets/src/edit-story/components/richText/formatters/color.js new file mode 100644 index 000000000000..690b1b310247 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/color.js @@ -0,0 +1,103 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { getHexFromSolid, getSolidFromHex } from '../../../utils/patternUtils'; +import isPatternEqual from '../../../utils/isPatternEqual'; +import createSolidFromString from '../../../utils/createSolidFromString'; +import createSolid from '../../../utils/createSolid'; +import generatePatternStyles from '../../../utils/generatePatternStyles'; +import { MULTIPLE_VALUE } from '../../form'; +import { NONE, COLOR } from '../customConstants'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../styleManipulation'; +import { isStyle, getVariable } from './util'; + +/* + * Color uses PREFIX-XXXXXXXX where XXXXXXXX is the 8 digit + * hex represenation of the RGBA color. + */ +const styleToColor = (style) => getSolidFromHex(getVariable(style, COLOR)); + +const colorToStyle = (color) => `${COLOR}-${getHexFromSolid(color)}`; + +function elementToStyle(element) { + const isSpan = element.tagName.toLowerCase() === 'span'; + const rawColor = element.style.color; + const hasColor = Boolean(rawColor); + if (isSpan && hasColor) { + const solid = createSolidFromString(rawColor); + return colorToStyle(solid); + } + + return null; +} + +function stylesToCSS(styles) { + const style = styles.find((someStyle) => isStyle(someStyle, COLOR)); + if (!style) { + return null; + } + let color; + try { + color = styleToColor(style); + } catch (e) { + return null; + } + + return generatePatternStyles(color, 'color'); +} + +function getColor(editorState) { + const styles = getPrefixStylesInSelection(editorState, COLOR); + if (styles.length > 1) { + return MULTIPLE_VALUE; + } + const colorStyle = styles[0]; + if (colorStyle === NONE) { + return createSolid(0, 0, 0); + } + return styleToColor(colorStyle); +} + +function setColor(editorState, color) { + // opaque black is default, and isn't necessary to set + const isBlack = isPatternEqual(createSolid(0, 0, 0), color); + const shouldSetStyle = () => !isBlack; + + // the style util manages conversion + const getStyleToSet = () => colorToStyle(color); + + return togglePrefixStyle(editorState, COLOR, shouldSetStyle, getStyleToSet); +} + +const formatter = { + elementToStyle, + stylesToCSS, + autoFocus: false, + getters: { + color: getColor, + }, + setters: { + setColor, + }, +}; + +export default formatter; diff --git a/assets/src/edit-story/components/richText/formatters/index.js b/assets/src/edit-story/components/richText/formatters/index.js new file mode 100644 index 000000000000..b85d981edc02 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/index.js @@ -0,0 +1,34 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import weightFormatter from './weight'; +import italicFormatter from './italic'; +import underlineFormatter from './underline'; +import colorFormatter from './color'; +import letterSpacingFormatter from './letterSpacing'; + +const formatters = [ + weightFormatter, + italicFormatter, + underlineFormatter, + colorFormatter, + letterSpacingFormatter, +]; + +export default formatters; diff --git a/assets/src/edit-story/components/richText/formatters/italic.js b/assets/src/edit-story/components/richText/formatters/italic.js new file mode 100644 index 000000000000..e829bb1aff42 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/italic.js @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { NONE, ITALIC } from '../customConstants'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../styleManipulation'; + +function elementToStyle(element) { + const isSpan = element.tagName.toLowerCase() === 'span'; + const isItalicFontStyle = element.style.fontStyle === 'italic'; + if (isSpan && isItalicFontStyle) { + return ITALIC; + } + + return null; +} + +function stylesToCSS(styles) { + const hasItalic = styles.includes(ITALIC); + if (!hasItalic) { + return null; + } + return { fontStyle: 'italic' }; +} + +function isItalic(editorState) { + const styles = getPrefixStylesInSelection(editorState, ITALIC); + return !styles.includes(NONE); +} + +function toggleItalic(editorState, flag) { + if (typeof flag === 'boolean') { + return togglePrefixStyle(editorState, ITALIC, () => flag); + } + return togglePrefixStyle(editorState, ITALIC); +} + +const formatter = { + elementToStyle, + stylesToCSS, + autoFocus: true, + getters: { + isItalic, + }, + setters: { + toggleItalic, + }, +}; + +export default formatter; diff --git a/assets/src/edit-story/components/richText/formatters/letterSpacing.js b/assets/src/edit-story/components/richText/formatters/letterSpacing.js new file mode 100644 index 000000000000..5176d9b36ec1 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/letterSpacing.js @@ -0,0 +1,99 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { MULTIPLE_VALUE } from '../../form'; +import { NONE, LETTERSPACING } from '../customConstants'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../styleManipulation'; +import { isStyle, numericToStyle, styleToNumeric } from './util'; + +function letterSpacingToStyle(weight) { + return numericToStyle(LETTERSPACING, weight); +} + +function styleToLetterSpacing(style) { + return styleToNumeric(LETTERSPACING, style); +} + +function elementToStyle(element) { + const isSpan = element.tagName.toLowerCase() === 'span'; + // This will implicitly strip any trailing unit from the value - it's assumed to be em + const letterSpacing = parseFloat(element.style.letterSpacing); + const hasLetterSpacing = letterSpacing && !isNaN(letterSpacing); + if (isSpan && hasLetterSpacing) { + return letterSpacingToStyle(letterSpacing * 100); + } + + return null; +} + +function stylesToCSS(styles) { + const style = styles.find((someStyle) => isStyle(someStyle, LETTERSPACING)); + if (!style) { + return null; + } + const letterSpacing = styleToLetterSpacing(style); + if (!letterSpacing) { + return null; + } + return { letterSpacing: `${letterSpacing / 100}em` }; +} + +function getLetterSpacing(editorState) { + const styles = getPrefixStylesInSelection(editorState, LETTERSPACING); + if (styles.length > 1) { + return MULTIPLE_VALUE; + } + const spacingStyle = styles[0]; + if (spacingStyle === NONE) { + return 0; + } + return styleToLetterSpacing(spacingStyle); +} + +function setLetterSpacing(editorState, letterSpacing) { + // if the spacing to set to non-0, set a style + const shouldSetStyle = () => letterSpacing !== 0; + + // and if we're setting a style, it's the style for the spacing of course + const getStyleToSet = () => letterSpacingToStyle(letterSpacing); + + return togglePrefixStyle( + editorState, + LETTERSPACING, + shouldSetStyle, + getStyleToSet + ); +} + +const formatter = { + elementToStyle, + stylesToCSS, + autoFocus: false, + getters: { + letterSpacing: getLetterSpacing, + }, + setters: { + setLetterSpacing, + }, +}; + +export default formatter; diff --git a/assets/src/edit-story/components/richText/formatters/test/_utils.js b/assets/src/edit-story/components/richText/formatters/test/_utils.js new file mode 100644 index 000000000000..f07eeab5a5a0 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/_utils.js @@ -0,0 +1,26 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { render } from 'react-dom'; + +export function getDOMElement(jsx) { + const el = document.createElement('div'); + render(jsx, el); + return el.firstChild; +} diff --git a/assets/src/edit-story/components/richText/formatters/test/color.js b/assets/src/edit-story/components/richText/formatters/test/color.js new file mode 100644 index 000000000000..07718fe663aa --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/color.js @@ -0,0 +1,177 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import createSolid from '../../../../utils/createSolid'; +import { MULTIPLE_VALUE } from '../../../form'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../../styleManipulation'; +import { NONE, COLOR } from '../../customConstants'; +import formatter from '../color'; +import { getDOMElement } from './_utils'; + +jest.mock('../../styleManipulation', () => { + return { + togglePrefixStyle: jest.fn(), + getPrefixStylesInSelection: jest.fn(), + }; +}); + +getPrefixStylesInSelection.mockImplementation(() => [NONE]); + +describe('Color formatter', () => { + const { elementToStyle, stylesToCSS, getters, setters } = formatter; + + beforeEach(() => { + togglePrefixStyle.mockClear(); + getPrefixStylesInSelection.mockClear(); + }); + + describe('elementToStyle', () => { + function setup(element) { + return elementToStyle(getDOMElement(element)); + } + + it('should ignore non-span elements', () => { + const element =
; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should ignore span elements without color style property', () => { + const element = ; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should extract color without opacity from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = `${COLOR}-ff000064`; + + expect(style).toBe(expected); + }); + + it('should extract color with opacity from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = `${COLOR}-00ff0032`; + + expect(style).toBe(expected); + }); + }); + + describe('stylesToCSS', () => { + it('should ignore styles without a color style', () => { + const css = stylesToCSS(['NOT-COLOR', 'ALSO-NOT-COLOR']); + + expect(css).toBeNull(); + }); + + it('should ignore invalid color style', () => { + const css = stylesToCSS([`${COLOR}-invalid`]); + + expect(css).toBeNull(); + }); + + it('should return correct CSS for a valid color style', () => { + const css = stylesToCSS([`${COLOR}-ff000032`]); + + expect(css).toStrictEqual({ color: 'rgba(255,0,0,0.5)' }); + }); + }); + + describe('getters', () => { + it('should contain color property with getter', () => { + expect(getters).toContainAllKeys(['color']); + expect(getters.color).toStrictEqual(expect.any(Function)); + }); + + it('should invoke getPrefixStylesInSelection with given state and correct style prefix', () => { + const state = {}; + getters.color(state); + expect(getPrefixStylesInSelection).toHaveBeenCalledWith(state, COLOR); + }); + + function setup(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.color({}); + } + + it('should return multiple if more than one style matches', () => { + const styles = [`${COLOR}-ff000064`, `${COLOR}-ffff0064`]; + const result = setup(styles); + expect(result).toBe(MULTIPLE_VALUE); + }); + + it('should return default black if no style matches', () => { + const styles = [NONE]; + const result = setup(styles); + expect(result).toStrictEqual(createSolid(0, 0, 0)); + }); + + it('should return parsed color if exactly one style matches', () => { + const styles = [`${COLOR}-ffff0032`]; + const result = setup(styles); + expect(result).toStrictEqual(createSolid(255, 255, 0, 0.5)); + }); + }); + + describe('setters', () => { + it('should contain setColor property with function', () => { + expect(setters).toContainAllKeys(['setColor']); + expect(setters.setColor).toStrictEqual(expect.any(Function)); + }); + + it('should invoke togglePrefixStyle correctly with non-black color', () => { + const state = {}; + const color = createSolid(255, 0, 255); + setters.setColor(state, color); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + COLOR, + expect.any(Function), + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + + // Fourth argument is actual style to set + const styleToSet = togglePrefixStyle.mock.calls[0][3]; + expect(styleToSet()).toStrictEqual(`${COLOR}-ff00ff64`); + }); + + it('should invoke togglePrefixStyle correctly with black color', () => { + const state = {}; + const color = createSolid(0, 0, 0); + setters.setColor(state, color); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + + // Fourth argument is ignored + }); + }); +}); diff --git a/assets/src/edit-story/components/richText/formatters/test/italic.js b/assets/src/edit-story/components/richText/formatters/test/italic.js new file mode 100644 index 000000000000..ba96c8ee1df5 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/italic.js @@ -0,0 +1,163 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../../styleManipulation'; +import { NONE, ITALIC } from '../../customConstants'; +import formatter from '../italic'; +import { getDOMElement } from './_utils'; + +jest.mock('../../styleManipulation', () => { + return { + togglePrefixStyle: jest.fn(), + getPrefixStylesInSelection: jest.fn(), + }; +}); + +getPrefixStylesInSelection.mockImplementation(() => [NONE]); + +describe('Italic formatter', () => { + beforeEach(() => { + togglePrefixStyle.mockClear(); + getPrefixStylesInSelection.mockClear(); + }); + + const { elementToStyle, stylesToCSS, getters, setters } = formatter; + + describe('elementToStyle', () => { + function setup(element) { + return elementToStyle(getDOMElement(element)); + } + + it('should ignore non-span elements', () => { + const element =
; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should ignore span elements without italic font style', () => { + const element = ; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should detect italic from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = ITALIC; + + expect(style).toBe(expected); + }); + }); + + describe('stylesToCSS', () => { + it('should ignore styles without italic style', () => { + const css = stylesToCSS(['NOT-ITALIC', 'ALSO-NOT-ITALIC']); + + expect(css).toBeNull(); + }); + + it('should return correct CSS if italic is present', () => { + const css = stylesToCSS([ITALIC]); + + expect(css).toStrictEqual({ fontStyle: 'italic' }); + }); + }); + + describe('getters', () => { + it('should contain isItalic property with getter', () => { + expect(getters).toContainAllKeys(['isItalic']); + expect(getters.isItalic).toStrictEqual(expect.any(Function)); + }); + + it('should invoke getPrefixStylesInSelection with given state and correct style prefix', () => { + const state = {}; + getters.isItalic(state); + expect(getPrefixStylesInSelection).toHaveBeenCalledWith(state, ITALIC); + }); + + function setup(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.isItalic({}); + } + + it('should return false if both italic and non-italic', () => { + const styles = [NONE, ITALIC]; + const result = setup(styles); + expect(result).toBe(false); + }); + + it('should return false if no style matches', () => { + const styles = [NONE]; + const result = setup(styles); + expect(result).toStrictEqual(false); + }); + + it('should return true if only italic', () => { + const styles = [ITALIC]; + const result = setup(styles); + expect(result).toStrictEqual(true); + }); + }); + + describe('setters', () => { + it('should contain toggleItalic property with function', () => { + expect(setters).toContainAllKeys(['toggleItalic']); + expect(setters.toggleItalic).toStrictEqual(expect.any(Function)); + }); + + it('should invoke togglePrefixStyle with state and prefix', () => { + const state = {}; + setters.toggleItalic(state); + expect(togglePrefixStyle).toHaveBeenCalledWith(state, ITALIC); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting italic to false', () => { + const state = {}; + setters.toggleItalic(state, false); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + ITALIC, + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting italic to true', () => { + const state = {}; + setters.toggleItalic(state, true); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + ITALIC, + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + }); + }); +}); diff --git a/assets/src/edit-story/components/richText/formatters/test/letterSpacing.js b/assets/src/edit-story/components/richText/formatters/test/letterSpacing.js new file mode 100644 index 000000000000..3fa57246b4dd --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/letterSpacing.js @@ -0,0 +1,183 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { MULTIPLE_VALUE } from '../../../form'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../../styleManipulation'; +import { NONE, LETTERSPACING } from '../../customConstants'; +import formatter from '../letterSpacing'; +import { getDOMElement } from './_utils'; + +jest.mock('../../styleManipulation', () => { + return { + togglePrefixStyle: jest.fn(), + getPrefixStylesInSelection: jest.fn(), + }; +}); + +getPrefixStylesInSelection.mockImplementation(() => [NONE]); + +describe('Color formatter', () => { + beforeEach(() => { + togglePrefixStyle.mockClear(); + getPrefixStylesInSelection.mockClear(); + }); + + const { elementToStyle, stylesToCSS, getters, setters } = formatter; + + describe('elementToStyle', () => { + function setup(element) { + return elementToStyle(getDOMElement(element)); + } + + it('should ignore non-span elements', () => { + const element =
; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should ignore span elements without letter spacing style property', () => { + const element = ; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should extract letter spacing from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = `${LETTERSPACING}-150`; + + expect(style).toBe(expected); + }); + }); + + describe('stylesToCSS', () => { + it('should ignore styles without a letter spacing style', () => { + const css = stylesToCSS(['NOT-LETTERSPACING', 'ALSO-NOT-LETTERSPACING']); + + expect(css).toBeNull(); + }); + + it('should ignore invalid letter spacing style', () => { + const css = stylesToCSS([`${LETTERSPACING}-invalid`]); + + expect(css).toBeNull(); + }); + + it('should return correct CSS for a positive style', () => { + const css = stylesToCSS([`${LETTERSPACING}-150`]); + + expect(css).toStrictEqual({ letterSpacing: '1.5em' }); + }); + + it('should return correct CSS for a negative style', () => { + const css = stylesToCSS([`${LETTERSPACING}-N250`]); + + expect(css).toStrictEqual({ letterSpacing: '-2.5em' }); + }); + }); + + describe('getters', () => { + it('should contain letterSpacing property with getter', () => { + expect(getters).toContainAllKeys(['letterSpacing']); + expect(getters.letterSpacing).toStrictEqual(expect.any(Function)); + }); + + it('should invoke getPrefixStylesInSelection with given state and correct style prefix', () => { + const state = {}; + getters.letterSpacing(state); + expect(getPrefixStylesInSelection).toHaveBeenCalledWith( + state, + LETTERSPACING + ); + }); + + function setup(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.letterSpacing({}); + } + + it('should return multiple if more than one style matches', () => { + const styles = [`${LETTERSPACING}-150`, `${LETTERSPACING}-N100`]; + const result = setup(styles); + expect(result).toBe(MULTIPLE_VALUE); + }); + + it('should return default 0 if no style matches', () => { + const styles = [NONE]; + const result = setup(styles); + expect(result).toStrictEqual(0); + }); + + it('should return parsed letter spacing if exactly one style matches', () => { + const styles = [`${LETTERSPACING}-34`]; + const result = setup(styles); + expect(result).toStrictEqual(34); + }); + }); + + describe('setters', () => { + it('should contain setLetterSpacing property with function', () => { + expect(setters).toContainAllKeys(['setLetterSpacing']); + expect(setters.setLetterSpacing).toStrictEqual(expect.any(Function)); + }); + + it('should invoke togglePrefixStyle with state and prefix', () => { + const state = {}; + const letterSpacing = 0; + setters.setLetterSpacing(state, letterSpacing); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + LETTERSPACING, + expect.any(Function), + expect.any(Function) + ); + }); + + it('should invoke togglePrefixStyle correctly for trivial letter spacing', () => { + const state = {}; + // 0 letter spacing is the trivial case, that doesn't need to be added + const letterSpacing = 0; + setters.setLetterSpacing(state, letterSpacing); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + }); + + it('should invoke togglePrefixStyle correctly for non-trivial letter spacing', () => { + const state = {}; + // A non-zero letter spacing should be added as a style + const letterSpacing = -150; + setters.setLetterSpacing(state, letterSpacing); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + + // Fourth argument is actual style to set + const styleToSet = togglePrefixStyle.mock.calls[0][3]; + expect(styleToSet()).toStrictEqual(`${LETTERSPACING}-N150`); + }); + }); +}); diff --git a/assets/src/edit-story/components/richText/formatters/test/underline.js b/assets/src/edit-story/components/richText/formatters/test/underline.js new file mode 100644 index 000000000000..80b143462332 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/underline.js @@ -0,0 +1,163 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../../styleManipulation'; +import { NONE, UNDERLINE } from '../../customConstants'; +import formatter from '../underline'; +import { getDOMElement } from './_utils'; + +jest.mock('../../styleManipulation', () => { + return { + togglePrefixStyle: jest.fn(), + getPrefixStylesInSelection: jest.fn(), + }; +}); + +getPrefixStylesInSelection.mockImplementation(() => [NONE]); + +describe('Underline formatter', () => { + beforeEach(() => { + togglePrefixStyle.mockClear(); + getPrefixStylesInSelection.mockClear(); + }); + + const { elementToStyle, stylesToCSS, getters, setters } = formatter; + + describe('elementToStyle', () => { + function setup(element) { + return elementToStyle(getDOMElement(element)); + } + + it('should ignore non-span elements', () => { + const element =
; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should ignore span elements without underline text decoration', () => { + const element = ; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should detect underline from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = UNDERLINE; + + expect(style).toBe(expected); + }); + }); + + describe('stylesToCSS', () => { + it('should ignore styles without underline style', () => { + const css = stylesToCSS(['NOT-UNDERLINE', 'ALSO-NOT-UNDERLINE']); + + expect(css).toBeNull(); + }); + + it('should return correct CSS if underline is present', () => { + const css = stylesToCSS([UNDERLINE]); + + expect(css).toStrictEqual({ textDecoration: 'underline' }); + }); + }); + + describe('getters', () => { + it('should contain isUnderline property with getter', () => { + expect(getters).toContainAllKeys(['isUnderline']); + expect(getters.isUnderline).toStrictEqual(expect.any(Function)); + }); + + it('should invoke getPrefixStylesInSelection with given state and correct style prefix', () => { + const state = {}; + getters.isUnderline(state); + expect(getPrefixStylesInSelection).toHaveBeenCalledWith(state, UNDERLINE); + }); + + function setup(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.isUnderline({}); + } + + it('should return false if both underline and non-underline', () => { + const styles = [NONE, UNDERLINE]; + const result = setup(styles); + expect(result).toBe(false); + }); + + it('should return false if no style matches', () => { + const styles = [NONE]; + const result = setup(styles); + expect(result).toStrictEqual(false); + }); + + it('should return true if only underline', () => { + const styles = [UNDERLINE]; + const result = setup(styles); + expect(result).toStrictEqual(true); + }); + }); + + describe('setters', () => { + it('should contain toggleUnderline property with function', () => { + expect(setters).toContainAllKeys(['toggleUnderline']); + expect(setters.toggleUnderline).toStrictEqual(expect.any(Function)); + }); + + it('should invoke togglePrefixStyle with state and prefix', () => { + const state = {}; + setters.toggleUnderline(state); + expect(togglePrefixStyle).toHaveBeenCalledWith(state, UNDERLINE); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting underline to false', () => { + const state = {}; + setters.toggleUnderline(state, false); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + UNDERLINE, + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting underline to true', () => { + const state = {}; + setters.toggleUnderline(state, true); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + UNDERLINE, + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + }); + }); +}); diff --git a/assets/src/edit-story/components/richText/formatters/test/weight.js b/assets/src/edit-story/components/richText/formatters/test/weight.js new file mode 100644 index 000000000000..59f713446626 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/test/weight.js @@ -0,0 +1,259 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { MULTIPLE_VALUE } from '../../../form'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../../styleManipulation'; +import { NONE, WEIGHT } from '../../customConstants'; +import formatter from '../weight'; +import { getDOMElement } from './_utils'; + +jest.mock('../../styleManipulation', () => { + return { + togglePrefixStyle: jest.fn(), + getPrefixStylesInSelection: jest.fn(), + }; +}); + +getPrefixStylesInSelection.mockImplementation(() => [NONE]); + +describe('Color formatter', () => { + beforeEach(() => { + togglePrefixStyle.mockClear(); + getPrefixStylesInSelection.mockClear(); + }); + + const { elementToStyle, stylesToCSS, getters, setters } = formatter; + + describe('elementToStyle', () => { + function setup(element) { + return elementToStyle(getDOMElement(element)); + } + + it('should ignore non-span elements', () => { + const element =
; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should ignore span elements without font weight style property', () => { + const element = ; + const style = setup(element); + + expect(style).toBeNull(); + }); + + it('should extract font weight from span elements and return correct style', () => { + const element = ; + const style = setup(element); + const expected = `${WEIGHT}-600`; + + expect(style).toBe(expected); + }); + }); + + describe('stylesToCSS', () => { + it('should ignore styles without a font weight style', () => { + const css = stylesToCSS(['NOT-WEIGHT', 'ALSO-NOT-WEIGHT']); + + expect(css).toBeNull(); + }); + + it('should ignore invalid font weight style', () => { + const css = stylesToCSS([`${WEIGHT}-invalid`]); + + expect(css).toBeNull(); + }); + + it('should return correct CSS for a valid style', () => { + const css = stylesToCSS([`${WEIGHT}-500`]); + + expect(css).toStrictEqual({ fontWeight: 500 }); + }); + }); + + describe('getters', () => { + it('should contain weight and isBold properties with getters', () => { + expect(getters).toContainAllKeys(['fontWeight', 'isBold']); + expect(getters.fontWeight).toStrictEqual(expect.any(Function)); + expect(getters.isBold).toStrictEqual(expect.any(Function)); + }); + + it('should invoke getPrefixStylesInSelection with given state and correct style prefix', () => { + const state = {}; + getters.fontWeight(state); + expect(getPrefixStylesInSelection).toHaveBeenCalledWith(state, WEIGHT); + }); + + function setupFontWeight(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.fontWeight({}); + } + + it('should return multiple if more than one style matches', () => { + const styles = [`${WEIGHT}-400`, `${WEIGHT}-600`]; + const result = setupFontWeight(styles); + expect(result).toBe(MULTIPLE_VALUE); + }); + + it('should return default 400 if no style matches', () => { + const styles = [NONE]; + const result = setupFontWeight(styles); + expect(result).toStrictEqual(400); + }); + + it('should return parsed font weight if exactly one style matches', () => { + const styles = [`${WEIGHT}-700`]; + const result = setupFontWeight(styles); + expect(result).toStrictEqual(700); + }); + + function setupIsBold(styleArray) { + getPrefixStylesInSelection.mockImplementationOnce(() => styleArray); + return getters.isBold({}); + } + + it('should return false if mix of bold and non-bold', () => { + const styles = [`${WEIGHT}-300`, `${WEIGHT}-700`]; + const result = setupIsBold(styles); + expect(result).toBe(false); + }); + + it('should return false if no style matches', () => { + const styles = [NONE]; + const result = setupIsBold(styles); + expect(result).toStrictEqual(false); + }); + + it('should return true if all are bold', () => { + const styles = [`${WEIGHT}-800`, `${WEIGHT}-600`]; + const result = setupIsBold(styles); + expect(result).toStrictEqual(true); + }); + }); + + describe('setters', () => { + it('should contain setWeight and toggleBold properties with functions', () => { + expect(setters).toContainAllKeys(['setFontWeight', 'toggleBold']); + expect(setters.setFontWeight).toStrictEqual(expect.any(Function)); + expect(setters.toggleBold).toStrictEqual(expect.any(Function)); + }); + + it('should invoke togglePrefixStyle with state and prefix', () => { + const state = {}; + const weight = 0; + setters.setFontWeight(state, weight); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + WEIGHT, + expect.any(Function), + expect.any(Function) + ); + }); + + it('should invoke togglePrefixStyle correctly for trivial font weight', () => { + const state = {}; + // 400 font weight is the trivial case, that doesn't need to be added + const weight = 400; + setters.setFontWeight(state, weight); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + }); + + it('should invoke togglePrefixStyle correctly for non-trivial font weight', () => { + const state = {}; + // A non-400 font weight should be added as a style + const weight = 900; + setters.setFontWeight(state, weight); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + + // Fourth argument is actual style to set + const styleToSet = togglePrefixStyle.mock.calls[0][3]; + expect(styleToSet()).toStrictEqual(`${WEIGHT}-900`); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting bold to false', () => { + const state = {}; + setters.toggleBold(state, false); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + WEIGHT, + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(false); + }); + + it('should invoke togglePrefixStyle correctly for explicitly setting bold to true', () => { + const state = {}; + setters.toggleBold(state, true); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + WEIGHT, + expect.any(Function), + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle()).toBe(true); + + // Fourth argument is actual style to set + const styleToSet = togglePrefixStyle.mock.calls[0][3]; + expect(styleToSet()).toStrictEqual(`${WEIGHT}-700`); + }); + + it('should correctly determine if setting bold when toggling without explicit flag', () => { + const state = {}; + setters.toggleBold(state); + expect(togglePrefixStyle).toHaveBeenCalledWith( + state, + WEIGHT, + expect.any(Function), + expect.any(Function) + ); + + // Third argument is tester + const shouldSetStyle = togglePrefixStyle.mock.calls[0][2]; + expect(shouldSetStyle([NONE])).toBe(true); + expect(shouldSetStyle([`${WEIGHT}-300`, `${WEIGHT}-900`])).toBe(true); + expect(shouldSetStyle([`${WEIGHT}-600`, `${WEIGHT}-900`])).toBe(false); + + // Fourth argument is actual style to set + const styleToSet = togglePrefixStyle.mock.calls[0][3]; + expect(styleToSet([NONE])).toBe(`${WEIGHT}-700`); + expect(styleToSet([`${WEIGHT}-300`, `${WEIGHT}-600`])).toBe( + `${WEIGHT}-700` + ); + expect(styleToSet([`${WEIGHT}-300`, `${WEIGHT}-900`])).toBe( + `${WEIGHT}-900` + ); + }); + }); +}); diff --git a/assets/src/edit-story/components/richText/formatters/underline.js b/assets/src/edit-story/components/richText/formatters/underline.js new file mode 100644 index 000000000000..07102776e120 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/underline.js @@ -0,0 +1,68 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { NONE, UNDERLINE } from '../customConstants'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../styleManipulation'; + +function elementToStyle(element) { + const isSpan = element.tagName.toLowerCase() === 'span'; + const isUnderlineDecoration = element.style.textDecoration === 'underline'; + if (isSpan && isUnderlineDecoration) { + return UNDERLINE; + } + + return null; +} + +function stylesToCSS(styles) { + const hasUnderline = styles.includes(UNDERLINE); + if (!hasUnderline) { + return null; + } + return { textDecoration: 'underline' }; +} + +function isUnderline(editorState) { + const styles = getPrefixStylesInSelection(editorState, UNDERLINE); + return !styles.includes(NONE); +} + +function toggleUnderline(editorState, flag) { + if (typeof flag === 'boolean') { + return togglePrefixStyle(editorState, UNDERLINE, () => flag); + } + return togglePrefixStyle(editorState, UNDERLINE); +} + +const formatter = { + elementToStyle, + stylesToCSS, + autoFocus: true, + getters: { + isUnderline, + }, + setters: { + toggleUnderline, + }, +}; + +export default formatter; diff --git a/assets/src/edit-story/components/richText/formatters/util.js b/assets/src/edit-story/components/richText/formatters/util.js new file mode 100644 index 000000000000..90d0d0245d6a --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/util.js @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const isStyle = (style, prefix) => + typeof style === 'string' && style.startsWith(prefix); + +export const getVariable = (style, prefix) => style.slice(prefix.length + 1); + +/* + * Numerics use PREFIX-123 for the number 123 + * and PREFIX-N123 for the number -123. + */ +export const numericToStyle = (prefix, num) => + `${prefix}-${num < 0 ? 'N' : ''}${Math.abs(num)}`; + +export const styleToNumeric = (prefix, style) => { + const raw = getVariable(style, prefix); + // Negative numbers are prefixed with an N: + if (raw.charAt(0) === 'N') { + return -parseInt(raw.slice(1)); + } + return parseInt(raw); +}; diff --git a/assets/src/edit-story/components/richText/formatters/weight.js b/assets/src/edit-story/components/richText/formatters/weight.js new file mode 100644 index 000000000000..3b14d6fff604 --- /dev/null +++ b/assets/src/edit-story/components/richText/formatters/weight.js @@ -0,0 +1,137 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import { MULTIPLE_VALUE } from '../../form'; +import { NONE, WEIGHT } from '../customConstants'; +import { + togglePrefixStyle, + getPrefixStylesInSelection, +} from '../styleManipulation'; +import { isStyle, numericToStyle, styleToNumeric } from './util'; + +const NORMAL_WEIGHT = 400; +const SMALLEST_BOLD = 600; +const DEFAULT_BOLD = 700; + +function weightToStyle(weight) { + return numericToStyle(WEIGHT, weight); +} + +function styleToWeight(style) { + return styleToNumeric(WEIGHT, style); +} + +function elementToStyle(element) { + const isSpan = element.tagName.toLowerCase() === 'span'; + const fontWeight = parseInt(element.style.fontWeight); + const hasFontWeight = fontWeight && !isNaN(fontWeight); + if (isSpan && hasFontWeight && fontWeight !== 400) { + return weightToStyle(fontWeight); + } + + return null; +} + +function stylesToCSS(styles) { + const style = styles.find((someStyle) => isStyle(someStyle, WEIGHT)); + if (!style) { + return null; + } + const fontWeight = styleToWeight(style); + if (!fontWeight) { + return null; + } + return { fontWeight }; +} + +// convert a set of weight styles to a set of weights +function getWeights(styles) { + return styles.map((style) => + style === NONE ? NORMAL_WEIGHT : styleToWeight(style) + ); +} + +function isBold(editorState) { + const styles = getPrefixStylesInSelection(editorState, WEIGHT); + const weights = getWeights(styles); + const allIsBold = weights.every((w) => w >= SMALLEST_BOLD); + return allIsBold; +} + +function toggleBold(editorState, flag) { + if (typeof flag === 'boolean') { + if (flag) { + const getDefault = () => weightToStyle(DEFAULT_BOLD); + return togglePrefixStyle(editorState, WEIGHT, () => true, getDefault); + } + + // No fourth arg needed as we're not setting a style + return togglePrefixStyle(editorState, WEIGHT, () => false); + } + + // if no flag is set, determine these values from current weights present + + // if any character has weight less than SMALLEST_BOLD, + // everything should be bolded + const shouldSetBold = (styles) => + getWeights(styles).some((w) => w < SMALLEST_BOLD); + + // if setting a bold, it should be the boldest current weight, + // though at least DEFAULT_BOLD + const getBoldToSet = (styles) => + weightToStyle(Math.max(...[DEFAULT_BOLD].concat(getWeights(styles)))); + + return togglePrefixStyle(editorState, WEIGHT, shouldSetBold, getBoldToSet); +} + +function getFontWeight(editorState) { + const styles = getPrefixStylesInSelection(editorState, WEIGHT); + const weights = getWeights(styles); + if (weights.length > 1) { + return MULTIPLE_VALUE; + } + return weights[0]; +} + +function setFontWeight(editorState, weight) { + // if the weight to set is non-400, set a style + // (if 400 is target, all other weights are just removed, and we're good) + const shouldSetStyle = () => weight !== 400; + + // and if we're setting a style, it's the style for the weight of course + const getBoldToSet = () => weightToStyle(weight); + + return togglePrefixStyle(editorState, WEIGHT, shouldSetStyle, getBoldToSet); +} + +const formatter = { + elementToStyle, + stylesToCSS, + autoFocus: true, + getters: { + isBold, + fontWeight: getFontWeight, + }, + setters: { + toggleBold, + setFontWeight, + }, +}; + +export default formatter; diff --git a/assets/src/edit-story/components/richText/getStateInfo.js b/assets/src/edit-story/components/richText/getStateInfo.js new file mode 100644 index 000000000000..99876a30425e --- /dev/null +++ b/assets/src/edit-story/components/richText/getStateInfo.js @@ -0,0 +1,35 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import formatters from './formatters'; + +function getStateInfo(state) { + const stateInfo = formatters.reduce( + (aggr, { getters }) => ({ + ...aggr, + ...Object.fromEntries( + Object.entries(getters).map(([key, getter]) => [key, getter(state)]) + ), + }), + {} + ); + return stateInfo; +} + +export default getStateInfo; diff --git a/assets/src/edit-story/components/richText/htmlManipulation.js b/assets/src/edit-story/components/richText/htmlManipulation.js new file mode 100644 index 000000000000..33d121b49930 --- /dev/null +++ b/assets/src/edit-story/components/richText/htmlManipulation.js @@ -0,0 +1,83 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { EditorState } from 'draft-js'; + +/** + * Internal dependencies + */ +import formatters from './formatters'; +import getStateInfo from './getStateInfo'; +import customImport from './customImport'; +import customExport from './customExport'; +import { getSelectionForAll } from './util'; + +/** + * Return an editor state object with content set to parsed HTML + * and selection set to everything. + * + * @param {string} html HTML string to parse into content + * @return {Object} New editor state with selection + */ +function getSelectAllStateFromHTML(html) { + const contentState = customImport(html); + const initialState = EditorState.createWithContent(contentState); + const selection = getSelectionForAll(initialState.getCurrentContent()); + return EditorState.forceSelection(initialState, selection); +} + +/** + * Convert HTML via updater function. As updater function works on the + * current selection in an editor state, first parse HTML to editor state + * with entire body selected, then run updater and then export back to + * HTML again. + * + * @param {string} html HTML string to parse into content + * @param {Function} updater A function converting a state to a new state + * @param {Array} args Extra args to supply to updater other than state + * @return {Object} New HTML with updates applied + */ +function updateAndReturnHTML(html, updater, ...args) { + const stateWithUpdate = updater(getSelectAllStateFromHTML(html), ...args); + const renderedHTML = customExport(stateWithUpdate); + return renderedHTML; +} + +const getHTMLFormatter = (setter) => (html, ...args) => + updateAndReturnHTML(html, setter, ...args); + +export const getHTMLFormatters = () => { + return formatters.reduce( + (aggr, { setters }) => ({ + ...aggr, + ...Object.fromEntries( + Object.entries(setters).map(([key, setter]) => [ + key, + getHTMLFormatter(setter), + ]) + ), + }), + {} + ); +}; + +export function getHTMLInfo(html) { + const htmlStateInfo = getStateInfo(getSelectAllStateFromHTML(html)); + return htmlStateInfo; +} diff --git a/assets/src/dashboard/components/navigationBar/index.js b/assets/src/edit-story/components/richText/index.js similarity index 91% rename from assets/src/dashboard/components/navigationBar/index.js rename to assets/src/edit-story/components/richText/index.js index 71a9d0a2d5a6..4f4f3a503e41 100644 --- a/assets/src/dashboard/components/navigationBar/index.js +++ b/assets/src/edit-story/components/richText/index.js @@ -17,4 +17,4 @@ /** * Internal dependencies */ -export { TemplateNavBar } from './templateNavBar'; +export { default as RichTextProvider } from './provider'; diff --git a/assets/src/edit-story/components/richText/provider.js b/assets/src/edit-story/components/richText/provider.js new file mode 100644 index 000000000000..ab5575d989d4 --- /dev/null +++ b/assets/src/edit-story/components/richText/provider.js @@ -0,0 +1,135 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import PropTypes from 'prop-types'; +import { useState, useCallback, useMemo } from 'react'; +import { EditorState } from 'draft-js'; + +/** + * Internal dependencies + */ +import useCanvas from '../canvas/useCanvas'; +import RichTextContext from './context'; +import { + getSelectionForAll, + getSelectionForOffset, + getFilteredState, + getHandleKeyCommandFromState, +} from './util'; +import getStateInfo from './getStateInfo'; +import { useFauxSelection } from './fauxSelection'; +import customImport from './customImport'; +import customExport from './customExport'; +import useSelectionManipulation from './useSelectionManipulation'; + +function RichTextProvider({ children }) { + const { + state: { editingElementState }, + } = useCanvas(); + + const [editorState, setEditorState] = useState(null); + + const selectionInfo = useMemo(() => { + if (editorState) { + return getStateInfo(editorState); + } + return { isBold: false, isItalic: false, isUnderline: false }; + }, [editorState]); + + const setStateFromContent = useCallback( + (content) => { + const { offset, clearContent, selectAll } = editingElementState || {}; + let state = EditorState.createWithContent(customImport(content)); + if (clearContent) { + // If `clearContent` is specified, push the update to clear content so that + // it can be undone. + state = EditorState.push(state, customImport(''), 'remove-range'); + } + let selection; + if (selectAll) { + selection = getSelectionForAll(state.getCurrentContent()); + } else if (offset) { + selection = getSelectionForOffset(state.getCurrentContent(), offset); + } + if (selection) { + state = EditorState.forceSelection(state, selection); + } + setEditorState(state); + }, + [editingElementState, setEditorState] + ); + + useFauxSelection(editorState, setEditorState); + + // This filters out illegal content (see `getFilteredState`) + // on paste and updates state accordingly. + // Furthermore it also sets initial selection if relevant. + const updateEditorState = useCallback( + (newEditorState) => { + const filteredState = getFilteredState(newEditorState, editorState); + setEditorState(filteredState); + }, + [editorState, setEditorState] + ); + + const getHandleKeyCommand = useCallback( + () => getHandleKeyCommandFromState(updateEditorState), + [updateEditorState] + ); + + const clearState = useCallback(() => { + setEditorState(null); + }, [setEditorState]); + + const hasCurrentEditor = Boolean(editorState); + + const selectionActions = useSelectionManipulation( + editorState, + setEditorState + ); + + const value = { + state: { + editorState, + hasCurrentEditor, + selectionInfo, + }, + actions: { + setStateFromContent, + updateEditorState, + getHandleKeyCommand, + clearState, + selectionActions, + // These actually don't work on the state at all, just pure functions + getContentFromState: customExport, + }, + }; + + return ( + + {children} + + ); +} + +RichTextProvider.propTypes = { + children: PropTypes.node.isRequired, +}; + +export default RichTextProvider; diff --git a/assets/src/edit-story/components/richText/styleManipulation.js b/assets/src/edit-story/components/richText/styleManipulation.js new file mode 100644 index 000000000000..8a142bb90c51 --- /dev/null +++ b/assets/src/edit-story/components/richText/styleManipulation.js @@ -0,0 +1,205 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { Modifier, EditorState } from 'draft-js'; + +/** + * Internal dependencies + */ +import { NONE } from './customConstants'; +import { getAllStyleSetsInSelection } from './draftUtils'; + +/** + * Get a first style in the given set that match the given prefix, + * or NONE if there's no match. + * + * @param {Set.} styles Set (ImmutableSet even) of styles to check + * @param {string} prefix Prefix to test styles for + * @return {string} First match or NONE + */ +export function getPrefixStyleForCharacter(styles, prefix) { + const list = styles.toArray().map((style) => style.style ?? style); + const matcher = (style) => style && style.startsWith(prefix); + if (!list.some(matcher)) { + return NONE; + } + return list.find(matcher); +} + +/** + * Get a deduped list of all matching styles for every character in the + * current selection for the current content. + * + * A style is matching, if it matches the current prefix. If for any given + * character in the current selection, there's no prefix match, NONE will + * be part of the returned set. + * + * If selection is collapsed, it'll get the style matching at the current + * insertion point (or NONE). + * + * + * // In all examples below, + * // - first character is WEIGHT-700, ITALIC + * // - second character is WEIGHT-900, ITALIC + * // - third character is ITALIC + * // - all other characters have no styling + * + * // This is the text: Hello + * // Editor state with selection "[He]llo": + * const editorStateEl = {}; + * // Editor state with selection "[Hel]lo": + * const editorStateHel = {}; + * // Editor state with selection "[Hello]": + * const editorStateHello = {}; + * // Editor state with selection "Hel[lo]": + * const editorStateLo = {}; + * + * const styles = getPrefixStylesInSelection(editorStateEl, 'ITALIC'); + * // styles are now: ['ITALIC'] + * + * const styles = getPrefixStylesInSelection(editorStateHello, 'ITALIC'); + * // styles are now: ['ITALIC', 'NONE'] + * + * const styles = getPrefixStylesInSelection(editorStateLo, 'ITALIC'); + * // styles are now: ['NONE'] + * + * const styles = getPrefixStylesInSelection(editorStateHel, 'WEIGHT'); + * // styles are now: ['WEIGHT-700', 'WEIGHT-900', 'NONE'] + * + * const styles = getPrefixStylesInSelection(editorStateLo, 'WEIGHT'); + * // styles are now: ['NONE'] + * + * + * @param {Object} editorState Current editor state + * @param {string} prefix Prefix to test styles for + * @return {Array.} Deduped array of all matching styles + */ +export function getPrefixStylesInSelection(editorState, prefix) { + const selection = editorState.getSelection(); + if (selection.isCollapsed()) { + return [ + getPrefixStyleForCharacter(editorState.getCurrentInlineStyle(), prefix), + ]; + } + + const styleSets = getAllStyleSetsInSelection(editorState); + const styles = new Set(); + styleSets.forEach((styleSet) => + styles.add(getPrefixStyleForCharacter(styleSet, prefix)) + ); + + return [...styles]; +} + +function applyContent(editorState, contentState) { + return EditorState.push(editorState, contentState, 'change-inline-style'); +} + +/** + * Toggle prefix style in selection. This is a pretty complex function and + * it's probably easiest to understand how it works by following the inline + * comments and reading through the corresponding exhaustive unit tests. + * + * @param {Object} editorState Current editor state + * @param {string} prefix Style (prefix) to remove from state and potentially + * replace with different style + * @param {Function} shouldSetStyle Optional function to get if new style + * should be added or not + * @param {Function} getStyleToSet Optional function to get what new style + * should be added + * + * @return {Object} New editor state + */ +export function togglePrefixStyle( + editorState, + prefix, + shouldSetStyle = null, + getStyleToSet = null +) { + if (editorState.getSelection().isCollapsed()) { + // A different set of rules apply here + // First find all styles that apply at cursor - we'll reapply those as override + // with modifications at the end + let inlineStyles = editorState.getCurrentInlineStyle(); + + // See if there's a matching style for our prefix + const foundMatch = getPrefixStyleForCharacter(inlineStyles, prefix); + + // Then remove potentially found style from list + if (foundMatch !== NONE) { + inlineStyles = inlineStyles.remove(foundMatch); + } + + // Then figure out whether to apply new style or not + const willAddStyle = shouldSetStyle + ? shouldSetStyle([foundMatch]) + : foundMatch === NONE; + + // If so, add to list + if (willAddStyle) { + const styleToAdd = getStyleToSet ? getStyleToSet([foundMatch]) : prefix; + inlineStyles = inlineStyles.add(styleToAdd); + } + + // Finally apply to style override + const newState = EditorState.setInlineStyleOverride( + editorState, + inlineStyles + ); + return newState; + } + + const matchingStyles = getPrefixStylesInSelection(editorState, prefix); + + // First remove all old styles matching prefix + // (except NONE, it's not actually a style) + const stylesToRemove = matchingStyles.filter((s) => s !== NONE); + const strippedContentState = stylesToRemove.reduce( + (contentState, styleToRemove) => + Modifier.removeInlineStyle( + contentState, + editorState.getSelection(), + styleToRemove + ), + editorState.getCurrentContent() + ); + + // Should we add a style to everything now? + // If no function is given, we simply add the style if any + // character did not have a match (NONE is in the list) + const willSetStyle = shouldSetStyle + ? shouldSetStyle(matchingStyles) + : matchingStyles.includes(NONE); + + if (!willSetStyle) { + // we're done! + return applyContent(editorState, strippedContentState); + } + + // Add style to entire selection + // If no function is given, we simple add the prefix as a style + const styleToSet = getStyleToSet ? getStyleToSet(matchingStyles) : prefix; + const newContentState = Modifier.applyInlineStyle( + strippedContentState, + editorState.getSelection(), + styleToSet + ); + + return applyContent(editorState, newContentState); +} diff --git a/assets/src/edit-story/components/richText/test/styleManipulation.js b/assets/src/edit-story/components/richText/test/styleManipulation.js new file mode 100644 index 000000000000..3826be93736e --- /dev/null +++ b/assets/src/edit-story/components/richText/test/styleManipulation.js @@ -0,0 +1,383 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { convertFromRaw, EditorState, SelectionState } from 'draft-js'; + +/** + * Internal dependencies + */ +import { + getPrefixStyleForCharacter, + getPrefixStylesInSelection, + togglePrefixStyle, +} from '../styleManipulation'; + +expect.extend({ + toHaveStyleAtCursor(received, style) { + const styles = received.getCurrentInlineStyle().toArray(); + const pass = styles.includes(style); + if (pass) { + return { + message: () => `expected ${styles} to not include ${style}`, + pass: true, + }; + } else { + return { + message: () => `expected ${styles} to include ${style}`, + pass: false, + }; + } + }, + toHaveStyleInSelection(received, style, stylePrefix = null) { + stylePrefix = stylePrefix ?? style; + const styles = getPrefixStylesInSelection(received, stylePrefix); + const pass = styles.includes(style); + if (pass) { + return { + message: () => `expected selection ${styles} to not include ${style}`, + pass: true, + }; + } else { + return { + message: () => `expected selection ${styles} to include ${style}`, + pass: false, + }; + } + }, + toHaveStyleInEntireSelection(received, style, stylePrefix = null) { + stylePrefix = stylePrefix ?? style; + const styles = getPrefixStylesInSelection(received, stylePrefix); + const pass = styles.includes(style) && styles.length === 1; + if (pass) { + return { + message: () => + `expected selection ${styles} to not only include ${style}`, + pass: true, + }; + } else { + return { + message: () => `expected selection ${styles} to only include ${style}`, + pass: false, + }; + } + }, +}); + +describe('getPrefixStyleForCharacter', () => { + function setup(styleArray, prefix) { + return getPrefixStyleForCharacter({ toArray: () => styleArray }, prefix); + } + + it('should return a direct match', () => { + const match = setup(['ALPHA', 'BRAVO', 'CHARLIE'], 'BRAVO'); + expect(match).toStrictEqual('BRAVO'); + }); + + it('should return a prefix match', () => { + const match = setup(['ALPHA-1', 'BRAVO-2', 'CHARLIE-3'], 'BRAVO'); + expect(match).toStrictEqual('BRAVO-2'); + }); + + it('should return first match if multiple', () => { + const match = setup(['ALPHA-1', 'BRAVO-2', 'BRAVO-3'], 'BRAVO'); + expect(match).toStrictEqual('BRAVO-2'); + }); + + it('should return NONE if no match', () => { + const match = setup(['ALPHA-1', 'BRAVO-2', 'CHARLIE-3'], 'DELTA'); + expect(match).toStrictEqual('NONE'); + }); +}); + +describe('getPrefixStylesInSelection', () => { + it('should return all different matched styles for non-collapsed selection', () => { + // Let's select "World" + const editorState = getEditorState(6, 11); + const matches = getPrefixStylesInSelection(editorState, 'CUSTOM-WEIGHT'); + expect(matches).toIncludeSameMembers([ + 'CUSTOM-WEIGHT-700', + 'CUSTOM-WEIGHT-900', + ]); + }); + + it('should include NONE if at least one character did not match for non-collapsed selection', () => { + // Let's select "Hello World" + const editorState = getEditorState(0, 11); + const matches = getPrefixStylesInSelection(editorState, 'CUSTOM-WEIGHT'); + expect(matches).toIncludeSameMembers([ + 'NONE', + 'CUSTOM-WEIGHT-700', + 'CUSTOM-WEIGHT-900', + ]); + }); + + it('should return matched style for collapsed selection', () => { + // Let's place selection where the * is: "Hello W*orld" + const editorState = getEditorState(7); + const matches = getPrefixStylesInSelection(editorState, 'CUSTOM-WEIGHT'); + expect(matches).toIncludeSameMembers(['CUSTOM-WEIGHT-700']); + }); + + it('should return NONE if no match for collapsed selection', () => { + // Let's place selection where the * is: "H*ello World" + const editorState = getEditorState(1); + const matches = getPrefixStylesInSelection(editorState, 'CUSTOM-WEIGHT'); + expect(matches).toIncludeSameMembers(['NONE']); + }); +}); + +describe('togglePrefixStyle', () => { + describe('on collapsed selection', () => { + it('should toggle simple style off if selection is not inside matching style', () => { + // Let's place selection where the * is: "H*ello World" + // So, inside italic style + const editorState = getEditorState(1); + + // Verify that it doesn't include italic: + expect(editorState).not.toHaveStyleAtCursor('CUSTOM-ITALIC'); + + // Now toggle italic + const newState = togglePrefixStyle(editorState, 'CUSTOM-ITALIC'); + + // And verify that it now has italic + expect(newState).toHaveStyleAtCursor('CUSTOM-ITALIC'); + }); + + it('should toggle simple style off if selection is inside matching style', () => { + // Let's place selection where the * is: "Hel*lo World" + // So, inside italic style + const editorState = getEditorState(3); + + // Verify that it includes italic: + expect(editorState).toHaveStyleAtCursor('CUSTOM-ITALIC'); + + // Now toggle italic + const newState = togglePrefixStyle(editorState, 'CUSTOM-ITALIC'); + + //And verify that it doesn't have italic anymore + expect(newState).not.toHaveStyleAtCursor('CUSTOM-ITALIC'); + }); + + it('should do nothing when toggling simple style *on* in selection *with style*', () => { + // Let's place selection where the * is: "Hel*lo World" + // So, inside italic style + const editorState = getEditorState(3); + + // Verify that it already includes italic: + expect(editorState).toHaveStyleAtCursor('CUSTOM-ITALIC'); + + // Now toggle italic + const newState = togglePrefixStyle( + editorState, + 'CUSTOM-ITALIC', + () => true + ); + + // And verify that it still includes italic + expect(newState).toHaveStyleAtCursor('CUSTOM-ITALIC'); + }); + + it('should do nothing when toggling simple style *off* in selection *without style*', () => { + // Let's place selection where the * is: "H*ello World" + // So, inside italic style + const editorState = getEditorState(1); + + // Verify that it doesn't include italic: + expect(editorState).not.toHaveStyleAtCursor('CUSTOM-ITALIC'); + + // Now toggle italic + const newState = togglePrefixStyle( + editorState, + 'CUSTOM-ITALIC', + () => false + ); + + // And verify that it still doesn't have italic + expect(newState).not.toHaveStyleAtCursor('CUSTOM-ITALIC'); + }); + + it('should toggle complex style based on callbacks', () => { + // Let's place selection where the * is: "Hel*lo World" + // So, inside bold style + const editorState = getEditorState(3); + + // Verify that it has custom weight 700: + expect(editorState).toHaveStyleAtCursor('CUSTOM-WEIGHT-700'); + + // Now toggle according to callbacks: + const shouldSetStyle = jest.fn().mockImplementation(() => true); + const styleToSet = jest + .fn() + .mockImplementation(() => 'CUSTOM-WEIGHT-900'); + const newState = togglePrefixStyle( + editorState, + 'CUSTOM-WEIGHT', + shouldSetStyle, + styleToSet + ); + + // And verify that it now has given style + expect(newState).toHaveStyleAtCursor('CUSTOM-WEIGHT-900'); + + // And verify given callbacks have been invoked correctly + expect(shouldSetStyle).toHaveBeenCalledWith(['CUSTOM-WEIGHT-700']); + expect(styleToSet).toHaveBeenCalledWith(['CUSTOM-WEIGHT-700']); + }); + }); + + describe('on non-collapsed selection', () => { + it('should toggle simple style off if selection contains style throughout', () => { + // Let's place selection here: "Hel[lo] World" + // So, everything is underlined + const editorState = getEditorState(3, 5); + + // Verify that it is underline throughout: + expect(editorState).toHaveStyleInEntireSelection('CUSTOM-UNDERLINE'); + + // Now toggle underline + const newState = togglePrefixStyle(editorState, 'CUSTOM-UNDERLINE'); + + // And verify that it doesn't have underline anywhere + expect(newState).not.toHaveStyleInSelection('CUSTOM-UNDERLINE'); + }); + + it('should toggle simple style on if selection contains style somewhere but not everywhere', () => { + // Let's place selection here: "H[ello] World" + // So, some is underlined, some not + const editorState = getEditorState(1, 5); + + // Verify that it is underline somewhere, but not everywhere: + expect(editorState).not.toHaveStyleInEntireSelection('CUSTOM-UNDERLINE'); + expect(editorState).toHaveStyleInSelection('CUSTOM-UNDERLINE'); + + // Now toggle underline + const newState = togglePrefixStyle(editorState, 'CUSTOM-UNDERLINE'); + + // And verify that now has underline throughout + expect(newState).toHaveStyleInEntireSelection('CUSTOM-UNDERLINE'); + }); + + it('should toggle simple style on if selection does not contain style at all', () => { + // Let's place selection here: "[He]llo World" + // So, no underline at all + const editorState = getEditorState(0, 2); + + // Verify that it is underline nowhere: + expect(editorState).not.toHaveStyleInSelection('CUSTOM-UNDERLINE'); + + // Now toggle underline + const newState = togglePrefixStyle(editorState, 'CUSTOM-UNDERLINE'); + + // And verify that now has underline throughout + expect(newState).toHaveStyleInEntireSelection('CUSTOM-UNDERLINE'); + }); + + it('should toggle complex style according to callbacks', () => { + // Let's place selection here: "Hell[o Worl]d" + // So, some regular weight, some bold, some black + const editorState = getEditorState(4, 10); + + // Verify that it is both none, bold and black somewhere: + expect(editorState).toHaveStyleInSelection('NONE', 'CUSTOM-WEIGHT'); + expect(editorState).toHaveStyleInSelection( + 'CUSTOM-WEIGHT-700', + 'CUSTOM-WEIGHT' + ); + expect(editorState).toHaveStyleInSelection( + 'CUSTOM-WEIGHT-900', + 'CUSTOM-WEIGHT' + ); + + // Now toggle font weight according to callbacks + const shouldSetStyle = jest.fn().mockImplementation(() => true); + const styleToSet = jest + .fn() + .mockImplementation(() => 'CUSTOM-WEIGHT-300'); + const newState = togglePrefixStyle( + editorState, + 'CUSTOM-WEIGHT', + shouldSetStyle, + styleToSet + ); + + // And verify that now has new font weight throughout + expect(newState).toHaveStyleInEntireSelection( + 'CUSTOM-WEIGHT-300', + 'CUSTOM-WEIGHT' + ); + + // And verify given callbacks have been invoked correctly + expect(shouldSetStyle).toHaveBeenCalledWith([ + 'NONE', + 'CUSTOM-WEIGHT-700', + 'CUSTOM-WEIGHT-900', + ]); + expect(styleToSet).toHaveBeenCalledWith([ + 'NONE', + 'CUSTOM-WEIGHT-700', + 'CUSTOM-WEIGHT-900', + ]); + }); + }); +}); + +/* Editor state has this content: + * Hello World + * + * With 7 different sections with styles as follows: + * + * 1. "He" is unstyled + * 2. "l" is bold (700) and italic + * 3. "l" is bold (700), italic and underline + * 4. "o" is underline + * 5. " " is unstyled + * 6. "Wo" is bold (700) + * 7. "rld" is black (900) + */ +function getEditorState(selectionStart, selectionEnd = null) { + const raw = { + blocks: [ + { + key: '65t0d', + text: 'Hello world', + type: 'unstyled', + depth: 0, + inlineStyleRanges: [ + { offset: 2, length: 2, style: 'CUSTOM-WEIGHT-700' }, + { offset: 2, length: 2, style: 'CUSTOM-ITALIC' }, + { offset: 3, length: 2, style: 'CUSTOM-UNDERLINE' }, + { offset: 6, length: 2, style: 'CUSTOM-WEIGHT-700' }, + { offset: 8, length: 3, style: 'CUSTOM-WEIGHT-900' }, + ], + entityRanges: [], + data: {}, + }, + ], + entityMap: {}, + }; + const contentState = convertFromRaw(raw); + const unselectedState = EditorState.createWithContent(contentState); + const selection = new SelectionState({ + anchorKey: raw.blocks[0].key, + anchorOffset: selectionStart, + focusKey: raw.blocks[0].key, + focusOffset: selectionEnd ?? selectionStart, + }); + return EditorState.forceSelection(unselectedState, selection); +} diff --git a/assets/src/dashboard/components/navigationBar/components.js b/assets/src/edit-story/components/richText/useRichText.js similarity index 51% rename from assets/src/dashboard/components/navigationBar/components.js rename to assets/src/edit-story/components/richText/useRichText.js index b5f4ee6bc309..4a45e5f5d723 100644 --- a/assets/src/dashboard/components/navigationBar/components.js +++ b/assets/src/edit-story/components/richText/useRichText.js @@ -17,33 +17,15 @@ /** * External dependencies */ -import styled from 'styled-components'; +import { useContext } from 'react'; /** * Internal dependencies */ -import Button from '../button'; +import RichTextContext from './context'; -export const Nav = styled.nav` - ${({ theme }) => ` - position: relative; - justify-content: space-between; - align-items: center; - border-bottom: ${theme.borders.gray50}; - display: flex; - flex-direction: row; - width: 100%; - height: ${theme.navBar.height}px; - padding: ${theme.pageGutter.small.desktop}px; - margin-bottom: 40px; +function useRichText() { + return useContext(RichTextContext); +} - @media ${theme.breakpoint.smallDisplayPhone} { - flex-wrap: wrap; - padding: 0 ${theme.pageGutter.small.min}px; - } - `} -`; - -export const ActionLink = styled(Button)` - padding: 0 24px; -`; +export default useRichText; diff --git a/assets/src/edit-story/components/richText/useSelectionManipulation.js b/assets/src/edit-story/components/richText/useSelectionManipulation.js new file mode 100644 index 000000000000..9f8c933544e1 --- /dev/null +++ b/assets/src/edit-story/components/richText/useSelectionManipulation.js @@ -0,0 +1,84 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { useCallback, useRef, useEffect, useMemo } from 'react'; +import { EditorState } from 'draft-js'; + +/** + * Internal dependencies + */ +import formatters from './formatters'; + +function useSelectionManipulation(editorState, setEditorState) { + const lastKnownState = useRef(null); + const lastKnownSelection = useRef(null); + useEffect(() => { + lastKnownState.current = editorState; + if (!editorState) { + lastKnownSelection.current = null; + } else if (editorState.getSelection().hasFocus) { + lastKnownSelection.current = editorState.getSelection(); + } + }, [editorState]); + + const updateWhileUnfocused = useCallback( + (updater, shouldForceFocus = true) => { + const oldState = lastKnownState.current; + const selection = lastKnownSelection.current; + const workingState = shouldForceFocus + ? EditorState.acceptSelection(oldState, selection) + : oldState; + const newState = updater(workingState); + setEditorState(newState); + }, + [setEditorState] + ); + + const getSetterName = useCallback( + (setterName) => `${setterName}InSelection`, + [] + ); + + const getSetterCallback = useCallback( + (setter, autoFocus) => (...args) => + updateWhileUnfocused((state) => setter(state, ...args), autoFocus), + [updateWhileUnfocused] + ); + + const selectionFormatters = useMemo( + () => + formatters.reduce( + (aggr, { setters, autoFocus }) => ({ + ...aggr, + ...Object.fromEntries( + Object.entries(setters).map(([key, setter]) => [ + getSetterName(key), + getSetterCallback(setter, autoFocus), + ]) + ), + }), + {} + ), + [getSetterName, getSetterCallback] + ); + + return selectionFormatters; +} + +export default useSelectionManipulation; diff --git a/assets/src/edit-story/components/richText/util.js b/assets/src/edit-story/components/richText/util.js new file mode 100644 index 000000000000..88e0ee7a9b0c --- /dev/null +++ b/assets/src/edit-story/components/richText/util.js @@ -0,0 +1,109 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * External dependencies + */ +import { SelectionState } from 'draft-js'; +import { filterEditorState } from 'draftjs-filters'; + +/** + * Internal dependencies + */ +import weightFormatter from './formatters/weight'; +import italicFormatter from './formatters/italic'; +import underlineFormatter from './formatters/underline'; + +export function getFilteredState(editorState, oldEditorState) { + const shouldFilterPaste = + oldEditorState.getCurrentContent() !== editorState.getCurrentContent() && + editorState.getLastChangeType() === 'insert-fragment'; + + if (!shouldFilterPaste) { + return editorState; + } + + return filterEditorState( + { + blocks: [], + styles: ['BOLD', 'ITALIC', 'UNDERLINE'], + entities: [], + maxNesting: 1, + whitespacedCharacters: [], + }, + editorState + ); +} + +function getStateFromCommmand(command, oldEditorState) { + switch (command) { + case 'bold': + return weightFormatter.setters.toggleBold(oldEditorState); + + case 'italic': + return italicFormatter.setters.toggleItalic(oldEditorState); + + case 'underline': + return underlineFormatter.setters.toggleUnderline(oldEditorState); + + default: + return null; + } +} + +export const getHandleKeyCommandFromState = (setEditorState) => ( + command, + currentEditorState +) => { + const newEditorState = getStateFromCommmand(command, currentEditorState); + if (newEditorState) { + setEditorState(newEditorState); + return 'handled'; + } + return 'not-handled'; +}; + +export function getSelectionForAll(content) { + const firstBlock = content.getFirstBlock(); + const lastBlock = content.getLastBlock(); + return new SelectionState({ + anchorKey: firstBlock.getKey(), + anchorOffset: 0, + focusKey: lastBlock.getKey(), + focusOffset: lastBlock.getLength(), + }); +} + +export function getSelectionForOffset(content, offset) { + const blocks = content.getBlocksAsArray(); + let countdown = offset; + for (let i = 0; i < blocks.length && countdown >= 0; i++) { + const block = blocks[i]; + const length = block.getLength(); + if (countdown <= length) { + const selection = new SelectionState({ + anchorKey: block.getKey(), + anchorOffset: countdown, + focusKey: block.getKey(), + focusOffset: countdown, + }); + return selection; + } + // +1 char for the delimiter. + countdown -= length + 1; + } + return null; +} diff --git a/assets/src/edit-story/components/workspace/index.js b/assets/src/edit-story/components/workspace/index.js index 0246c257f31c..3c1e2630e645 100644 --- a/assets/src/edit-story/components/workspace/index.js +++ b/assets/src/edit-story/components/workspace/index.js @@ -21,21 +21,24 @@ import Inspector from '../inspector'; import Canvas from '../canvas'; import { SidebarProvider } from '../sidebar'; import CanvasProvider from '../canvas/canvasProvider'; +import RichTextProvider from '../richText/provider'; import { WorkspaceLayout, CanvasArea, InspectorArea } from './layout'; function Workspace() { return ( - - - - - - - - - - + + + + + + + + + + + + ); } diff --git a/assets/src/edit-story/constants.js b/assets/src/edit-story/constants.js index a01fea7c9bda..873bc0846d3d 100644 --- a/assets/src/edit-story/constants.js +++ b/assets/src/edit-story/constants.js @@ -28,10 +28,16 @@ export const PAGE_NAV_PADDING = 60; export const PAGE_NAV_BUTTON_WIDTH = 40; export const PAGE_NAV_WIDTH = PAGE_NAV_PADDING + PAGE_NAV_BUTTON_WIDTH; +export const PAGE_RATIO = 2 / 3; export const PAGE_WIDTH = 440; export const PAGE_HEIGHT = 660; -export const DEFAULT_EDITOR_PAGE_WIDTH = PAGE_WIDTH; -export const DEFAULT_EDITOR_PAGE_HEIGHT = PAGE_HEIGHT; + +export const ALLOWED_EDITOR_PAGE_WIDTHS = [440, 280, 240]; + +export const FULLBLEED_RATIO = 9 / 16; + +export const COLOR_PRESETS_PER_ROW = 6; +export const STYLE_PRESETS_PER_ROW = 3; // Default device pixel ratio. export const DEFAULT_DPR = 0.5; diff --git a/assets/src/edit-story/elements/media/index.js b/assets/src/edit-story/elements/media/index.js index 4e6e75007d2f..16e474674467 100644 --- a/assets/src/edit-story/elements/media/index.js +++ b/assets/src/edit-story/elements/media/index.js @@ -81,6 +81,5 @@ export const resizeRules = { export const MEDIA_PANELS = [ PanelTypes.BACKGROUND_SIZE_POSITION, PanelTypes.LAYER_STYLE, - PanelTypes.BACKGROUND_DISPLAY, PanelTypes.SIZE_POSITION, ]; diff --git a/assets/src/edit-story/elements/shared/index.js b/assets/src/edit-story/elements/shared/index.js index 78c42cb040b7..9d98990d286c 100644 --- a/assets/src/edit-story/elements/shared/index.js +++ b/assets/src/edit-story/elements/shared/index.js @@ -54,16 +54,13 @@ export const elementWithBackgroundColor = css` convertToCSS(generatePatternStyles(backgroundColor))}; `; -export const elementWithFontColor = css` - ${({ color }) => convertToCSS(generatePatternStyles(color, 'color'))}; -`; - export const elementWithFont = css` white-space: pre-wrap; font-family: ${({ font }) => font?.family}; font-style: ${({ fontStyle }) => fontStyle}; font-size: ${({ fontSize }) => fontSize}px; font-weight: ${({ fontWeight }) => fontWeight}; + color: #000000; `; /** @@ -73,9 +70,7 @@ export const elementWithTextParagraphStyle = css` margin: ${({ margin }) => margin || 0}; padding: ${({ padding }) => padding || 0}; line-height: ${({ lineHeight }) => lineHeight}; - letter-spacing: ${({ letterSpacing }) => letterSpacing}; text-align: ${({ textAlign }) => textAlign}; - text-decoration: ${({ textDecoration }) => textDecoration}; `; export const SHARED_DEFAULT_ATTRIBUTES = { diff --git a/assets/src/edit-story/elements/text/display.js b/assets/src/edit-story/elements/text/display.js index 59018b6627b9..4c6be2a2757b 100644 --- a/assets/src/edit-story/elements/text/display.js +++ b/assets/src/edit-story/elements/text/display.js @@ -18,7 +18,7 @@ * External dependencies */ import styled from 'styled-components'; -import { useEffect, useRef } from 'react'; +import { useEffect, useRef, useMemo } from 'react'; /** * Internal dependencies @@ -29,22 +29,21 @@ import { elementFillContent, elementWithFont, elementWithBackgroundColor, - elementWithFontColor, elementWithTextParagraphStyle, } from '../shared'; import StoryPropTypes from '../../types'; import { BACKGROUND_TEXT_MODE } from '../../constants'; import { useTransformHandler } from '../../components/transform'; import { - draftMarkupToContent, - getHighlightLineheight, - generateParagraphTextStyle, -} from './util'; + getHTMLFormatters, + getHTMLInfo, +} from '../../components/richText/htmlManipulation'; +import createSolid from '../../utils/createSolid'; +import { getHighlightLineheight, generateParagraphTextStyle } from './util'; const HighlightWrapperElement = styled.div` ${elementFillContent} ${elementWithFont} - ${elementWithFontColor} ${elementWithTextParagraphStyle} line-height: ${({ lineHeight, verticalPadding }) => getHighlightLineheight(lineHeight, verticalPadding)}; @@ -90,20 +89,11 @@ const FillElement = styled.p` ${elementFillContent} ${elementWithFont} ${elementWithBackgroundColor} - ${elementWithFontColor} ${elementWithTextParagraphStyle} `; function TextDisplay({ - element: { - id, - bold, - content, - color, - backgroundColor, - backgroundTextMode, - ...rest - }, + element: { id, content, backgroundColor, backgroundTextMode, ...rest }, box: { width }, }) { const ref = useRef(null); @@ -112,10 +102,11 @@ function TextDisplay({ actions: { dataToEditorY, dataToEditorX }, } = useUnits(); - const { font, fontWeight, fontStyle } = rest; + const { font } = rest; + const isItalic = useMemo(() => getHTMLInfo(content).isItalic, [content]); + const fontWeight = useMemo(() => getHTMLInfo(content).fontWeight, [content]); const props = { - color, font, ...(backgroundTextMode === BACKGROUND_TEXT_MODE.NONE ? {} @@ -128,16 +119,15 @@ function TextDisplay({ const { actions: { maybeEnqueueFontStyle }, } = useFont(); - useEffect(() => { maybeEnqueueFontStyle([ { font, fontWeight, - fontStyle, + isItalic, }, ]); - }, [font, fontWeight, fontStyle, maybeEnqueueFontStyle]); + }, [font, fontWeight, isItalic, maybeEnqueueFontStyle]); useTransformHandler(id, (transform) => { const target = ref.current; @@ -147,6 +137,13 @@ function TextDisplay({ : ''; }); + // Setting the text color of the entire block to black essentially removes all inline + // color styling allowing us to apply transparent to all of them. + const contentWithoutColor = useMemo( + () => getHTMLFormatters().setColor(content, createSolid(0, 0, 0)), + [content] + ); + if (backgroundTextMode === BACKGROUND_TEXT_MODE.HIGHLIGHT) { return ( @@ -155,7 +152,7 @@ function TextDisplay({ @@ -165,7 +162,7 @@ function TextDisplay({ @@ -178,7 +175,7 @@ function TextDisplay({ diff --git a/assets/src/edit-story/elements/text/edit.js b/assets/src/edit-story/elements/text/edit.js index dd3f22859259..3aa58487da18 100644 --- a/assets/src/edit-story/elements/text/edit.js +++ b/assets/src/edit-story/elements/text/edit.js @@ -18,46 +18,33 @@ * External dependencies */ import styled from 'styled-components'; -import { Editor, EditorState } from 'draft-js'; -import { stateFromHTML } from 'draft-js-import-html'; -import { stateToHTML } from 'draft-js-export-html'; import { - useMemo, - useState, useEffect, useLayoutEffect, useRef, useCallback, + useMemo, } from 'react'; /** * Internal dependencies */ import { useStory, useFont } from '../../app'; -import { useCanvas } from '../../components/canvas'; +import RichTextEditor from '../../components/richText/editor'; +import { getHTMLInfo } from '../../components/richText/htmlManipulation'; import { useUnits } from '../../units'; import { elementFillContent, elementWithFont, elementWithBackgroundColor, - elementWithFontColor, elementWithTextParagraphStyle, } from '../shared'; import StoryPropTypes from '../../types'; import { BACKGROUND_TEXT_MODE } from '../../constants'; -import useFocusOut from '../../utils/useFocusOut'; import useUnmount from '../../utils/useUnmount'; import createSolid from '../../utils/createSolid'; import calcRotatedResizeOffset from '../../utils/calcRotatedResizeOffset'; -import { - draftMarkupToContent, - getFilteredState, - getHandleKeyCommand, - getSelectionForAll, - getSelectionForOffset, - generateParagraphTextStyle, - getHighlightLineheight, -} from './util'; +import { generateParagraphTextStyle, getHighlightLineheight } from './util'; // Wrapper bounds the text editor within the element bounds. The resize // logic updates the height of this element to show the new height based @@ -85,7 +72,6 @@ const TextBox = styled.div` ${elementWithFont} ${elementWithTextParagraphStyle} ${elementWithBackgroundColor} - ${elementWithFontColor} opacity: ${({ opacity }) => (opacity ? opacity / 100 : null)}; position: absolute; @@ -97,9 +83,7 @@ const TextBox = styled.div` function TextEdit({ element: { id, - bold, content, - color, backgroundColor, backgroundTextMode, opacity, @@ -108,14 +92,15 @@ function TextEdit({ }, box: { x, y, height, rotationAngle }, }) { - const { font, fontWeight, fontStyle, fontSize } = rest; + const { font, fontSize } = rest; + const isItalic = useMemo(() => getHTMLInfo(content).isItalic, [content]); + const fontWeight = useMemo(() => getHTMLInfo(content).fontWeight, [content]); const { actions: { dataToEditorX, dataToEditorY, editorToDataX, editorToDataY }, } = useUnits(); const textProps = { ...generateParagraphTextStyle(rest, dataToEditorX, dataToEditorY), - color, font, backgroundColor, opacity, @@ -124,102 +109,53 @@ function TextEdit({ rest.lineHeight, dataToEditorX(rest.padding?.vertical || 0) ), - color: createSolid(0, 0, 0), backgroundColor: createSolid(255, 255, 255), }), ...(backgroundTextMode === BACKGROUND_TEXT_MODE.NONE && { backgroundColor: null, }), }; - const wrapperRef = useRef(null); - const textBoxRef = useRef(null); - const editorRef = useRef(null); const { actions: { maybeEnqueueFontStyle }, } = useFont(); const { actions: { updateElementById }, } = useStory(); - const { - state: { editingElementState }, - } = useCanvas(); + const setProperties = useCallback( (properties) => updateElementById({ elementId: id, properties }), [id, updateElementById] ); - const { offset, clearContent, selectAll } = editingElementState || {}; - const initialState = useMemo(() => { - const contentWithBreaks = (content || '') - // Re-insert manual line-breaks for empty lines - .replace(/\n(?=\n)/g, '\n
') - .split('\n') - .map((s) => { - return `

${draftMarkupToContent(s, bold)}

`; - }) - .join(''); - let state = EditorState.createWithContent(stateFromHTML(contentWithBreaks)); - if (clearContent) { - // If `clearContent` is specified, push the update to clear content so that - // it can be undone. - state = EditorState.push(state, stateFromHTML(''), 'remove-range'); - } - let selection; - if (selectAll) { - selection = getSelectionForAll(state.getCurrentContent()); - } else if (offset) { - selection = getSelectionForOffset(state.getCurrentContent(), offset); - } - if (selection) { - state = EditorState.forceSelection(state, selection); - } - return state; - }, [content, clearContent, selectAll, offset, bold]); - const [editorState, setEditorState] = useState(initialState); + const wrapperRef = useRef(null); + const textBoxRef = useRef(null); + const editorRef = useRef(null); + const contentRef = useRef(); const editorHeightRef = useRef(0); - // This is to allow the finalizing useEffect to *not* depend on editorState, - // as would otherwise be a lint error. - const lastKnownState = useRef(null); - - // This filters out illegal content (see `getFilteredState`) - // on paste and updates state accordingly. - // Furthermore it also sets initial selection if relevant. - const updateEditorState = useCallback( - (newEditorState) => { - const filteredState = getFilteredState(newEditorState, editorState); - lastKnownState.current = filteredState.getCurrentContent(); - setEditorState(filteredState); - }, - [editorState] - ); - - // Handle basic key commands such as bold, italic and underscore. - const handleKeyCommand = getHandleKeyCommand(updateEditorState); - // Make sure to allow the user to click in the text box while working on the text. const onClick = (evt) => { const editor = editorRef.current; // Refocus the editor if the container outside it is clicked. - if (!editor.editorContainer.contains(evt.target)) { + if (!editor.getNode().contains(evt.target)) { editor.focus(); } evt.stopPropagation(); }; + // Set focus when initially rendered. + useLayoutEffect(() => { + if (editorRef.current) { + editorRef.current.focus(); + } + }, []); + const updateContent = useCallback(() => { - const newState = lastKnownState.current; const newHeight = editorHeightRef.current; wrapperRef.current.style.height = ''; - if (newState) { + if (contentRef.current) { // Remove manual line breaks and remember to trim any trailing non-breaking space. - const properties = { - content: stateToHTML(lastKnownState.current, { - defaultBlockTag: null, - }) - .replace(/
/g, '') - .replace(/ $/, ''), - }; + const properties = { content: contentRef.current }; // Recalculate the new height and offset. if (newHeight) { const [dx, dy] = calcRotatedResizeOffset( @@ -245,43 +181,50 @@ function TextEdit({ y, ]); - // Update content for element on focus out. - useFocusOut(textBoxRef, updateContent, [updateContent]); - // Update content for element on unmount. useUnmount(updateContent); - // Set focus when initially rendered. - useLayoutEffect(() => { - editorRef.current.focus(); - }, []); - - // Remeasure the height on each content update. - useEffect(() => { + // A function to remeasure height + const handleResize = useCallback(() => { const wrapper = wrapperRef.current; const textBox = textBoxRef.current; editorHeightRef.current = textBox.offsetHeight; wrapper.style.height = `${editorHeightRef.current}px`; - }, [editorState, elementHeight, font, fontWeight, fontStyle, fontSize]); + }, []); + // Invoke on each content update. + const handleUpdate = useCallback( + (newContent) => { + contentRef.current = newContent; + handleResize(); + }, + [handleResize] + ); + // Also invoke if the raw element height ever changes + useEffect(handleResize, [ + elementHeight, + font, + fontWeight, + isItalic, + fontSize, + ]); useEffect(() => { maybeEnqueueFontStyle([ { font, fontWeight, - fontStyle, + isItalic, }, ]); - }, [font, fontWeight, fontStyle, maybeEnqueueFontStyle]); + }, [font, fontWeight, isItalic, maybeEnqueueFontStyle]); return ( - diff --git a/assets/src/edit-story/elements/text/index.js b/assets/src/edit-story/elements/text/index.js index 2cdc0839f100..80612d0423b1 100644 --- a/assets/src/edit-story/elements/text/index.js +++ b/assets/src/edit-story/elements/text/index.js @@ -34,17 +34,11 @@ export { default as updateForResizeEvent } from './updateForResizeEvent'; export const defaultAttributes = { ...SHARED_DEFAULT_ATTRIBUTES, backgroundTextMode: BACKGROUND_TEXT_MODE.NONE, - bold: false, font: TEXT_ELEMENT_DEFAULT_FONT, - fontWeight: 400, fontSize: 36, - fontStyle: 'normal', backgroundColor: createSolid(196, 196, 196), - color: createSolid(0, 0, 0), - letterSpacing: 0, lineHeight: 1.3, textAlign: 'initial', - textDecoration: 'none', padding: { vertical: 0, horizontal: 0, diff --git a/assets/src/edit-story/elements/text/output.js b/assets/src/edit-story/elements/text/output.js index fbe8c29eb7af..94504f360e26 100644 --- a/assets/src/edit-story/elements/text/output.js +++ b/assets/src/edit-story/elements/text/output.js @@ -18,33 +18,24 @@ * External dependencies */ import PropTypes from 'prop-types'; +import { useMemo } from 'react'; /** * Internal dependencies */ import StoryPropTypes from '../../types'; import generatePatternStyles from '../../utils/generatePatternStyles'; +import { getHTMLFormatters } from '../../components/richText/htmlManipulation'; +import createSolid from '../../utils/createSolid'; import { dataToEditorX, dataToEditorY } from '../../units'; import { BACKGROUND_TEXT_MODE } from '../../constants'; -import { - draftMarkupToContent, - generateParagraphTextStyle, - getHighlightLineheight, -} from './util'; +import { generateParagraphTextStyle, getHighlightLineheight } from './util'; /** * Renders DOM for the text output based on the provided unit converters. */ export function TextOutputWithUnits({ - element: { - bold, - content, - color, - backgroundColor, - backgroundTextMode, - padding, - ...rest - }, + element: { content, backgroundColor, backgroundTextMode, padding, ...rest }, dataToStyleX, dataToStyleY, dataToFontSizeY, @@ -78,8 +69,8 @@ export function TextOutputWithUnits({ dataToStyleY, dataToFontSizeY ), - ...generatePatternStyles(color, 'color'), ...bgColor, + color: '#000000', padding: `${paddingStyles.vertical} ${paddingStyles.horizontal}`, }; @@ -138,6 +129,13 @@ export function TextOutputWithUnits({ background: 'none', }; + // Setting the text color of the entire block to black essentially removes all inline + // color styling allowing us to apply transparent to all of them. + const contentWithoutColor = useMemo( + () => getHTMLFormatters().setColor(content, createSolid(0, 0, 0)), + [content] + ); + if (backgroundTextMode === BACKGROUND_TEXT_MODE.HIGHLIGHT) { return ( <> @@ -146,7 +144,7 @@ export function TextOutputWithUnits({ @@ -156,7 +154,7 @@ export function TextOutputWithUnits({ @@ -169,7 +167,7 @@ export function TextOutputWithUnits({

); } diff --git a/assets/src/edit-story/elements/text/test/output.js b/assets/src/edit-story/elements/text/test/output.js index 394543a23e9a..3cf1f71b06db 100644 --- a/assets/src/edit-story/elements/text/test/output.js +++ b/assets/src/edit-story/elements/text/test/output.js @@ -38,14 +38,6 @@ describe('TextOutput', () => { const element = { id: '123', content: 'Content', - color: { - color: { - r: 255, - g: 255, - b: 255, - a: 0.5, - }, - }, backgroundColor: { color: { r: 255, @@ -58,9 +50,7 @@ describe('TextOutput', () => { family: 'Roboto', }, fontSize: 16, - letterSpacing: 1.3, textAlign: 'left', - textDecoration: 'none', type: 'text', x: 10, y: 10, @@ -87,33 +77,21 @@ describe('TextOutput', () => { whiteSpace: 'pre-wrap', padding: '0% 0%', margin: '0px', - color: 'rgba(255, 255, 255, 0.5)', backgroundColor: 'rgba(255, 0, 0, 0.3)', fontSize: '0.242424em', - letterSpacing: '1.3em', textAlign: 'left', - textDecoration: 'none', }); }); it('should convert padding to percent of width', () => { const element = { id: '123', - color: { - color: { - r: 255, - g: 255, - b: 255, - }, - }, content: 'Content', font: { family: 'Roboto', }, fontSize: 16, - letterSpacing: 1.3, textAlign: 'left', - textDecoration: 'none', type: 'text', x: 10, y: 10, @@ -140,55 +118,6 @@ describe('TextOutput', () => { }); }); - it('should apply tags if bold', () => { - const element = { - id: '123', - content: 'Content', - color: { - color: { - r: 255, - g: 255, - b: 255, - a: 0.5, - }, - }, - backgroundColor: { - color: { - r: 255, - g: 0, - b: 0, - a: 0.3, - }, - }, - font: { - family: 'Roboto', - }, - fontSize: 16, - letterSpacing: 1.3, - textAlign: 'left', - textDecoration: 'none', - type: 'text', - x: 10, - y: 10, - width: 50, - height: 50, - rotationAngle: 0, - padding: { - vertical: 0, - horizontal: 0, - }, - bold: true, - }; - - const output = renderViaString( - - ); - expect(output.innerHTML).toBe('Content'); - }); - it('should wrap font-family into quotes', () => { const element = { id: '123', @@ -207,7 +136,6 @@ describe('TextOutput', () => { vertical: 0, horizontal: 0, }, - color: { type: 'solid', color: { r: 255, g: 255, b: 255 } }, }; const output = renderViaString( @@ -238,7 +166,6 @@ describe('TextOutput', () => { vertical: 0, horizontal: 0, }, - color: { type: 'solid', color: { r: 255, g: 255, b: 255 } }, }; const output = renderViaString( diff --git a/assets/src/edit-story/elements/text/test/util.js b/assets/src/edit-story/elements/text/test/util.js index 8d4ffb298b7b..1b286f6a6ee8 100644 --- a/assets/src/edit-story/elements/text/test/util.js +++ b/assets/src/edit-story/elements/text/test/util.js @@ -17,7 +17,7 @@ /** * Internal dependencies */ -import { draftMarkupToContent, generateFontFamily } from '../util'; +import { generateFontFamily } from '../util'; describe('Text/util', () => { describe('Text/util/generateFontFamily', () => { @@ -46,30 +46,4 @@ describe('Text/util', () => { ).toStrictEqual(expected); }); }); - - describe('Text/util/draftMarkupToContent', () => { - it('should return valid HTML for content', () => { - const input = 'Hello World!'; - expect(draftMarkupToContent(input, false)).toStrictEqual( - 'Hello World!' - ); - - const nestedInput = 'Hello World, again!'; - expect(draftMarkupToContent(nestedInput, false)).toStrictEqual( - 'Hello World, again!' - ); - - const invalidInput = 'Hello world { + it('should remove isFullbleedBackground', () => { + expect( + isFullbleedDeprecate({ + _test: 'story', + pages: [ + { + _test: 'page1', + elements: [ + { + _test: 'element1', + type: 'square', + x: 10, + y: 20, + width: 100, + height: 200, + isFullbleedBackground: true, + }, + { + _test: 'element2', + type: 'image', + resource: { + type: 'image', + mimeType: 'image/png', + src: 'https://example.com/image.png', + width: 265, + height: 527, + }, + x: 29, + y: 55, + width: 265, + height: 527, + fontSize: 58, + isFullbleedBackground: false, + }, + ], + }, + { + _test: 'page2', + elements: [ + { + _test: 'element3', + type: 'video', + resource: { + type: 'video', + src: 'https://example.com/video.mp4', + poster: 'https://example.com/image.png', + posterId: 100002, + id: 102113, + mimeType: 'video/mp4', + width: 265, + height: 527, + }, + x: 29, + y: 55, + width: 265, + height: 527, + fontSize: 58, + isFullbleedBackground: true, + }, + ], + }, + ], + }) + ).toStrictEqual({ + _test: 'story', + pages: [ + { + _test: 'page1', + elements: [ + { + _test: 'element1', + type: 'square', + x: 10, + y: 20, + width: 100, + height: 200, + }, + { + _test: 'element2', + type: 'image', + resource: { + type: 'image', + mimeType: 'image/png', + src: 'https://example.com/image.png', + width: 265, + height: 527, + }, + x: 29, + y: 55, + width: 265, + height: 527, + fontSize: 58, + }, + ], + }, + { + _test: 'page2', + elements: [ + { + _test: 'element3', + type: 'video', + resource: { + type: 'video', + src: 'https://example.com/video.mp4', + poster: 'https://example.com/image.png', + posterId: 100002, + id: 102113, + mimeType: 'video/mp4', + width: 265, + height: 527, + }, + x: 29, + y: 55, + width: 265, + height: 527, + fontSize: 58, + }, + ], + }, + ], + }); + }); +}); diff --git a/assets/src/edit-story/migration/migrations/test/v0017_inlineTextProperties.js b/assets/src/edit-story/migration/migrations/test/v0017_inlineTextProperties.js new file mode 100644 index 000000000000..a80dfd8d29b9 --- /dev/null +++ b/assets/src/edit-story/migration/migrations/test/v0017_inlineTextProperties.js @@ -0,0 +1,369 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import inlineTextProperties from '../v0017_inlineTextProperties'; + +function convert(content, properties) { + const converted = inlineTextProperties({ + pages: [{ elements: [{ type: 'text', content, ...properties }] }], + }); + return converted.pages[0].elements[0].content; +} + +describe('inlineTextProperties', () => { + describe('should parse all text elements', () => { + it('should ignore non-text elements', () => { + expect( + inlineTextProperties({ + pages: [ + { + elements: [ + { + type: 'square', + bold: true, + }, + { + type: 'image', + content: 'Horse', + color: 'red', + }, + ], + }, + ], + }) + ).toStrictEqual({ + pages: [ + { + elements: [ + { + type: 'square', + bold: true, + }, + { + type: 'image', + content: 'Horse', + color: 'red', + }, + ], + }, + ], + }); + }); + + it('should parse multiple text elements on multiple pages', () => { + expect( + inlineTextProperties({ + pages: [ + { + elements: [ + { + type: 'text', + bold: true, + content: 'Hello', + }, + { + type: 'text', + textDecoration: 'underline', + content: 'Hello', + }, + ], + }, + { + elements: [ + { + type: 'text', + fontStyle: 'italic', + content: 'Hello', + }, + { + type: 'text', + fontWeight: 300, + content: 'Hello', + }, + ], + }, + ], + }) + ).toStrictEqual({ + pages: [ + { + elements: [ + { + type: 'text', + content: 'Hello', + }, + { + type: 'text', + content: + 'Hello', + }, + ], + }, + { + elements: [ + { + type: 'text', + content: 'Hello', + }, + { + type: 'text', + content: 'Hello', + }, + ], + }, + ], + }); + }); + + it('should remove all deprecated properties', () => { + const res = inlineTextProperties({ + pages: [ + { + elements: [ + { + type: 'text', + bold: true, + fontWeight: 300, + fontStyle: 'italic', + textDecoration: 'underline', + letterSpacing: 5, + color: { color: { r: 255, g: 0, b: 0 } }, + content: 'Hello', + }, + ], + }, + ], + }); + + const convertedElementKeys = res.pages[0].elements[0]; + expect(convertedElementKeys).toContainKey('content'); + expect(convertedElementKeys).not.toContainKeys([ + 'bold', + 'fontWeight', + 'fontStyle', + 'textDecoration', + 'letterSpacing', + 'color', + ]); + }); + }); + + it('should convert inline elements', () => { + const original = ` + Lorem + ipsum + dolor + sit + amet, + + consectetur + adipiscing + elit + . + `; + const converted = convert(original); + const expected = ` + Lorem + ipsum + dolor + sit + amet, + + consectetur + adipiscing + elit + . + `; + expect(converted).toStrictEqual(expected); + }); + + it('should convert nested elements', () => { + const original = ` + Lorem + ipsum + dolor + sit + amet, + + consectetur + adipiscing + elit + . + `; + const converted = convert(original); + const expected = ` + Lorem + ipsum + dolor + sit + amet, + + consectetur + adipiscing + elit + . + `; + expect(converted).toStrictEqual(expected); + }); + describe('should correctly interpret bold properties', () => { + it('should correctly interpret bold property and ignore inline elements', () => { + const original = 'Lorem ipsum'; + const properties = { bold: true }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should correctly interpret font weight property and ignore inline elements', () => { + const original = 'Lorem ipsum'; + const properties = { fontWeight: 300 }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should ignore font weight set to 400', () => { + const original = 'Lorem ipsum'; + const properties = { fontWeight: 400 }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should use font weight over bold when both set if not 400', () => { + const original = 'Lorem ipsum'; + const properties = { fontWeight: 300, bold: true }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should do nothing globally if font weight is 400 and bold is false', () => { + const original = 'Lorem ipsum'; + const properties = { fontWeight: 400, bold: false }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + }); + + describe('should correctly interpret italic property', () => { + it('should correctly interpret font style property and ignore inline elements', () => { + const original = 'Lorem ipsum'; + const properties = { fontStyle: 'italic' }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should ignore font style set to anything but "italic"', () => { + const original = 'Lorem ipsum'; + const properties = { fontStyle: 'oblique' }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + }); + + describe('should correctly interpret underline property', () => { + it('should correctly interpret text decoration property and ignore inline elements', () => { + const original = 'Lorem ipsum'; + const properties = { textDecoration: 'underline' }; + const converted = convert(original, properties); + const expected = + 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should ignore text decoration set to anything but "underline"', () => { + const original = 'Lorem ipsum'; + const properties = { textDecoration: 'line-through' }; + const converted = convert(original, properties); + const expected = + 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + }); + + it('should correctly inline color property', () => { + const original = 'Lorem ipsum'; + const properties = { color: { color: { r: 255, g: 0, b: 0, a: 0.5 } } }; + const converted = convert(original, properties); + const expected = + 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should correctly inline letter spacing property', () => { + const original = 'Lorem ipsum'; + const properties = { letterSpacing: 20 }; + const converted = convert(original, properties); + const expected = 'Lorem ipsum'; + expect(converted).toStrictEqual(expected); + }); + + it('should all work correctly together', () => { + const original = ` + Lorem + ipsum + dolor + sit + amet, + consectetur + adipiscing + elit. + `; + // Here we have global italic, color and letter spacing, but no global font weight or underline. + const properties = { + letterSpacing: 20, + bold: false, + fontWeight: 400, + fontStyle: 'italic', + color: { color: { r: 255, g: 0, b: 0 } }, + textDecoration: 'line-through', + }; + const converted = convert(original, properties); + const expected = ` + + + + Lorem + ipsum + dolor + sit + amet, + consectetur + adipiscing + elit. + + + + `; + expect(uniformWhitespace(converted)).toStrictEqual( + uniformWhitespace(expected) + ); + + function uniformWhitespace(str) { + return str + .replace(/^|$/g, ' ') // insert single space front and back + .replace(/> <') // and insert space between tags + .replace(/\s+/g, ' '); // and then collapse all multi-whitespace to single space + } + }); +}); diff --git a/assets/src/edit-story/migration/migrations/v0016_isFullbleedDeprecate.js b/assets/src/edit-story/migration/migrations/v0016_isFullbleedDeprecate.js new file mode 100644 index 000000000000..40339e4508b9 --- /dev/null +++ b/assets/src/edit-story/migration/migrations/v0016_isFullbleedDeprecate.js @@ -0,0 +1,38 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function isFullbleedDeprecate({ pages, ...rest }) { + return { + pages: pages.map(reducePage), + ...rest, + }; +} + +function reducePage({ elements, ...rest }) { + return { + elements: elements.map(updateElement), + ...rest, + }; +} + +function updateElement(element) { + if (typeof element.isFullbleedBackground !== 'undefined') { + delete element.isFullbleedBackground; + } + return element; +} + +export default isFullbleedDeprecate; diff --git a/assets/src/edit-story/migration/migrations/v0017_inlineTextProperties.js b/assets/src/edit-story/migration/migrations/v0017_inlineTextProperties.js new file mode 100644 index 000000000000..5389e284a953 --- /dev/null +++ b/assets/src/edit-story/migration/migrations/v0017_inlineTextProperties.js @@ -0,0 +1,150 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +function inlineTextProperties({ pages, ...rest }) { + return { + pages: pages.map(reducePage), + ...rest, + }; +} + +function reducePage({ elements, ...rest }) { + return { + elements: elements.map(updateElement), + ...rest, + }; +} + +function updateElement(element) { + if (element.type === 'text') { + return updateTextContent(element); + } + + return element; +} + +function updateTextContent({ + bold, + fontWeight, + fontStyle, + textDecoration, + letterSpacing, + color, + content, + ...rest +}) { + // We use an array to chain all the converters more nicely + const convertedContent = [content] + .map((c) => convertInlineBold(c, bold, fontWeight)) + .map((c) => convertInlineItalic(c, fontStyle)) + .map((c) => convertInlineUnderline(c, textDecoration)) + .map((c) => addInlineColor(c, color)) + .map((c) => addInlineLetterSpacing(c, letterSpacing)) + .pop(); + + return { ...rest, content: convertedContent }; +} + +function convertInlineBold(content, isBold, fontWeight) { + // Do we have a specific global weight to apply for entire text field? + const globalWeight = + typeof fontWeight === 'number' && fontWeight != 400 + ? fontWeight + : isBold === true + ? 700 + : null; + + if (globalWeight) { + // In that case, strip any inline bold from the text and wrap everything in a span with correct style + const stripped = stripTag(content, 'strong'); + const fancyBold = `font-weight: ${globalWeight}`; + const wrapped = wrapWithSpan(stripped, fancyBold); + return wrapped; + } + + const justBold = 'font-weight: 700'; + return replaceTagWithSpan(content, 'strong', justBold); +} + +function convertInlineItalic(content, fontStyle) { + // Do we have a specific font style to apply for entire text field? + const globalFontStyle = fontStyle === 'italic' ? fontStyle : null; + const italicStyle = 'font-style: italic'; + + if (globalFontStyle) { + // In that case, strip any inline em from the text and wrap everything in a span with correct style + const stripped = stripTag(content, 'em'); + const wrapped = wrapWithSpan(stripped, italicStyle); + return wrapped; + } + + return replaceTagWithSpan(content, 'em', italicStyle); +} + +function convertInlineUnderline(content, textDecoration) { + // Do we have a specific text decoration to apply for entire text field? + const globalDecoration = + textDecoration === 'underline' ? textDecoration : null; + const underlineStyle = 'text-decoration: underline'; + + if (globalDecoration) { + // In that case, strip any inline underline from the text and wrap everything in a span with correct style + const stripped = stripTag(content, 'u'); + const wrapped = wrapWithSpan(stripped, underlineStyle); + return wrapped; + } + + return replaceTagWithSpan(content, 'u', underlineStyle); +} + +function addInlineColor(content, color) { + // If we don't have a color (should never happen, but if), just return + if (!color) { + return content; + } + + const { + color: { r, g, b, a = 1 }, + } = color; + return wrapWithSpan(content, `color: rgba(${r}, ${g}, ${b}, ${a})`); +} + +function addInlineLetterSpacing(content, letterSpacing) { + // If we don't have letterSpacing, just return + if (!letterSpacing) { + return content; + } + + return wrapWithSpan(content, `letter-spacing: ${letterSpacing / 100}em`); +} + +function stripTag(html, tag) { + // This is a very naive strip. Can only remove non-self-closing tags with attributes, which is sufficent here + return html.replace(new RegExp(``, 'gi'), ''); +} + +function replaceTagWithSpan(html, tag, style) { + // Again, very naive + return html + .replace(new RegExp(`<${tag}>`, 'gi'), ``) + .replace(new RegExp(``, 'gi'), ''); +} + +function wrapWithSpan(html, style) { + return `${html}`; +} + +export default inlineTextProperties; diff --git a/assets/src/edit-story/output/page.js b/assets/src/edit-story/output/page.js index dc36bb605b18..82af44371a5b 100644 --- a/assets/src/edit-story/output/page.js +++ b/assets/src/edit-story/output/page.js @@ -42,15 +42,8 @@ function OutputPage({ page, autoAdvance, defaultPageDuration }) { backgroundPosition: '0 0, 0 10px, 10px -10px, -10px 0px', }; const backgroundOverlayStyles = generateOverlayStyles(backgroundOverlay); - const backgroundNonFullbleedElements = elements.filter( - (element) => - element.id === backgroundElementId && - element.isFullbleedBackground === false - ); - const backgroundFullbleedElements = elements.filter( - (element) => - element.id === backgroundElementId && - element.isFullbleedBackground !== false + const backgroundElements = elements.filter( + (element) => element.id === backgroundElementId ); const regularElements = elements.filter( (element) => element.id !== backgroundElementId @@ -66,20 +59,10 @@ function OutputPage({ page, autoAdvance, defaultPageDuration }) { id={id} auto-advance-after={autoAdvance ? autoAdvanceAfter : undefined} > - {backgroundFullbleedElements.length > 0 && ( + {backgroundElements.length > 0 && (

- {backgroundFullbleedElements.map((element) => ( - - ))} -
- - )} - - {backgroundNonFullbleedElements.length > 0 && ( - -
- {backgroundNonFullbleedElements.map((element) => ( + {backgroundElements.map((element) => ( ))}
diff --git a/assets/src/edit-story/output/test/story.js b/assets/src/edit-story/output/test/story.js index ab38ffae0b1d..8a12824dae46 100644 --- a/assets/src/edit-story/output/test/story.js +++ b/assets/src/edit-story/output/test/story.js @@ -199,9 +199,7 @@ describe('Story output', () => { await expect().toBeValidAMP(); }); - // Disable reason: https://github.com/ampproject/amphtml/issues/27881 - // eslint-disable-next-line jest/no-disabled-tests - it.skip('should produce valid AMP output when using Google fonts', async () => { + it('should produce valid AMP output when using Google fonts', async () => { const props = { id: '123', backgroundColor: { type: 'solid', color: { r: 255, g: 255, b: 255 } }, diff --git a/assets/src/edit-story/output/utils/styles.js b/assets/src/edit-story/output/utils/styles.js index d58c02ebef72..6141beb27514 100644 --- a/assets/src/edit-story/output/utils/styles.js +++ b/assets/src/edit-story/output/utils/styles.js @@ -20,11 +20,15 @@ function CustomStyles() { amp-custom="" dangerouslySetInnerHTML={{ __html: ` + amp-story-grid-layer { + overflow: visible; + } + .page-background-area, .page-background-overlay-area, .page-safe-area { position: absolute; - overflow: hidden; + overflow: visible; top: 0; bottom: 0; left: 0; diff --git a/assets/src/edit-story/types.js b/assets/src/edit-story/types.js index 50a91c49ae97..52b0c013eecd 100644 --- a/assets/src/edit-story/types.js +++ b/assets/src/edit-story/types.js @@ -204,14 +204,10 @@ export const FontPropType = PropTypes.shape({ StoryPropTypes.elements.text = PropTypes.shape({ ...StoryElementPropTypes, content: PropTypes.string, - color: PatternPropType.isRequired, backgroundTextMode: PropTypes.oneOf(Object.values(BACKGROUND_TEXT_MODE)), backgroundColor: PatternPropType, font: FontPropType.isRequired, fontSize: PropTypes.number, - fontWeight: PropTypes.number, - fontStyle: PropTypes.string, - letterSpacing: PropTypes.number, lineHeight: PropTypes.number, padding: PropTypes.shape({ horizontal: PropTypes.number, @@ -219,7 +215,6 @@ StoryPropTypes.elements.text = PropTypes.shape({ locked: PropTypes.bool, }), textAlign: PropTypes.string, - textDecoration: PropTypes.string, }); StoryPropTypes.elements.shape = PropTypes.shape({ diff --git a/assets/src/edit-story/units/dimensions.js b/assets/src/edit-story/units/dimensions.js index bf6be8f47180..062cad8dcad2 100644 --- a/assets/src/edit-story/units/dimensions.js +++ b/assets/src/edit-story/units/dimensions.js @@ -17,7 +17,12 @@ /** * Internal dependencies */ -import { PAGE_WIDTH, PAGE_HEIGHT, DEFAULT_EM } from '../constants'; +import { + PAGE_WIDTH, + PAGE_HEIGHT, + FULLBLEED_RATIO, + DEFAULT_EM, +} from '../constants'; /** * Rounds the pixel value to the max allowed precision in the "data" space. @@ -116,9 +121,15 @@ export function getBox( const displayFull = isFill || isBackground; return { x: dataToEditorX(displayFull ? 0 : x, pageWidth), - y: dataToEditorY(displayFull ? 0 : y, pageHeight), + y: dataToEditorY( + displayFull ? -(PAGE_WIDTH / FULLBLEED_RATIO - PAGE_HEIGHT) / 2 : y, + pageHeight + ), width: dataToEditorX(displayFull ? PAGE_WIDTH : width, pageWidth), - height: dataToEditorY(displayFull ? PAGE_HEIGHT : height, pageHeight), + height: dataToEditorY( + displayFull ? PAGE_WIDTH / FULLBLEED_RATIO : height, + pageHeight + ), rotationAngle: displayFull ? 0 : rotationAngle, }; } diff --git a/assets/src/edit-story/utils/createError.js b/assets/src/edit-story/utils/createError.js new file mode 100644 index 000000000000..376c85ef647c --- /dev/null +++ b/assets/src/edit-story/utils/createError.js @@ -0,0 +1,36 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Helper function to get create a js error. + * + * @param {string} name Error name. + * @param {string} fileName File name. + * @param {string} message Message in error. + * @return {Error} Error Object. + */ +function createError(name, fileName, message) { + const validError = new Error(); + + validError.name = name; + validError.file = fileName; + validError.isUserError = true; + validError.message = message; + + return validError; +} + +export default createError; diff --git a/assets/src/edit-story/utils/getValidHTML.js b/assets/src/edit-story/utils/getValidHTML.js new file mode 100644 index 000000000000..92f7d975ab9c --- /dev/null +++ b/assets/src/edit-story/utils/getValidHTML.js @@ -0,0 +1,22 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const contentBuffer = document.createElement('template'); + +export default function getValidHTML(string) { + contentBuffer.innerHTML = string; + return contentBuffer.innerHTML; +} diff --git a/assets/src/edit-story/utils/isPatternEqual.js b/assets/src/edit-story/utils/isPatternEqual.js new file mode 100644 index 000000000000..831a6868e00a --- /dev/null +++ b/assets/src/edit-story/utils/isPatternEqual.js @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import convertToCSS from './convertToCSS'; +import generatePatternStyles from './generatePatternStyles'; + +export default function isPatternEqual(p1, p2, patternType = undefined) { + const p1CSS = convertToCSS(generatePatternStyles(p1, patternType)); + const p2CSS = convertToCSS(generatePatternStyles(p2, patternType)); + return p1CSS === p2CSS; +} diff --git a/assets/src/edit-story/utils/patternUtils.js b/assets/src/edit-story/utils/patternUtils.js new file mode 100644 index 000000000000..503b32071c25 --- /dev/null +++ b/assets/src/edit-story/utils/patternUtils.js @@ -0,0 +1,45 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Internal dependencies + */ +import createSolidFromString from './createSolidFromString'; +import createSolid from './createSolid'; + +export function getSolidFromHex(hex) { + // We already have a nice parser for most of this, but we need to + // parse opacity as the last two hex digits as percent, not 1/256th + + const { + color: { r, g, b }, + } = createSolidFromString(`#${hex.slice(0, 6)}`); + + const opacity = parseInt(hex.slice(6), 16); + + return createSolid(r, g, b, opacity / 100); +} + +export function getHexFromSolid(solid) { + const { + color: { r, g, b, a = 1 }, + } = solid; + const dims = [r, g, b, Math.round(a * 100)]; + return dims + .map((n) => n.toString(16)) + .map((s) => s.padStart(2, '0')) + .join(''); +} diff --git a/assets/src/edit-story/utils/test/createError.js b/assets/src/edit-story/utils/test/createError.js new file mode 100644 index 000000000000..579fa4cbd5f4 --- /dev/null +++ b/assets/src/edit-story/utils/test/createError.js @@ -0,0 +1,27 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Internal dependencies + */ +import createError from '../createError'; + +describe('createError', () => { + it('should return an error', () => { + expect(() => { + throw createError('testing', 'test.jpg', 'hello there'); + }).toThrow('hello there'); + }); +}); diff --git a/assets/src/edit-story/utils/test/useLiveRegion.js b/assets/src/edit-story/utils/test/useLiveRegion.js index 58bdeb925b77..993bd40427d9 100644 --- a/assets/src/edit-story/utils/test/useLiveRegion.js +++ b/assets/src/edit-story/utils/test/useLiveRegion.js @@ -31,7 +31,9 @@ describe('useLiveRegion', () => { expect( queryById(document.documentElement, 'web-stories-aria-live-region-polite') - ).toBeEmpty(); + ).toHaveTextContent(''); + // .toBeEmpty() cannot be used, because of + // https://github.com/testing-library/jest-dom/issues/216 act(() => { result.current('Hello World'); @@ -53,7 +55,9 @@ describe('useLiveRegion', () => { document.documentElement, 'web-stories-aria-live-region-assertive' ) - ).toBeEmpty(); + ).toHaveTextContent(''); + // .toBeEmpty() cannot be used, because of + // https://github.com/testing-library/jest-dom/issues/216 act(() => result.current('Hello World')); @@ -73,7 +77,9 @@ describe('useLiveRegion', () => { expect( queryById(document.documentElement, 'web-stories-aria-live-region-polite') - ).toBeEmpty(); + ).toHaveTextContent(''); + // .toBeEmpty() cannot be used, because of + // https://github.com/testing-library/jest-dom/issues/216 unmount(); @@ -87,7 +93,9 @@ describe('useLiveRegion', () => { expect( queryById(document.documentElement, 'web-stories-aria-live-region-polite') - ).toBeEmpty(); + ).toHaveTextContent(''); + // .toBeEmpty() cannot be used, because of + // https://github.com/testing-library/jest-dom/issues/216 act(() => { result.current('Foo'); @@ -110,6 +118,8 @@ describe('useLiveRegion', () => { expect( queryById(document.documentElement, 'web-stories-aria-live-region-polite') - ).toBeEmpty(); + ).toHaveTextContent(''); + // .toBeEmpty() cannot be used, because of + // https://github.com/testing-library/jest-dom/issues/216 }); }); diff --git a/assets/src/edit-story/utils/useFocusOut.js b/assets/src/edit-story/utils/useFocusOut.js index a22fac872ef6..cd853a4ff208 100644 --- a/assets/src/edit-story/utils/useFocusOut.js +++ b/assets/src/edit-story/utils/useFocusOut.js @@ -35,19 +35,23 @@ function useFocusOut(ref, callback, deps) { const onDocumentClick = (evt) => { // If something outside the target node is clicked, callback time! - const isInDocument = node.ownerDocument.contains(evt.target); const isInNode = node.contains(evt.target); - if (!isInNode && isInDocument) { + if (!isInNode) { callback(); } }; node.addEventListener('focusout', onFocusOut); - node.ownerDocument.addEventListener('pointerdown', onDocumentClick); + // Often elements are removed in pointerdown handlers elsewhere, causing them + // to fail the node.contains check regardless of being inside target ref or not. + // By checking the click target in the capture phase, we circumvent that completely. + const opts = { capture: true }; + const doc = node.ownerDocument; + doc.addEventListener('pointerdown', onDocumentClick, opts); return () => { node.removeEventListener('focusout', onFocusOut); - node.ownerDocument.removeEventListener('pointerdown', onDocumentClick); + doc.removeEventListener('pointerdown', onDocumentClick, opts); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, deps || []); diff --git a/assets/src/edit-story/utils/usePreventWindowUnload.js b/assets/src/edit-story/utils/usePreventWindowUnload.js index 33001d2cce1a..1fcc068bf6c8 100644 --- a/assets/src/edit-story/utils/usePreventWindowUnload.js +++ b/assets/src/edit-story/utils/usePreventWindowUnload.js @@ -50,8 +50,8 @@ function usePreventWindowUnload() { }, [context] ); - return setPreventUnload; } -export default usePreventWindowUnload; +const shouldDisablePrevent = Boolean(process.env.DISABLE_PREVENT); +export default shouldDisablePrevent ? () => {} : usePreventWindowUnload; diff --git a/assets/src/edit-story/utils/useWhyDidYouUpdate.js b/assets/src/edit-story/utils/useWhyDidYouUpdate.js index 071545743b38..f7bd63ef8bec 100644 --- a/assets/src/edit-story/utils/useWhyDidYouUpdate.js +++ b/assets/src/edit-story/utils/useWhyDidYouUpdate.js @@ -56,6 +56,6 @@ function useWhyDidYouUpdate(name, props) { }); } -const isDevelopment = process.env.NODE_ENV !== 'production'; +const isDevelopment = process.env.NODE_ENV === 'development'; export default isDevelopment ? useWhyDidYouUpdate : () => {}; diff --git a/bin/deploy-to-test-environment.sh b/bin/deploy-to-test-environment.sh index 747d240c604f..9387e8babd6e 100755 --- a/bin/deploy-to-test-environment.sh +++ b/bin/deploy-to-test-environment.sh @@ -81,14 +81,11 @@ fi # Install and build. cd "$project_dir" -echo "Starting build process..." - -echo "Installing npm dependencies" -npm install --silent - echo "Building plugin" npm run build:js --silent npm run build:plugin --silent -- --prerelease + +echo "Bundling plugin" npm run bundle-plugin --silent -- --copy echo "Moving files to repository" diff --git a/bin/local-env/includes.sh b/bin/local-env/includes.sh index 8d0c2a04bd28..56feb35f8684 100755 --- a/bin/local-env/includes.sh +++ b/bin/local-env/includes.sh @@ -155,7 +155,7 @@ dc() { # Executes a WP CLI request in the CLI container. ## wp() { - dc exec -u xfs $CLI wp "$@" + dc exec -T -u xfs $CLI wp "$@" } ## @@ -164,5 +164,5 @@ wp() { # Executes the given command in the wordpress container. ## container() { - dc exec $CONTAINER "$@" + dc exec -T $CONTAINER "$@" } diff --git a/bin/local-env/install-composer.sh b/bin/local-env/install-composer.sh index 6a638ec43ecd..6866290dd54f 100755 --- a/bin/local-env/install-composer.sh +++ b/bin/local-env/install-composer.sh @@ -7,12 +7,11 @@ set -e # Include useful functions . "$(dirname "$0")/includes.sh" - # Change to the expected directory cd "$(dirname "$0")/../.." # Check if composer is installed -if [ "$TRAVIS" != "true" ] && ! command_exists "composer"; then +if [ "$CI" != "true" ] && ! command_exists "composer"; then if ask "$(error_message "Composer isn't installed, would you like to download and install it automatically?")" Y; then echo -en $(status_message "Installing Composer..." ) download "https://getcomposer.org/installer" | bash >/dev/null 2>&1 @@ -29,12 +28,14 @@ if [ "$TRAVIS" != "true" ] && ! command_exists "composer"; then fi # Check if the current Composer version is up to date. -if [ "$TRAVIS" != "true" ] && ! [[ "$(composer --version)" == "Composer version $COMPOSER_VERSION "* ]]; then +if [ "$CI" != "true" ] && ! [[ "$(composer --version)" == "Composer version $COMPOSER_VERSION "* ]]; then echo -en $(status_message "Updating Composer..." ) composer self-update echo ' done!' fi # Install/update packages -echo -e $(status_message "Installing and updating Composer packages..." ) -composer install --optimize-autoloader --no-interaction --prefer-dist --no-suggest --ignore-platform-reqs +if [ "$CI" != "true" ]; then + echo -e $(status_message "Installing and updating Composer packages..." ) + composer install --optimize-autoloader --no-interaction --prefer-dist --no-suggest --ignore-platform-reqs +fi diff --git a/bin/local-env/install-node-nvm.sh b/bin/local-env/install-node-nvm.sh index 98f2a18dfea9..3d8e16c713af 100755 --- a/bin/local-env/install-node-nvm.sh +++ b/bin/local-env/install-node-nvm.sh @@ -22,7 +22,7 @@ fi cd "$(dirname "$0")/../.." # Check if nvm is installed -if [ "$TRAVIS" != "true" ] && ! command_exists "nvm"; then +if [ "$CI" != "true" ] && ! command_exists "nvm"; then if ask "$(error_message "NVM isn't installed, would you like to download and install it automatically?")" Y; then # The .bash_profile file needs to exist for NVM to install if [ ! -e ~/.bash_profile ]; then @@ -46,7 +46,7 @@ if [ "$TRAVIS" != "true" ] && ! command_exists "nvm"; then fi # Check if the current nvm version is up to date. -if [ "$TRAVIS" != "true" ] && [ $NVM_VERSION != "v$(nvm --version)" ]; then +if [ "$CI" != "true" ] && [ $NVM_VERSION != "v$(nvm --version)" ]; then echo -en $(status_message "Updating NVM..." ) download "https://raw.githubusercontent.com/nvm-sh/nvm/$NVM_VERSION/install.sh" | bash >/dev/null 2>&1 echo ' done!' @@ -58,14 +58,14 @@ if [ "$TRAVIS" != "true" ] && [ $NVM_VERSION != "v$(nvm --version)" ]; then fi # Check if the current node version is up to date. -if [ "$TRAVIS" != "true" ] && [ "$(nvm current)" != "$(nvm version-remote --lts)" ]; then +if [ "$CI" != "true" ] && [ "$(nvm current)" != "$(nvm version-remote --lts)" ]; then echo -e $(warning_message "Node version does not match the latest long term support version. Please run this command to install and use it:" ) echo -e $(warning_message "$(action_format "nvm install")" ) echo -e $(warning_message "After that, re-run the setup script to continue." ) exit 1 fi -if [ "$TRAVIS" != "true" ]; then +if [ "$CI" != "true" ]; then # Install/update packages echo -e $(status_message "Installing and updating NPM packages..." ) npm install @@ -75,7 +75,7 @@ if [ "$TRAVIS" != "true" ]; then fi # There was a bug in NPM that caused changes in package-lock.json. Handle that. -if [ "$TRAVIS" != "true" ] && ! git diff --no-ext-diff --exit-code package-lock.json >/dev/null; then +if [ "$CI" != "true" ] && ! git diff --no-ext-diff --exit-code package-lock.json >/dev/null; then if ask "$(warning_message "Your package-lock.json changed, which may mean there's an issue with your NPM cache. Would you like to try and automatically clean it up?" )" N 10; then rm -rf node_modules/ npm cache clean --force >/dev/null 2>&1 diff --git a/docs/getting-started.md b/docs/getting-started.md index 28977eef9c79..f6f2a2b7707d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -83,6 +83,10 @@ To get a production build, run: npm run build:js ``` +### Live reload + +We don't provide it OOTB. You can setup your own solution and `DISABLE_PREVENT=1 npm run dev` will help you with unwanted `beforeunload` alert. + ### Testing #### PHP Unit Tests diff --git a/includes/Dashboard.php b/includes/Dashboard.php index d5b34f23ed08..4c23d0bfb2e0 100644 --- a/includes/Dashboard.php +++ b/includes/Dashboard.php @@ -153,8 +153,11 @@ public function enqueue_assets( $hook_suffix ) { 'pluginDir' => WEBSTORIES_PLUGIN_DIR_URL, 'version' => WEBSTORIES_VERSION, 'api' => [ - 'stories' => sprintf( '/wp/v2/%s', $rest_base ), - 'fonts' => '/web-stories/v1/fonts', + 'stories' => sprintf( '/wp/v2/%s', $rest_base ), + 'users' => '/wp/v2/users', + 'tags' => '/wp/v2/tags', + 'categories' => '/wp/v2/categories', + 'fonts' => '/web-stories/v1/fonts', ], ], ] diff --git a/includes/REST_API/Stories_Controller.php b/includes/REST_API/Stories_Controller.php index 28ff94dbfb54..a8f3536c6c7c 100644 --- a/includes/REST_API/Stories_Controller.php +++ b/includes/REST_API/Stories_Controller.php @@ -26,6 +26,7 @@ namespace Google\Web_Stories\REST_API; +use Google\Web_Stories\Dashboard; use Google\Web_Stories\Story_Post_Type; use stdClass; use WP_Error; @@ -166,7 +167,7 @@ public function update_item( $request ) { } /** - * Retrieves the attachment's schema, conforming to JSON Schema. + * Retrieves the story's schema, conforming to JSON Schema. * * @return array Item schema as an array. */ @@ -211,4 +212,39 @@ public function get_item_schema() { return $this->add_additional_fields_schema( $this->schema ); } + /** + * Filters the orderby query to filter first all the current user's posts and then the rest. + * + * @param string $orderby Original orderby clause. + * @param \WP_Query $query WP_Query object. + * @return string Orderby clause. + */ + public function filter_posts_orderby( $orderby, $query ) { + global $wpdb; + if ( Story_Post_Type::POST_TYPE_SLUG !== $query->get( 'post_type' ) ) { + return $orderby; + } + if ( 'story_author' !== $query->get( 'orderby' ) ) { + return $orderby; + } + + $current_user = get_current_user_id(); + if ( ! $current_user ) { + return $orderby; + } + return $wpdb->prepare( 'wp_posts.post_author = %s DESC, wp_posts.post_author DESC', $current_user ); + } + + /** + * Retrieves a collection of web stories. + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + add_filter( 'posts_orderby', [ $this, 'filter_posts_orderby' ], 10, 2 ); + $response = parent::get_items( $request ); + remove_filter( 'posts_orderby', [ $this, 'filter_posts_orderby' ], 10 ); + return $response; + } } diff --git a/includes/Story_Post_Type.php b/includes/Story_Post_Type.php index 5ba23a57dffa..1eadf20f00ec 100644 --- a/includes/Story_Post_Type.php +++ b/includes/Story_Post_Type.php @@ -144,6 +144,8 @@ public static function init() { add_filter( 'admin_body_class', [ __CLASS__, 'admin_body_class' ], 99 ); add_filter( 'wp_kses_allowed_html', [ __CLASS__, 'filter_kses_allowed_html' ], 10, 2 ); + add_filter( 'rest_' . self::POST_TYPE_SLUG . '_collection_params', [ __CLASS__, 'filter_rest_collection_params' ], 10, 2 ); + // Select the single-web-story.php template for Stories. add_filter( 'template_include', [ __CLASS__, 'filter_template_include' ] ); @@ -185,6 +187,41 @@ static function( $skipped, $post ) { PHP_INT_MAX, 2 ); + + add_filter( '_wp_post_revision_fields', [ __CLASS__, 'filter_revision_fields' ], 10, 2 ); + } + + /** + * Add story_author as allowed orderby value for REST API. + * + * @param array $query_params Array of allowed query params. + * @param \WP_Post_Type $post_type Post type. + * @return array Array of query params. + */ + public static function filter_rest_collection_params( $query_params, $post_type ) { + if ( self::POST_TYPE_SLUG !== $post_type->name ) { + return $query_params; + } + + if ( empty( $query_params['orderby'] ) ) { + return $query_params; + } + $query_params['orderby']['enum'][] = 'story_author'; + return $query_params; + } + + /** + * Filters the revision fields to ensure that JSON representation gets saved to Story revisions. + * + * @param array $fields Array of allowed revision fields. + * @param array $story Story post array. + * @return array Array of allowed fields. + */ + public static function filter_revision_fields( $fields, $story ) { + if ( self::POST_TYPE_SLUG === $story['post_type'] ) { + $fields['post_content_filtered'] = __( 'Story data', 'web-stories' ); + } + return $fields; } /** @@ -265,56 +302,12 @@ public static function admin_enqueue_scripts( $hook ) { wp_set_script_translations( self::WEB_STORIES_SCRIPT_HANDLE, 'web-stories' ); - $post = get_post(); - $story_id = ( $post ) ? $post->ID : null; - $rest_base = self::POST_TYPE_SLUG; - $post_type_object = get_post_type_object( self::POST_TYPE_SLUG ); + $settings = self::get_editor_settings(); - if ( $post_type_object instanceof \WP_Post_Type ) { - $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; - } - - // Media settings. - $max_upload_size = wp_max_upload_size(); - if ( ! $max_upload_size ) { - $max_upload_size = 0; - } - - $preview_query_args = [ - 'preview_id' => $story_id, - // Leveraging the default WP post preview logic. - 'preview_nonce' => wp_create_nonce( 'post_preview_' . $story_id ), - ]; wp_localize_script( self::WEB_STORIES_SCRIPT_HANDLE, 'webStoriesEditorSettings', - [ - 'id' => 'edit-story', - 'config' => [ - 'isRTL' => is_rtl(), - 'timeFormat' => get_option( 'time_format' ), - 'allowedMimeTypes' => self::get_allowed_mime_types(), - 'allowedFileTypes' => self::get_allowed_file_types(), - 'postType' => self::POST_TYPE_SLUG, - 'storyId' => $story_id, - 'previewLink' => get_preview_post_link( $story_id, $preview_query_args ), - 'maxUpload' => $max_upload_size, - 'pluginDir' => WEBSTORIES_PLUGIN_DIR_URL, - 'api' => [ - 'stories' => sprintf( '/wp/v2/%s', $rest_base ), - 'media' => '/wp/v2/media', - 'users' => '/wp/v2/users', - 'statuses' => '/wp/v2/statuses', - 'fonts' => '/web-stories/v1/fonts', - 'link' => '/web-stories/v1/link', - ], - 'metadata' => [ - 'publisher' => self::get_publisher_data(), - 'logoPlaceholder' => self::PUBLISHER_LOGO_PLACEHOLDER, - 'fallbackPoster' => plugins_url( 'assets/images/fallback-poster.jpg', WEBSTORIES_PLUGIN_FILE ), - ], - ], - ] + $settings ); wp_register_style( @@ -338,6 +331,79 @@ public static function admin_enqueue_scripts( $hook ) { ); } + /** + * Get edittor settings as an array. + * + * @return array + */ + public static function get_editor_settings() { + $post = get_post(); + $story_id = ( $post ) ? $post->ID : null; + $rest_base = self::POST_TYPE_SLUG; + $has_publish_action = false; + $has_assign_author_action = false; + $has_upload_media_action = current_user_can( 'upload_files' ); + $post_type_object = get_post_type_object( self::POST_TYPE_SLUG ); + + if ( $post_type_object instanceof \WP_Post_Type ) { + $rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name; + if ( property_exists( $post_type_object->cap, 'publish_posts' ) ) { + $has_publish_action = current_user_can( $post_type_object->cap->publish_posts ); + } + if ( property_exists( $post_type_object->cap, 'edit_others_posts' ) ) { + $has_assign_author_action = current_user_can( $post_type_object->cap->edit_others_posts ); + } + } + + // Media settings. + $max_upload_size = wp_max_upload_size(); + if ( ! $max_upload_size ) { + $max_upload_size = 0; + } + + $preview_query_args = [ + 'preview_id' => $story_id, + // Leveraging the default WP post preview logic. + 'preview_nonce' => wp_create_nonce( 'post_preview_' . $story_id ), + ]; + + $settings = [ + 'id' => 'edit-story', + 'config' => [ + 'autoSaveInterval' => defined( 'AUTOSAVE_INTERVAL' ) ? AUTOSAVE_INTERVAL : null, + 'isRTL' => is_rtl(), + 'timeFormat' => get_option( 'time_format' ), + 'allowedMimeTypes' => self::get_allowed_mime_types(), + 'allowedFileTypes' => self::get_allowed_file_types(), + 'postType' => self::POST_TYPE_SLUG, + 'storyId' => $story_id, + 'previewLink' => get_preview_post_link( $story_id, $preview_query_args ), + 'maxUpload' => $max_upload_size, + 'pluginDir' => WEBSTORIES_PLUGIN_DIR_URL, + 'capabilities' => [ + 'hasPublishAction' => $has_publish_action, + 'hasAssignAuthorAction' => $has_assign_author_action, + 'hasUploadMediaAction' => $has_upload_media_action, + ], + 'api' => [ + 'stories' => sprintf( '/wp/v2/%s', $rest_base ), + 'media' => '/wp/v2/media', + 'users' => '/wp/v2/users', + 'statuses' => '/wp/v2/statuses', + 'fonts' => '/web-stories/v1/fonts', + 'link' => '/web-stories/v1/link', + ], + 'metadata' => [ + 'publisher' => self::get_publisher_data(), + 'logoPlaceholder' => self::PUBLISHER_LOGO_PLACEHOLDER, + 'fallbackPoster' => plugins_url( 'assets/images/fallback-poster.jpg', WEBSTORIES_PLUGIN_FILE ), + ], + ], + ]; + + return $settings; + } + /** * Returns a list of allowed file types. * diff --git a/package-lock.json b/package-lock.json index 52ac1e1c7d64..3da39fe0a3b8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1513,17 +1513,17 @@ } }, "@daybrush/drag": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/@daybrush/drag/-/drag-0.13.1.tgz", - "integrity": "sha512-wUciS4IAwHQ2zGGjBwp67YVB4jd3jReG/Ol8Ib9k5etmMSrLlBh8h6L5diZuzT09NUKRWPfEuYkhKs9A1lykng==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@daybrush/drag/-/drag-0.14.0.tgz", + "integrity": "sha512-AVVFLfGOosSR2LrgxyRKg8FRSATaa5sDFZlTQ9YgKy/F5etg2v/IO8kdcpIKD6FDZ74kT5hdjG0JJf+9YcIWfQ==", "requires": { - "@daybrush/utils": "^0.10.0" + "@daybrush/utils": "^0.10.3" } }, "@daybrush/utils": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@daybrush/utils/-/utils-0.10.1.tgz", - "integrity": "sha512-pKnlI3C+u/qgkhypQQT9G7YwEmBp+6XD5ZHzVCKtA0avuku//KGdLoIT6QL78aXI7ZPlJwY31t24+6xkxqdoJg==" + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@daybrush/utils/-/utils-0.10.3.tgz", + "integrity": "sha512-CwbGNct2loskTaSJ/OSmapOhdNfDHI71cApPQSPs5MXbfUb33u2JHNkL6sUJpv0VVpsO+s+3UPpGDS2KCAyEKQ==" }, "@egjs/agent": { "version": "2.1.5", @@ -1795,9 +1795,9 @@ } }, "@jest/core": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.3.tgz", - "integrity": "sha512-/wY1JCmY61wMjGsix0ccNmiHsl7+l1nGMvdXnWbceezWyf8gFoqA7kRN6e2CTcF2K1GVYhsUdLdyKpz7hVMegQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz", + "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==", "dev": true, "requires": { "@jest/console": "^25.5.0", @@ -1810,14 +1810,14 @@ "exit": "^0.1.2", "graceful-fs": "^4.2.4", "jest-changed-files": "^25.5.0", - "jest-config": "^25.5.3", + "jest-config": "^25.5.4", "jest-haste-map": "^25.5.1", "jest-message-util": "^25.5.0", "jest-regex-util": "^25.2.6", "jest-resolve": "^25.5.1", - "jest-resolve-dependencies": "^25.5.2", - "jest-runner": "^25.5.3", - "jest-runtime": "^25.5.3", + "jest-resolve-dependencies": "^25.5.4", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4", "jest-snapshot": "^25.5.1", "jest-util": "^25.5.0", "jest-validate": "^25.5.0", @@ -2942,16 +2942,16 @@ } }, "@jest/test-sequencer": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.3.tgz", - "integrity": "sha512-/xvmwp+7eTsgmbyFp649WCpyf6x+c7CMERYOnZtIYIqpOW2p0vDbY1TNyr1wum4c/xSe+KR8iBIzsZKa6csmeg==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz", + "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==", "dev": true, "requires": { "@jest/test-result": "^25.5.0", "graceful-fs": "^4.2.4", "jest-haste-map": "^25.5.1", - "jest-runner": "^25.5.3", - "jest-runtime": "^25.5.3" + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4" }, "dependencies": { "@jest/console": { @@ -5230,6 +5230,32 @@ "fill-range": "^7.0.1" } }, + "cacache": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", + "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "dev": true, + "requires": { + "chownr": "^1.1.2", + "figgy-pudding": "^3.5.1", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "infer-owner": "^1.0.4", + "lru-cache": "^5.1.1", + "minipass": "^3.0.0", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "p-map": "^3.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^2.7.1", + "ssri": "^7.0.0", + "unique-filename": "^1.1.1" + } + }, "chalk": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", @@ -5324,6 +5350,16 @@ "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", "dev": true }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -5333,6 +5369,15 @@ "p-locate": "^4.1.0" } }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, "make-dir": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.2.tgz", @@ -5367,6 +5412,15 @@ "p-limit": "^2.2.0" } }, + "p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5388,12 +5442,53 @@ "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "schema-utils": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz", + "integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==", + "dev": true, + "requires": { + "ajv": "^6.12.0", + "ajv-keywords": "^3.4.1" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "serialize-javascript": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz", + "integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "ssri": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", + "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1", + "minipass": "^3.1.1" + } + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -5403,6 +5498,53 @@ "has-flag": "^4.0.0" } }, + "terser": { + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz", + "integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.6.tgz", + "integrity": "sha512-I8IDsQwZrqjdmOicNeE8L/MhwatAap3mUrtcAKJuilsemUNcX+Hier/eAzwStVqhlCxq0aG3ni9bK/0BESXkTg==", + "dev": true, + "requires": { + "cacache": "^13.0.1", + "find-cache-dir": "^3.3.1", + "jest-worker": "^25.4.0", + "p-limit": "^2.3.0", + "schema-utils": "^2.6.6", + "serialize-javascript": "^3.0.0", + "source-map": "^0.6.1", + "terser": "^4.6.12", + "webpack-sources": "^1.4.3" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5411,6 +5553,12 @@ "requires": { "is-number": "^7.0.0" } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true } } }, @@ -6499,26 +6647,162 @@ "integrity": "sha512-oocsqY7g0cR+Gur5jRQLSrX2OtpMLMse1I10JQBm8CdGMrDkh1Mg2gjsiquMHRtBs4Qwu5wgEp5GgIYHk4SNPw==" }, "@testing-library/dom": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.2.2.tgz", - "integrity": "sha512-g+gT//COYh2FgRrlgcgdkifkjqSk7wQIS7F8jbrf6yoEsh85PJUJ/QtO0bJ9QU7pQPYQgKcgqNJsOs0dlyFYag==", + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-7.3.0.tgz", + "integrity": "sha512-F/gFzOGsX1ulldWMREiDkoBg4/kfKXhXi04iW9irwwwF/BLW9dSMLyK0zqnBjCTx76A8B584EuYBhNgWNOnUfA==", "dev": true, "requires": { - "@babel/runtime": "^7.9.2", - "@types/testing-library__dom": "^7.0.0", + "@babel/runtime": "^7.9.6", + "@types/testing-library__dom": "^7.0.2", "aria-query": "^4.0.2", - "dom-accessibility-api": "^0.4.2", - "pretty-format": "^25.1.0" + "dom-accessibility-api": "^0.4.3", + "pretty-format": "^26.0.1" }, "dependencies": { "@babel/runtime": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.2.tgz", - "integrity": "sha512-NE2DtOdufG7R5vnfQUTehdTfNycfUANEtCa9PssN9O/xmTzP4E08UI797ixaei6hBEVL9BI/PsdJS5x7mWoB9Q==", + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } + }, + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@types/testing-library__dom": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/testing-library__dom/-/testing-library__dom-7.0.2.tgz", + "integrity": "sha512-8yu1gSwUEAwzg2OlPNbGq+ixhmSviGurBu1+ivxRKq1eRcwdjkmlwtPvr9VhuxTq2fNHBWN2po6Iem3Xt5A6rg==", + "dev": true, + "requires": { + "pretty-format": "^25.1.0" + }, + "dependencies": { + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + } + } + }, + "@types/yargs": { + "version": "15.0.4", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.4.tgz", + "integrity": "sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "pretty-format": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz", + "integrity": "sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw==", + "dev": true, + "requires": { + "@jest/types": "^26.0.1", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "dependencies": { + "@jest/types": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } + } + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -6677,9 +6961,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.10", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.10.tgz", - "integrity": "sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==", + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", + "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -7130,6 +7414,53 @@ "@types/node": "*" } }, + "@typescript-eslint/experimental-utils": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.31.0.tgz", + "integrity": "sha512-MI6IWkutLYQYTQgZ48IVnRXmLR/0Q6oAyJgiOror74arUMh7EWjJkADfirZhRsUMHeLJ85U2iySDwHTSnNi9vA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.31.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.31.0.tgz", + "integrity": "sha512-vxW149bXFXXuBrAak0eKHOzbcu9cvi6iNcJDzEtOkRwGHxJG15chiAQAwhLOsk+86p9GTr/TziYvw+H9kMaIgA==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^6.3.0", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -7644,6 +7975,11 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==" + }, "abab": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", @@ -8032,20 +8368,17 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-equal": { "version": "1.0.0", @@ -8088,8 +8421,7 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.find": { "version": "2.1.1", @@ -8197,8 +8529,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "ast-types": { "version": "0.12.4", @@ -8247,8 +8578,7 @@ "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "autoprefixer": { "version": "9.7.4", @@ -8419,55 +8749,54 @@ "dev": true }, "babel-jest": { - "version": "25.5.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", - "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.1.tgz", + "integrity": "sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw==", "dev": true, "requires": { - "@jest/transform": "^25.5.1", - "@jest/types": "^25.5.0", + "@jest/transform": "^26.0.1", + "@jest/types": "^26.0.1", "@types/babel__core": "^7.1.7", "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^25.5.0", - "chalk": "^3.0.0", + "babel-preset-jest": "^26.0.0", + "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "slash": "^3.0.0" }, "dependencies": { "@jest/transform": { - "version": "25.5.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", - "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.0.1.tgz", + "integrity": "sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/types": "^25.5.0", + "@jest/types": "^26.0.1", "babel-plugin-istanbul": "^6.0.0", - "chalk": "^3.0.0", + "chalk": "^4.0.0", "convert-source-map": "^1.4.0", "fast-json-stable-stringify": "^2.0.0", "graceful-fs": "^4.2.4", - "jest-haste-map": "^25.5.1", - "jest-regex-util": "^25.2.6", - "jest-util": "^25.5.0", + "jest-haste-map": "^26.0.1", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.0.1", "micromatch": "^4.0.2", "pirates": "^4.0.1", - "realpath-native": "^2.0.0", "slash": "^3.0.0", "source-map": "^0.6.1", "write-file-atomic": "^3.0.0" } }, "@jest/types": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", - "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz", + "integrity": "sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^1.1.1", "@types/yargs": "^15.0.0", - "chalk": "^3.0.0" + "chalk": "^4.0.0" } }, "@types/yargs": { @@ -8522,9 +8851,9 @@ } }, "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -8602,20 +8931,20 @@ } }, "jest-haste-map": { - "version": "25.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", - "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.0.1.tgz", + "integrity": "sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA==", "dev": true, "requires": { - "@jest/types": "^25.5.0", + "@jest/types": "^26.0.1", "@types/graceful-fs": "^4.1.2", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "fsevents": "^2.1.2", "graceful-fs": "^4.2.4", - "jest-serializer": "^25.5.0", - "jest-util": "^25.5.0", - "jest-worker": "^25.5.0", + "jest-serializer": "^26.0.0", + "jest-util": "^26.0.1", + "jest-worker": "^26.0.0", "micromatch": "^4.0.2", "sane": "^4.0.3", "walker": "^1.0.7", @@ -8623,37 +8952,37 @@ } }, "jest-regex-util": { - "version": "25.2.6", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", - "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", + "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", "dev": true }, "jest-serializer": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", - "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.0.0.tgz", + "integrity": "sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ==", "dev": true, "requires": { "graceful-fs": "^4.2.4" } }, "jest-util": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", - "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.0.1.tgz", + "integrity": "sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g==", "dev": true, "requires": { - "@jest/types": "^25.5.0", - "chalk": "^3.0.0", + "@jest/types": "^26.0.1", + "chalk": "^4.0.0", "graceful-fs": "^4.2.4", "is-ci": "^2.0.0", "make-dir": "^3.0.0" } }, "jest-worker": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", - "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz", + "integrity": "sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -8679,12 +9008,6 @@ "picomatch": "^2.0.5" } }, - "realpath-native": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", - "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", - "dev": true - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -8871,9 +9194,9 @@ } }, "babel-plugin-jest-hoist": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", - "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz", + "integrity": "sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w==", "dev": true, "requires": { "@babel/template": "^7.3.3", @@ -9142,12 +9465,12 @@ } }, "babel-preset-jest": { - "version": "25.5.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", - "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz", + "integrity": "sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw==", "dev": true, "requires": { - "babel-plugin-jest-hoist": "^25.5.0", + "babel-plugin-jest-hoist": "^26.0.0", "babel-preset-current-node-syntax": "^0.1.2" } }, @@ -9241,14 +9564,12 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -9263,7 +9584,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -9272,7 +9592,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -9281,7 +9600,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -9290,7 +9608,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -9561,7 +9878,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -9571,7 +9887,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -9589,7 +9904,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -9821,7 +10135,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -10080,8 +10393,7 @@ "ci-info": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, "cipher-base": { "version": "1.0.4", @@ -10097,7 +10409,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -10109,7 +10420,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -10411,7 +10721,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -10506,14 +10815,12 @@ "component-emitter": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -10643,8 +10950,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-to-clipboard": { "version": "3.3.1", @@ -10790,7 +11096,6 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -11329,7 +11634,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -11339,7 +11643,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -11348,7 +11651,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -11357,7 +11659,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -11548,11 +11849,11 @@ } }, "dom-helpers": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz", - "integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.4.tgz", + "integrity": "sha512-TjMyeVUvNEnOnhzs6uAn9Ya47GmMo3qq7m+Lr/3ON0Rs5kHvb8I+SQYjLUSYn7qhEm0QjW0yrBkvz9yOrwwz1A==", "requires": { - "@babel/runtime": "^7.6.3", + "@babel/runtime": "^7.8.7", "csstype": "^2.6.7" } }, @@ -12678,10 +12979,13 @@ "dev": true }, "eslint-plugin-testing-library": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.0.4.tgz", - "integrity": "sha512-GRyWsfMzfKSnD6NPIH40+psc5CqV0z36Tzqbkpdx1rDOgrIKN6t3aqxGrYYAKAlSsqP7VOKfvptVBprFflw60g==", - "dev": true + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-testing-library/-/eslint-plugin-testing-library-3.1.0.tgz", + "integrity": "sha512-N2T2w57lLcrl/1OBnN8b+Bv6lAzIETO2kde5chwN9egcfZnVkwaVh4D6GbgNe5vOkTE7dVj8nLP5Y8NHqwp/ew==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^2.29.0" + } }, "eslint-scope": { "version": "4.0.3", @@ -12836,7 +13140,6 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -12851,7 +13154,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -12860,7 +13162,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -12869,7 +13170,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -12877,8 +13177,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -13179,7 +13478,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -13189,7 +13487,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -13211,7 +13508,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, "requires": { "array-unique": "^0.3.2", "define-property": "^1.0.0", @@ -13227,7 +13523,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -13236,7 +13531,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -13245,7 +13539,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -13254,7 +13547,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -13263,7 +13555,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -13475,7 +13766,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -13487,7 +13777,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -13614,6 +13903,35 @@ "semver-regex": "^2.0.0" } }, + "find-yarn-workspace-root": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-1.2.1.tgz", + "integrity": "sha512-dVtfb0WuQG+8Ag2uWkbG79hOUzEsRrhBzgfn86g2sJPkzmcpGdghbNTfUKGTxymFrY/tLIodDzLoW9nOJ4FY8Q==", + "requires": { + "fs-extra": "^4.0.3", + "micromatch": "^3.1.4" + }, + "dependencies": { + "fs-extra": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz", + "integrity": "sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } + } + }, "findup": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/findup/-/findup-0.1.5.tgz", @@ -13737,8 +14055,7 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "for-own": { "version": "0.1.5", @@ -13796,7 +14113,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -13886,8 +14202,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.11", @@ -13903,28 +14218,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true, "optional": true }, "aproba": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, @@ -13935,14 +14250,14 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true, "optional": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "optional": true, @@ -13953,42 +14268,42 @@ }, "chownr": { "version": "1.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true, "optional": true }, "concat-map": { "version": "0.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true, "optional": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true, "optional": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "3.2.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "optional": true, @@ -13998,28 +14313,28 @@ }, "deep-extend": { "version": "0.6.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", "dev": true, "optional": true, @@ -14029,14 +14344,14 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, @@ -14053,7 +14368,7 @@ }, "glob": { "version": "7.1.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "optional": true, @@ -14068,14 +14383,14 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "resolved": false, + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, @@ -14085,7 +14400,7 @@ }, "ignore-walk": { "version": "3.0.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "dev": true, "optional": true, @@ -14095,7 +14410,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, @@ -14106,21 +14421,21 @@ }, "inherits": { "version": "2.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "optional": true }, "ini": { "version": "1.3.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "optional": true, @@ -14130,14 +14445,14 @@ }, "isarray": { "version": "1.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "optional": true, @@ -14147,14 +14462,14 @@ }, "minimist": { "version": "0.0.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true, "optional": true }, "minipass": { "version": "2.9.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "dev": true, "optional": true, @@ -14165,7 +14480,7 @@ }, "minizlib": { "version": "1.3.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "dev": true, "optional": true, @@ -14175,7 +14490,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "optional": true, @@ -14185,14 +14500,14 @@ }, "ms": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "optional": true }, "needle": { "version": "2.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", "dev": true, "optional": true, @@ -14204,7 +14519,7 @@ }, "node-pre-gyp": { "version": "0.14.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz", "integrity": "sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==", "dev": true, "optional": true, @@ -14223,7 +14538,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, @@ -14234,7 +14549,7 @@ }, "npm-bundled": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", "dev": true, "optional": true, @@ -14244,14 +14559,14 @@ }, "npm-normalize-package-bin": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.7", - "resolved": false, + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.7.tgz", "integrity": "sha512-vAj7dIkp5NhieaGZxBJB8fF4R0078rqsmhJcAfXZ6O7JJhjhPK96n5Ry1oZcfLXgfun0GWTZPOxaEyqv8GBykQ==", "dev": true, "optional": true, @@ -14262,7 +14577,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, @@ -14275,21 +14590,21 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true, "optional": true }, "object-assign": { "version": "4.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "optional": true, @@ -14299,21 +14614,21 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, @@ -14324,21 +14639,21 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "resolved": false, + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, @@ -14351,7 +14666,7 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true @@ -14360,7 +14675,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": false, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, @@ -14376,7 +14691,7 @@ }, "rimraf": { "version": "2.7.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "optional": true, @@ -14386,49 +14701,49 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, "optional": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "optional": true, @@ -14440,7 +14755,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, @@ -14450,7 +14765,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "optional": true, @@ -14460,14 +14775,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.13", - "resolved": false, + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "dev": true, "optional": true, @@ -14483,14 +14798,14 @@ }, "util-deprecate": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "resolved": false, + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, @@ -14500,14 +14815,14 @@ }, "wrappy": { "version": "1.0.2", - "resolved": false, + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true, "optional": true }, "yallist": { "version": "3.1.1", - "resolved": false, + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true, "optional": true @@ -14616,8 +14931,7 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", @@ -14649,7 +14963,6 @@ "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -14832,8 +15145,7 @@ "graceful-fs": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", - "dev": true + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" }, "graceful-readlink": { "version": "1.0.1", @@ -14921,7 +15233,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -14932,7 +15243,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -14942,7 +15252,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -15549,7 +15858,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -15558,8 +15866,7 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { "version": "1.3.5", @@ -15749,7 +16056,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -15758,7 +16064,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -15822,7 +16127,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, "requires": { "ci-info": "^2.0.0" } @@ -15845,7 +16149,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -15854,7 +16157,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -15876,7 +16178,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -15886,8 +16187,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, @@ -15934,8 +16234,7 @@ "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { "version": "2.1.1", @@ -15989,7 +16288,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -15998,7 +16296,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -16027,7 +16324,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -16123,8 +16419,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "is-word-character": { "version": "1.0.4", @@ -16141,20 +16436,17 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isomorphic-fetch": { "version": "2.2.1", @@ -16300,14 +16592,14 @@ } }, "jest": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.3.tgz", - "integrity": "sha512-YImNJIOcQ5pguFpzZ14FQ+v2rannjrFP3R+SU/Wi2TyLQE5hZXcAbg9+WVl+lnEZHAZ99Jyh+tjhW59iMiI0pg==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", "dev": true, "requires": { - "@jest/core": "^25.5.3", + "@jest/core": "^25.5.4", "import-local": "^3.0.2", - "jest-cli": "^25.5.3" + "jest-cli": "^25.5.4" }, "dependencies": { "@jest/console": { @@ -16483,12 +16775,12 @@ "dev": true }, "jest-cli": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.3.tgz", - "integrity": "sha512-epjgZ0WeBECVXOQ11pn7Pc3n8SeGAqdWb+X2eSLoyCGieFEBAatB7bVI+UmyH/vsiOxpIb0qwby0QrrGMZMfcQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz", + "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==", "dev": true, "requires": { - "@jest/core": "^25.5.3", + "@jest/core": "^25.5.4", "@jest/test-result": "^25.5.0", "@jest/types": "^25.5.0", "chalk": "^3.0.0", @@ -16496,7 +16788,7 @@ "graceful-fs": "^4.2.4", "import-local": "^3.0.2", "is-ci": "^2.0.0", - "jest-config": "^25.5.3", + "jest-config": "^25.5.4", "jest-util": "^25.5.0", "jest-validate": "^25.5.0", "prompts": "^2.0.1", @@ -16882,13 +17174,13 @@ } }, "jest-config": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.3.tgz", - "integrity": "sha512-PrzL7wCP9XMEmUd+0ArcR37SVL7oF1lnIPxzppfeYvPytOtgTdXa2M/0TKcPYPHaa/T3UvHEpSNiC25yXktfsQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz", + "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==", "dev": true, "requires": { "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^25.5.3", + "@jest/test-sequencer": "^25.5.4", "@jest/types": "^25.5.0", "babel-jest": "^25.5.1", "chalk": "^3.0.0", @@ -16898,7 +17190,7 @@ "jest-environment-jsdom": "^25.5.0", "jest-environment-node": "^25.5.0", "jest-get-type": "^25.2.6", - "jest-jasmine2": "^25.5.2", + "jest-jasmine2": "^25.5.4", "jest-regex-util": "^25.2.6", "jest-resolve": "^25.5.1", "jest-util": "^25.5.0", @@ -16908,6 +17200,30 @@ "realpath-native": "^2.0.0" }, "dependencies": { + "@jest/transform": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", + "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^25.5.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^3.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-regex-util": "^25.2.6", + "jest-util": "^25.5.0", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, "@jest/types": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", @@ -16945,6 +17261,66 @@ "color-convert": "^2.0.1" } }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-jest": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", + "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", + "dev": true, + "requires": { + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", + "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-jest": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", + "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^25.5.0", + "babel-preset-current-node-syntax": "^0.1.2" + } + }, "braces": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", @@ -16994,6 +17370,13 @@ "to-regex-range": "^5.0.1" } }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, "graceful-fs": { "version": "4.2.4", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", @@ -17012,12 +17395,63 @@ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz", + "integrity": "sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@babel/parser": "^7.7.5", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "jest-haste-map": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", + "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "@types/graceful-fs": "^4.1.2", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-serializer": "^25.5.0", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7", + "which": "^2.0.2" + } + }, "jest-regex-util": { "version": "25.2.6", "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", "dev": true }, + "jest-serializer": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", + "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4" + } + }, "jest-util": { "version": "25.5.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", @@ -17031,6 +17465,16 @@ "make-dir": "^3.0.0" } }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -17074,6 +17518,18 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, "supports-color": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", @@ -17083,6 +17539,17 @@ "has-flag": "^4.0.0" } }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -17091,6 +17558,27 @@ "requires": { "is-number": "^7.0.0" } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } } } }, @@ -17849,44 +18337,170 @@ "color-convert": "^2.0.1" } }, - "chalk": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", - "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "jest-extended": { + "version": "0.11.5", + "resolved": "https://registry.npmjs.org/jest-extended/-/jest-extended-0.11.5.tgz", + "integrity": "sha512-3RsdFpLWKScpsLD6hJuyr/tV5iFOrw7v6YjA3tPdda9sJwoHwcMROws5gwiIZfcwhHlJRwFJB2OUvGmF3evV/Q==", + "dev": true, + "requires": { + "expect": "^24.1.0", + "jest-get-type": "^22.4.3", + "jest-matcher-utils": "^22.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + }, + "dependencies": { + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + } } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + }, + "dependencies": { + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + } } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "jest-get-type": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", + "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "jest-matcher-utils": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", + "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-get-type": "^22.4.3", + "pretty-format": "^22.4.3" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "pretty-format": { + "version": "22.4.3", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", + "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + } + } + } }, - "supports-color": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", - "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" } } } @@ -17928,9 +18542,9 @@ } }, "jest-jasmine2": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.2.tgz", - "integrity": "sha512-wRtHAy97F4hafJgnh5CwI/N1tDo7z+urteQAyr3rjK7X3TZWX5hSV4cO7WIBKLDV0kPICCmsGiNYs1caeHD/sQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz", + "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==", "dev": true, "requires": { "@babel/traverse": "^7.1.0", @@ -17945,7 +18559,7 @@ "jest-each": "^25.5.0", "jest-matcher-utils": "^25.5.0", "jest-message-util": "^25.5.0", - "jest-runtime": "^25.5.2", + "jest-runtime": "^25.5.4", "jest-snapshot": "^25.5.1", "jest-util": "^25.5.0", "pretty-format": "^25.5.0", @@ -18766,9 +19380,9 @@ } }, "jest-resolve-dependencies": { - "version": "25.5.2", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.2.tgz", - "integrity": "sha512-4xlPp6/SFFZj7g7WkhoKEEWsYqmAK6WcmFFRfDJ0K4T2f/MCJgFEPqv1F88ro6ZJdpOti08CxGku4gBwau/RjQ==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz", + "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==", "dev": true, "requires": { "@jest/types": "^25.5.0", @@ -18856,9 +19470,9 @@ } }, "jest-runner": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.3.tgz", - "integrity": "sha512-g4DyOG9hXdFhYL57bzVKWT6AuakcwDgvkB1RIie17mzF1EK4FFLSq9D/5hglgWtlhh6IH3CRDePIFetMHzLmCw==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz", + "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==", "dev": true, "requires": { "@jest/console": "^25.5.0", @@ -18868,14 +19482,14 @@ "chalk": "^3.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.4", - "jest-config": "^25.5.3", + "jest-config": "^25.5.4", "jest-docblock": "^25.3.0", "jest-haste-map": "^25.5.1", - "jest-jasmine2": "^25.5.2", + "jest-jasmine2": "^25.5.4", "jest-leak-detector": "^25.5.0", "jest-message-util": "^25.5.0", "jest-resolve": "^25.5.1", - "jest-runtime": "^25.5.3", + "jest-runtime": "^25.5.4", "jest-util": "^25.5.0", "jest-worker": "^25.5.0", "source-map-support": "^0.5.6", @@ -19146,9 +19760,9 @@ } }, "jest-runtime": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.3.tgz", - "integrity": "sha512-q6ajy2rtRDjIzSJm7J0g1kYG4MLlgfBeMNdaEKOa7z/JHLxEBz0ija67C9UioO1L0oWxiulH3Zt7iFhE8r3ITg==", + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz", + "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==", "dev": true, "requires": { "@jest/console": "^25.5.0", @@ -19164,7 +19778,7 @@ "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.4", - "jest-config": "^25.5.3", + "jest-config": "^25.5.4", "jest-haste-map": "^25.5.1", "jest-message-util": "^25.5.0", "jest-mock": "^25.5.0", @@ -20581,8 +21195,7 @@ "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "klaw": { "version": "1.3.1", @@ -20593,6 +21206,14 @@ "graceful-fs": "^4.1.9" } }, + "klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "requires": { + "graceful-fs": "^4.1.11" + } + }, "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -21296,8 +21917,7 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "4.1.0", @@ -21315,7 +21935,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -21359,18 +21978,18 @@ } }, "markdownlint": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.19.0.tgz", - "integrity": "sha512-+MsWOnYVUH4klcKM7iRx5cno9FQMDAb6FC6mWlZkeXPwIaK6Z5Vd9VkXkykPidRqmLHU2wI+MNyfUMnUCBw3pQ==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.2.tgz", + "integrity": "sha512-TU/SgsylEzp9oAj9dGGZ6009yJq48GpEAeYIsREje5NuvadzO12qvl4wV21vHIerdPy/aSEyM2e9c+CzWq6Reg==", "dev": true, "requires": { "markdown-it": "10.0.0" } }, "markdownlint-cli": { - "version": "0.22.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.22.0.tgz", - "integrity": "sha512-qRg6tK5dXWqkaFvEstz9YSQal1ECMgofrSZgdBOaPWG8cD50pk8Hs0ZpBCJ6SCHPKF71pCdtuSL2u82sIx2XWA==", + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.0.tgz", + "integrity": "sha512-qkr0OB86Q8PTIVUbzninYEhdEsj3h6rELNcyFEo5ejOkMZo55f03NwlWzGMGGWVz/L1GANN/HdidhpjMPv5vUg==", "dev": true, "requires": { "commander": "~2.9.0", @@ -21382,9 +22001,10 @@ "jsonc-parser": "~2.2.0", "lodash.differencewith": "~4.5.0", "lodash.flatten": "~4.4.0", - "markdownlint": "~0.19.0", - "markdownlint-rule-helpers": "~0.7.0", + "markdownlint": "~0.20.2", + "markdownlint-rule-helpers": "~0.9.0", "minimatch": "~3.0.4", + "minimist": "~1.2.5", "rc": "~1.2.7" }, "dependencies": { @@ -21412,9 +22032,9 @@ } }, "markdownlint-rule-helpers": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.7.0.tgz", - "integrity": "sha512-xZByWJNBaCMHo7nYPv/5aO8Jt68YcMvyouFXhuXmJzbqCsQy8rfCj0kYcv22kdK5PwAgMdbHg0hyTdURbUZtJw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.9.0.tgz", + "integrity": "sha512-ySKvg7tlOAxLzGZyu3J3pKGGNHAQgQNcZOMFRd2b5MrhA00j40XYL1mtAI34NzCbVfvhnPPynh5AsJoL+uRHcw==", "dev": true }, "material-colors": { @@ -21723,7 +22343,6 @@ "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -21818,7 +22437,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -21826,8 +22444,7 @@ "minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "minimist-options": { "version": "4.0.2", @@ -21891,6 +22508,24 @@ "minipass": "^3.0.0" } }, + "minizlib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz", + "integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==", + "dev": true, + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -21913,7 +22548,6 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -21923,7 +22557,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -21964,9 +22597,9 @@ "dev": true }, "moment": { - "version": "2.25.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.0.tgz", - "integrity": "sha512-vbrf6kJGpevOxmDRvCCvGuCSXvRj93264WcFzjm3Z3pV4lfjrXll8rvSP+EbmCte64udj1LkJMILxQnjXAQBzg==" + "version": "2.25.3", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.25.3.tgz", + "integrity": "sha512-PuYv0PHxZvzc15Sp8ybUCoQ+xpyPWvjOuK72a5ovzp2LI32rJXOiIfyoFoYvG3s6EwwrdkMyWuRiEHSZRLJNdg==" }, "mousetrap": { "version": "1.6.5", @@ -21987,6 +22620,23 @@ "run-queue": "^1.0.3" } }, + "move-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/move-file/-/move-file-2.0.0.tgz", + "integrity": "sha512-cdkdhNCgbP5dvS4tlGxZbD+nloio9GIimP57EjqFhwLcMjnU+XJKAZzlmg/TN/AK1LuNAdTSvm3CPPP4Xkv0iQ==", + "dev": true, + "requires": { + "path-exists": "^4.0.0" + }, + "dependencies": { + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + } + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -22009,7 +22659,6 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -22063,8 +22712,7 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "no-case": { "version": "3.0.3", @@ -22331,7 +22979,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -22342,7 +22989,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -22351,7 +22997,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -22377,7 +23022,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" } @@ -22430,7 +23074,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -22464,7 +23107,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -22567,8 +23209,7 @@ "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "p-defer": { "version": "1.0.0", @@ -22613,9 +23254,9 @@ } }, "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" @@ -22768,8 +23409,46 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + }, + "patch-package": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-6.2.2.tgz", + "integrity": "sha512-YqScVYkVcClUY0v8fF0kWOjDYopzIM8e3bj/RU1DPeEF14+dCGm6UeOYm4jvCyxqIEQ5/eJzmbWfDWnUleFNMg==", + "requires": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^2.4.2", + "cross-spawn": "^6.0.5", + "find-yarn-workspace-root": "^1.2.1", + "fs-extra": "^7.0.1", + "is-ci": "^2.0.0", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.0", + "rimraf": "^2.6.3", + "semver": "^5.6.0", + "slash": "^2.0.0", + "tmp": "^0.0.33" + }, + "dependencies": { + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + } + } }, "path-browserify": { "version": "0.0.1", @@ -22792,14 +23471,12 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.6", @@ -22963,11 +23640,21 @@ } }, "polished": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/polished/-/polished-3.5.2.tgz", - "integrity": "sha512-vWoRDg3gY5RQBtUfcj9MRN10VCIf4EkdUikGxyXItg2Hnwk+eIVtdBiLajN0ldFeT3Vq4r/QNbjrQdhqBKrTug==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/polished/-/polished-3.6.2.tgz", + "integrity": "sha512-V0dyaVJUM5e5wIhLHvZyyE9PhXoI0AlGT6RDww1V/v+STsocLsVGWmi+9OKTL49oaQj85XFDvsWi/uHQJ0rpWg==", "requires": { - "@babel/runtime": "^7.8.7" + "@babel/runtime": "^7.9.2" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", + "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "requires": { + "regenerator-runtime": "^0.13.4" + } + } } }, "popper.js": { @@ -22979,8 +23666,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "7.0.27", @@ -24313,9 +24999,9 @@ } }, "react-color": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.0.tgz", - "integrity": "sha512-FyVeU1kQiSokWc8NPz22azl1ezLpJdUyTbWL0LPUpcuuYDrZ/Y1veOk9rRK5B3pMlyDGvTk4f4KJhlkIQNRjEA==", + "version": "2.18.1", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.18.1.tgz", + "integrity": "sha512-X5XpyJS6ncplZs74ak0JJoqPi+33Nzpv5RYWWxn17bslih+X7OlgmfpmGC1fNvdkK7/SGWYf1JJdn7D2n5gSuQ==", "requires": { "@icons/material": "^0.2.4", "lodash": "^4.17.11", @@ -24781,12 +25467,12 @@ } }, "react-moveable": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/react-moveable/-/react-moveable-0.19.2.tgz", - "integrity": "sha512-11N8ucq8Q+U0Nrv6/qcq0/ayOqDr2Km5zhy5wtBodHa4uRqMCyx+AhYS02//EY5RD7lh8k3fG+m36s3vmegp0A==", + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/react-moveable/-/react-moveable-0.20.3.tgz", + "integrity": "sha512-j5ct2tvPh4LbplrlKsqM9yCBMhNv4Ehv0E6RbehMv2fObIUR57OZDcIqwm9k4Fkn8vkRO3SPMmAyoLarvDOBog==", "requires": { - "@daybrush/drag": "^0.13.1", - "@daybrush/utils": "^0.10.1", + "@daybrush/drag": "^0.14.0", + "@daybrush/utils": "^0.10.3", "@egjs/agent": "^2.1.5", "@egjs/children-differ": "^1.0.0", "@moveable/matrix": "^0.3.1", @@ -24953,9 +25639,9 @@ } }, "react-transition-group": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz", - "integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.0.tgz", + "integrity": "sha512-c7wuNGJlHItwt6+HS0e9r53ZD5ARJDJSRYBv0APmfQqSCSokdrgJm07IO0ZKwjTVkIQKOlSTO3F06A3JqUSl7Q==", "requires": { "@babel/runtime": "^7.5.5", "dom-helpers": "^5.0.1", @@ -25186,7 +25872,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -25586,14 +26271,12 @@ "repeat-element": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { "version": "1.0.0", @@ -25769,8 +26452,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "restore-cursor": { "version": "3.1.0", @@ -25785,8 +26467,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "reusify": { "version": "1.0.4", @@ -25819,7 +26500,6 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -25960,7 +26640,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -26033,8 +26712,7 @@ "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" }, "semver-compare": { "version": "1.0.0", @@ -26149,7 +26827,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -26161,7 +26838,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -26233,7 +26909,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -26241,8 +26916,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shell-quote": { "version": "1.7.2", @@ -26342,8 +27016,7 @@ "slash": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "dev": true + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" }, "slice-ansi": { "version": "2.1.0", @@ -26368,7 +27041,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -26384,7 +27056,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -26393,7 +27064,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -26402,7 +27072,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -26410,8 +27079,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -26419,7 +27087,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -26430,7 +27097,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -26439,7 +27105,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -26448,7 +27113,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -26457,7 +27121,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -26470,7 +27133,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" }, @@ -26479,7 +27141,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -26553,7 +27214,6 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, "requires": { "atob": "^2.1.2", "decode-uri-component": "^0.2.0", @@ -26583,8 +27243,7 @@ "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" }, "space-separated-tokens": { "version": "1.1.5", @@ -26651,7 +27310,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -26708,7 +27366,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -26718,7 +27375,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -27698,6 +28354,40 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz", + "integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==", + "dev": true, + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.0", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, "tar-fs": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", @@ -27811,48 +28501,53 @@ } }, "terser-webpack-plugin": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-2.3.6.tgz", - "integrity": "sha512-I8IDsQwZrqjdmOicNeE8L/MhwatAap3mUrtcAKJuilsemUNcX+Hier/eAzwStVqhlCxq0aG3ni9bK/0BESXkTg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-3.0.1.tgz", + "integrity": "sha512-eFDtq8qPUEa9hXcUzTwKXTnugIVtlqc1Z/ZVhG8LmRT3lgRY13+pQTnFLY2N7ATB6TKCHuW/IGjoAnZz9wOIqw==", "dev": true, "requires": { - "cacache": "^13.0.1", + "cacache": "^15.0.3", "find-cache-dir": "^3.3.1", - "jest-worker": "^25.4.0", + "jest-worker": "^26.0.0", "p-limit": "^2.3.0", "schema-utils": "^2.6.6", "serialize-javascript": "^3.0.0", "source-map": "^0.6.1", - "terser": "^4.6.12", + "terser": "^4.6.13", "webpack-sources": "^1.4.3" }, "dependencies": { "cacache": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz", - "integrity": "sha512-5ZvAxd05HDDU+y9BVvcqYu2LLXmPnQ0hW62h32g4xBTgL/MppR4/04NHfj/ycM2y6lmTnbw6HVi+1eN0Psba6w==", + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.3.tgz", + "integrity": "sha512-bc3jKYjqv7k4pWh7I/ixIjfcjPul4V4jme/WbjvwGS5LzoPL/GzXr4C5EgPNLO/QEZl9Oi61iGitYEdwcrwLCQ==", "dev": true, "requires": { - "chownr": "^1.1.2", - "figgy-pudding": "^3.5.1", + "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", - "graceful-fs": "^4.2.2", "infer-owner": "^1.0.4", "lru-cache": "^5.1.1", - "minipass": "^3.0.0", + "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "p-map": "^3.0.0", + "mkdirp": "^1.0.3", + "move-file": "^2.0.0", + "p-map": "^4.0.0", "promise-inflight": "^1.0.1", - "rimraf": "^2.7.1", - "ssri": "^7.0.0", + "rimraf": "^3.0.2", + "ssri": "^8.0.0", + "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -27887,9 +28582,9 @@ "dev": true }, "jest-worker": { - "version": "25.4.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.4.0.tgz", - "integrity": "sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz", + "integrity": "sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw==", "dev": true, "requires": { "merge-stream": "^2.0.0", @@ -27923,6 +28618,12 @@ "semver": "^6.0.0" } }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -27957,9 +28658,9 @@ } }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -27994,12 +28695,11 @@ "dev": true }, "ssri": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-7.1.0.tgz", - "integrity": "sha512-77/WrDZUWocK0mvA5NTRQyveUf+wsrIc6vyrxpS8tVvYBcX215QbafrJR3KtkpskIzoFLqqNuuYQvxaMjXJ/0g==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "dev": true, "requires": { - "figgy-pudding": "^3.5.1", "minipass": "^3.1.1" } }, @@ -28013,9 +28713,9 @@ } }, "terser": { - "version": "4.6.12", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.12.tgz", - "integrity": "sha512-fnIwuaKjFPANG6MAixC/k1TDtnl1YlPLUlLVIxxGZUn1gfUx2+l3/zGNB72wya+lgsb50QBi2tUV75RiODwnww==", + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.13.tgz", + "integrity": "sha512-wMvqukYgVpQlymbnNbabVZbtM6PN63AzqexpwJL8tbh/mRT9LE5o+ruVduAGL7D6Fpjl+Q+06U5I9Ul82odAhw==", "dev": true, "requires": { "commander": "^2.20.0", @@ -28128,7 +28828,6 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, "requires": { "os-tmpdir": "~1.0.2" } @@ -28154,7 +28853,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -28163,7 +28861,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -28174,7 +28871,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -28186,7 +28882,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -28486,7 +29181,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -28643,8 +29337,7 @@ "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "dev": true + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" }, "unpipe": { "version": "1.0.0", @@ -28662,7 +29355,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -28672,7 +29364,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -28683,7 +29374,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -28693,8 +29383,7 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" } } }, @@ -28715,8 +29404,7 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", @@ -28778,8 +29466,7 @@ "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, "use-callback-ref": { "version": "1.2.1", @@ -29592,7 +30279,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -29775,8 +30461,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "write": { "version": "1.0.3", diff --git a/package.json b/package.json index 0511a376592d..7f324c085a19 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ }, "dependencies": { "@wordpress/api-fetch": "^3.14.0", - "@wordpress/element": "^2.13.1", "@wordpress/i18n": "^3.12.0", "colorthief": "^2.3.0", "draft-js": "^0.11.5", @@ -29,18 +28,19 @@ "draft-js-import-html": "^1.4.1", "draftjs-filters": "^2.3.0", "history": "^4.10.1", - "moment": "^2.25.0", + "moment": "^2.25.3", "mousetrap": "^1.6.5", - "polished": "^3.5.2", + "patch-package": "^6.2.2", + "polished": "^3.6.2", "prop-types": "^15.7.2", "query-string": "^6.12.1", "react": "^16.13.1", - "react-color": "^2.18.0", + "react-color": "^2.18.1", "react-dates": "^21.8.0", "react-dom": "^16.13.1", "react-modal": "^3.11.2", - "react-moveable": "^0.19.2", - "react-transition-group": "^4.3.0", + "react-moveable": "0.20.3", + "react-transition-group": "^4.4.0", "resize-observer-polyfill": "^1.5.1", "styled-components": "^5.1.0", "use-debounce": "^3.4.2", @@ -66,7 +66,7 @@ "@storybook/client-api": "^5.3.18", "@storybook/react": "^5.3.18", "@svgr/webpack": "^5.4.0", - "@testing-library/dom": "^7.2.2", + "@testing-library/dom": "^7.3.0", "@testing-library/jest-dom": "^5.5.0", "@testing-library/react": "^10.0.4", "@testing-library/react-hooks": "^3.2.1", @@ -78,7 +78,7 @@ "@wordpress/jest-puppeteer-axe": "^1.8.0", "amphtml-validator": "^1.0.30", "babel-eslint": "^10.1.0", - "babel-jest": "^25.5.1", + "babel-jest": "^26.0.1", "babel-loader": "^8.1.0", "babel-plugin-styled-components": "^1.10.7", "babel-plugin-transform-react-remove-prop-types": "^0.4.24", @@ -98,17 +98,18 @@ "eslint-plugin-markdown": "^1.0.2", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-react": "^7.19.0", - "eslint-plugin-testing-library": "^3.0.4", + "eslint-plugin-testing-library": "^3.1.0", "eslint-utils": "^2.0.0", "husky": "^4.2.5", - "jest": "^25.5.3", + "jest": "^25.5.4", "jest-canvas-mock": "^2.2.0", + "jest-extended": "^0.11.5", "jest-fetch-mock": "^3.0.3", "jest-matcher-deep-close-to": "^2.0.0", "jest-puppeteer": "^4.4.0", "jest-silent-reporter": "^0.2.1", "lint-staged": "^10.2.2", - "markdownlint-cli": "^0.22.0", + "markdownlint-cli": "^0.23.0", "mini-css-extract-plugin": "^0.9.0", "npm-run-all": "^4.1.5", "optimize-css-assets-webpack-plugin": "^5.0.3", @@ -124,7 +125,7 @@ "stylelint-prettier": "^1.1.2", "stylelint-processor-styled-components": "^1.10.0", "stylis-plugin-rtl": "^1.0.0", - "terser-webpack-plugin": "^2.3.6", + "terser-webpack-plugin": "^3.0.1", "thread-loader": "^2.1.3", "webpack": "^4.43.0", "webpack-bundle-analyzer": "^3.7.0", @@ -137,7 +138,7 @@ "build:js": "NODE_ENV=production webpack", "build:plugin": "./bin/commander.js build", "build:storybook": "build-storybook -c .storybook -o build/storybook --quiet", - "prebuild:fonts": "[[ ! -z $GOOGLE_FONTS_API_KEY ]] && curl -s \"https://www.googleapis.com/webfonts/v1/webfonts?fields=items&prettyPrint=false&key=$GOOGLE_FONTS_API_KEY\" | jq -c '[.items[] | { family: .family, variants: .variants, category: .category }]' > includes/data/fonts.json", + "prebuild:fonts": "curl -s \"https://www.googleapis.com/webfonts/v1/webfonts?fields=items&prettyPrint=false&key=$GOOGLE_FONTS_API_KEY\" | jq -c '[.items[] | { family: .family, variants: .variants, category: .category }]' > includes/data/fonts.json", "build:fonts": "./bin/build-fonts.js", "bundle-plugin": "./bin/commander.js bundle", "predev": "rm -rf assets/css/* assets/js/*", @@ -161,6 +162,7 @@ "lint:php": "vendor/bin/phpcs", "lint:php:fix": "vendor/bin/phpcbf", "lint:md": "markdownlint .", + "postinstall": "npx patch-package", "storybook": "start-storybook --quiet", "test": "npm-run-all --parallel test:*", "test:js": "jest --config=tests/js/jest.config.js", diff --git a/patches/draft-js-import-element+1.4.0.patch b/patches/draft-js-import-element+1.4.0.patch new file mode 100644 index 000000000000..25f49e233fb5 --- /dev/null +++ b/patches/draft-js-import-element+1.4.0.patch @@ -0,0 +1,26 @@ +diff --git a/node_modules/draft-js-import-element/esm/stateFromElement.js b/node_modules/draft-js-import-element/esm/stateFromElement.js +index eccabfd..9ec7496 100644 +--- a/node_modules/draft-js-import-element/esm/stateFromElement.js ++++ b/node_modules/draft-js-import-element/esm/stateFromElement.js +@@ -373,7 +373,7 @@ function () { + switch (customInline.type) { + case 'STYLE': + { +- style = style.add(customInline.style); ++ [].concat(customInline.style).forEach(customStyle => { style = style.add(customStyle); }); + break; + } + +diff --git a/node_modules/draft-js-import-element/lib/stateFromElement.js b/node_modules/draft-js-import-element/lib/stateFromElement.js +index e814c05..c469bda 100644 +--- a/node_modules/draft-js-import-element/lib/stateFromElement.js ++++ b/node_modules/draft-js-import-element/lib/stateFromElement.js +@@ -389,7 +389,7 @@ function () { + switch (customInline.type) { + case 'STYLE': + { +- style = style.add(customInline.style); ++ [].concat(customInline.style).forEach(customStyle => { style = style.add(customStyle); }); + break; + } + diff --git a/tests/js/jest.setup.js b/tests/js/jest.setup.js index e690574b36f8..3d3330af6691 100644 --- a/tests/js/jest.setup.js +++ b/tests/js/jest.setup.js @@ -19,7 +19,9 @@ */ // Extend Jest matchers. // See https://github.com/testing-library/jest-dom. +import 'jest-extended'; import '@testing-library/jest-dom'; +import 'jest-extended'; /** * Internal dependencies diff --git a/tests/phpunit/tests/REST_API/Stories_Controller.php b/tests/phpunit/tests/REST_API/Stories_Controller.php index 694a9d94d699..08b48f7dca03 100644 --- a/tests/phpunit/tests/REST_API/Stories_Controller.php +++ b/tests/phpunit/tests/REST_API/Stories_Controller.php @@ -23,6 +23,20 @@ class Stories_Controller extends \WP_Test_REST_TestCase { protected $server; + protected static $user_id; + + public static function wpSetUpBeforeClass( $factory ) { + self::$user_id = $factory->user->create( + [ + 'role' => 'administrator', + ] + ); + } + + public static function wpTearDownAfterClass() { + self::delete_user( self::$user_id ); + } + public function setUp() { parent::setUp(); @@ -58,4 +72,47 @@ public function test_get_item_schema() { $this->assertArrayHasKey( 'story_data', $properties ); $this->assertArrayHasKey( 'featured_media_url', $properties ); } + + public function test_filter_posts_orderby() { + wp_set_current_user( self::$user_id ); + do_action( 'init' ); + $controller = new \Google\Web_Stories\REST_API\Stories_Controller( \Google\Web_Stories\Story_Post_Type::POST_TYPE_SLUG ); + + $initial_orderby = 'foo bar'; + + $query = new \WP_Query(); + $query->set( 'post_type', \Google\Web_Stories\Story_Post_Type::POST_TYPE_SLUG ); + $query->set( 'orderby', 'story_author' ); + + $orderby = $controller->filter_posts_orderby( $initial_orderby, $query ); + + $this->assertEquals( $orderby, "wp_posts.post_author = '" . self::$user_id . "' DESC, wp_posts.post_author DESC" ); + } + + public function test_filter_posts_orderby_irrelevant_query() { + do_action( 'init' ); + $controller = new \Google\Web_Stories\REST_API\Stories_Controller( \Google\Web_Stories\Story_Post_Type::POST_TYPE_SLUG ); + + $initial_orderby = 'foo bar'; + + $query = new \WP_Query(); + $query->set( 'post_type', 'foo' ); + $query->set( 'orderby', 'story_author' ); + + $orderby = $controller->filter_posts_orderby( $initial_orderby, $query ); + $this->assertEquals( $orderby, $initial_orderby ); + + $query->set( 'post_type', \Google\Web_Stories\Story_Post_Type::POST_TYPE_SLUG ); + $query->set( 'orderby', 'author' ); + + $orderby = $controller->filter_posts_orderby( $initial_orderby, $query ); + $this->assertEquals( $orderby, $initial_orderby ); + + $query->set( 'orderby', 'story_author' ); + wp_set_current_user( 0 ); + + $orderby = $controller->filter_posts_orderby( $initial_orderby, $query ); + $this->assertEquals( $orderby, $initial_orderby ); + + } } diff --git a/tests/phpunit/tests/Story_Post_Type.php b/tests/phpunit/tests/Story_Post_Type.php new file mode 100644 index 000000000000..c8244984e705 --- /dev/null +++ b/tests/phpunit/tests/Story_Post_Type.php @@ -0,0 +1,97 @@ +user->create( + [ 'role' => 'administrator' ] + ); + self::$subscriber_id = $factory->user->create( + [ 'role' => 'subscriber' ] + ); + } + + public function setUp() { + parent::setUp(); + do_action( 'init' ); + } + + public function test_get_editor_settings_admin() { + wp_set_current_user( self::$admin_id ); + $post_type = new \Google\Web_Stories\Story_Post_Type(); + $results = $post_type::get_editor_settings(); + $this->assertTrue( $results['config']['capabilities']['hasUploadMediaAction'] ); + } + + public function test_get_editor_settings_sub() { + wp_set_current_user( self::$subscriber_id ); + $post_type = new \Google\Web_Stories\Story_Post_Type(); + $results = $post_type::get_editor_settings(); + $this->assertFalse( $results['config']['capabilities']['hasUploadMediaAction'] ); + } + + public function test_filter_rest_collection_params() { + $query_params = [ + 'foo', + 'orderby' => [ + 'enum' => [], + ], + ]; + + $post_type = get_post_type_object( \Google\Web_Stories\Story_Post_Type::POST_TYPE_SLUG ); + $filtered_params = \Google\Web_Stories\Story_Post_Type::filter_rest_collection_params( $query_params, $post_type ); + $this->assertEquals( + $filtered_params, + [ + 'foo', + 'orderby' => [ + 'enum' => [ 'story_author' ], + ], + ] + ); + } + + public function test_filter_rest_collection_params_incorrect_post_type() { + $query_params = [ + 'foo', + 'orderby' => [ + 'enum' => [], + ], + ]; + + $post_type = new \stdClass(); + $post_type->name = 'post'; + $filtered_params = \Google\Web_Stories\Story_Post_Type::filter_rest_collection_params( $query_params, $post_type ); + $this->assertEquals( $filtered_params, $query_params ); + } +} diff --git a/tests/phpunit/tests/Story_Renderer.php b/tests/phpunit/tests/Story_Renderer.php index 164a4bb00f99..212ba0dff791 100644 --- a/tests/phpunit/tests/Story_Renderer.php +++ b/tests/phpunit/tests/Story_Renderer.php @@ -18,7 +18,6 @@ namespace Google\Web_Stories\Tests; use Google\Web_Stories\REST_API\Stories_Controller; -use Google\Web_Stories\Story_Post_Type; class Story_Renderer extends \WP_UnitTestCase { protected static $user; @@ -215,9 +214,10 @@ public function test_maybe_add_analytics_remove_with_filter() { * @covers \Google\Web_Stories\Story_Renderer::add_publisher_logo */ public function test_add_publisher_logo() { + $placeholder = \Google\Web_Stories\Story_Post_Type::PUBLISHER_LOGO_PLACEHOLDER; $post_with_publisher_logo = self::factory()->post->create_and_get( [ - 'post_content' => '', + 'post_content' => '', ] ); @@ -229,6 +229,7 @@ public function test_add_publisher_logo() { delete_option( Stories_Controller::PUBLISHER_LOGOS_OPTION ); $this->assertContains( 'attachment', $rendered ); - $this->assertNotContains( Story_Post_Type::PUBLISHER_LOGO_PLACEHOLDER, $rendered ); + $this->assertNotContains( $placeholder, $rendered ); + } }