Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20778999a2 | |||
| 3c39f49a84 | |||
| 344ce55e67 | |||
| 73be6c050b | |||
| f23c745a15 | |||
| 6ddc9b9910 | |||
| b5d23d612f | |||
| 7be17a28fa | |||
| c5502e2be2 | |||
| a9597684de | |||
| d1bbffce2e | |||
| 12ef5e0570 | |||
| c48420249b | |||
| 82d4f1b755 | |||
| f8a46b8148 | |||
| 8c75133759 | |||
| 1d214ca60f | |||
| ff33974b78 | |||
| 212e0a414b | |||
| e407dcd0ca | |||
| 7d168b27ee | |||
| c570cd9c75 | |||
| 86f64e8b4b | |||
| 9ac3a19b2f | |||
| 89e24c9285 | |||
| f9d30546ca | |||
| 7201d360ee | |||
| 7eeafdbd31 | |||
| 1d3aec4dff | |||
| 1132790fd8 | |||
| 5672912f04 | |||
| fe95eb0ef3 | |||
| 86602b0ab8 | |||
| 49cc9f685c | |||
| 0ace3de83e | |||
| 38629e7fbb | |||
| 44dc99c641 | |||
| dfcd9681bc | |||
| 29a75a6e26 | |||
| 0ec37c4c5b | |||
| 56839fee2e |
@@ -10,8 +10,4 @@ deploy-*.sh
|
||||
|
||||
# Custom
|
||||
blocks
|
||||
config
|
||||
data
|
||||
src
|
||||
exports
|
||||
block.json
|
||||
|
||||
@@ -5,9 +5,11 @@ import config from 'config';
|
||||
import Generator from "yeoman-generator";
|
||||
import yeoman from 'yeoman-environment';
|
||||
import {buildHubspot} from "./platforms/hubspot/hubspot-adapter.js";
|
||||
import {getConfigs} from "./helpers.js";
|
||||
import {buildWordPress} from "./platforms/wordpress/wordpress-adapter.js";
|
||||
import path from "path";
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
||||
const modulePath = isDev ? '' : 'node_modules/create-block-dev-tool/';
|
||||
const {modulesPath, projectPath} = getConfigs();
|
||||
|
||||
const blockName = config.has('blockName') ? config.get('blockName') : 'development';
|
||||
|
||||
@@ -18,7 +20,7 @@ class buildGenerator extends Generator {
|
||||
type: "list",
|
||||
name: "platform",
|
||||
message: "Choose Platform",
|
||||
choices: ['WordPress', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
|
||||
choices: ['WordPress', 'WordPress Block', 'Hubspot', 'Hubspot Email', 'JavaScript', 'PHP'],
|
||||
default: 'WordPress'
|
||||
}
|
||||
])
|
||||
@@ -27,11 +29,29 @@ class buildGenerator extends Generator {
|
||||
writing() {
|
||||
new Promise((resolve => {
|
||||
if (['WordPress', 'PHP'].includes(this.data.platform)) {
|
||||
const backPath = modulePath ? modulePath.substr(-1).split('/').map(() => '..').join('/') : '';
|
||||
exec(`cd ${modulePath}platforms/php && composer install && php build.php '${blockName}' '${backPath}'`, function (error, stdout) {
|
||||
const backPath = modulesPath ? modulesPath.split('/').map(() => '..').join('/') : '';
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const phpGeneratorPath = path.join(modulesPath, 'platforms', 'php');
|
||||
exec(`cd ${phpGeneratorPath} && composer install && php build.php '${blockName}' '${backPath}' '${projectPath}'`, function (error, stdout) {
|
||||
if (error) {
|
||||
console.log('Error:', error)
|
||||
reject(error);
|
||||
}
|
||||
|
||||
console.log(stdout);
|
||||
resolve();
|
||||
});
|
||||
}).then(() => {
|
||||
if (this.data.platform === 'WordPress') {
|
||||
return buildWordPress();
|
||||
}
|
||||
|
||||
if (this.data.platform === 'WordPress Block') {
|
||||
return buildWordPress(true);
|
||||
}
|
||||
});
|
||||
|
||||
} else if (this.data.platform === 'Hubspot Email') {
|
||||
buildHubspot(blockName)
|
||||
.then(() => {
|
||||
@@ -50,6 +70,5 @@ class buildGenerator extends Generator {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const build = new buildGenerator([], {env: yeoman.createEnv()}, {});
|
||||
build.run().then(() => null);
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import {Command} from 'commander';
|
||||
import path from 'path';
|
||||
import fetch from "node-fetch";
|
||||
import fs from "fs";
|
||||
import http from "http";
|
||||
import https from "https";
|
||||
import unzipper from "unzipper";
|
||||
import {createTechnicalFiles, defaultGitRepo} from "./generators/block/index.js";
|
||||
import {fileURLToPath} from 'url';
|
||||
import config from 'config';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
|
||||
const blocksDirectory = isDev ? 'blocks/' : '';
|
||||
|
||||
try {
|
||||
const blockName = await init();
|
||||
console.log(`🎉 Done! \n\nCheck the "${blocksDirectory}${blockName}" directory. \n`);
|
||||
} catch (e) {
|
||||
console.log('Fail.');
|
||||
}
|
||||
|
||||
async function init() {
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('create-block')
|
||||
.description('AXE-WEB Platform Blocks');
|
||||
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
program.command('pull')
|
||||
.argument('<string>', 'Provide a full name of required block, for example: @axe-web/hero-block')
|
||||
.action(async (blockName, options) => {
|
||||
console.log('📦 Block to download:', blockName)
|
||||
|
||||
const status = await getBlockSourceFiles(blockName);
|
||||
if (status) {
|
||||
resolve(blockName);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
program.parse();
|
||||
return promise;
|
||||
}
|
||||
|
||||
async function getBlockSourceFiles(blockName) {
|
||||
const queryString = new URLSearchParams();
|
||||
queryString.append('blockName', blockName);
|
||||
queryString.append('includeDevConfig', 'true');
|
||||
|
||||
const response = await fetch(`${blocksRegistry}?${queryString.toString()}`);
|
||||
const responseData = await response.json();
|
||||
if (!responseData || !responseData.name) {
|
||||
console.log("⚠️ Block not found, please contact administrator.");
|
||||
return;
|
||||
}
|
||||
|
||||
const zipFile = await downloadFile(responseData.downloadUrl, responseData.name + '.zip');
|
||||
await fs.createReadStream(zipFile)
|
||||
.pipe(unzipper.Extract({path: `${blocksDirectory}${responseData.name}/src`}))
|
||||
.promise();
|
||||
|
||||
// Remove downloaded file.
|
||||
try {
|
||||
await fs.promises.access(zipFile, fs.constants.W_OK);
|
||||
await fs.promises.unlink(zipFile);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
||||
if (responseData.statusCode && responseData.statusCode !== 200) {
|
||||
console.log(responseData);
|
||||
console.log("⚠️ [ERROR]", responseData.message || "Server side error.");
|
||||
return;
|
||||
}
|
||||
|
||||
await createTechnicalFiles({
|
||||
title: responseData.title,
|
||||
name: responseData.name,
|
||||
blockFilename: responseData.name,
|
||||
blockGroupName: responseData.project,
|
||||
version: responseData.version,
|
||||
devToolSource: defaultGitRepo,
|
||||
}, __dirname, `${blocksDirectory}${responseData.name}`);
|
||||
|
||||
if (responseData.config.design_files) {
|
||||
await downloadDesignFiles(responseData.name, responseData.config.design_files);
|
||||
}
|
||||
|
||||
await createDataFiles(responseData.name, responseData.config.data_sources);
|
||||
|
||||
await createConfigFile(responseData.name, responseData.config);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async function createConfigFile(blockName, config = {}) {
|
||||
const obj = {
|
||||
blockName,
|
||||
baseView: config.baseView,
|
||||
remToPx: config.remToPx,
|
||||
};
|
||||
|
||||
const cssAssets = config.assets.filter(item => item.type === 'css').map(item => item.url);
|
||||
if (cssAssets.length) {
|
||||
obj.cssUrl = cssAssets;
|
||||
}
|
||||
|
||||
const jsAssets = config.assets.filter(item => item.type === 'js').map(item => item.url);
|
||||
if (jsAssets.length) {
|
||||
obj.jsUrl = jsAssets;
|
||||
}
|
||||
|
||||
await setupPath(`${blocksDirectory}${blockName}/config`);
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/config/default.json`, JSON.stringify(obj, null, 2));
|
||||
}
|
||||
|
||||
async function downloadDesignFiles(blockName = '', files) {
|
||||
for (let url of files) {
|
||||
const fileName = url.split('/').pop();
|
||||
await setupPath(`${blocksDirectory}${blockName}/design`);
|
||||
await downloadFile(url, `${blocksDirectory}${blockName}/design/${fileName}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function createDataFiles(blockName, dataSources = []) {
|
||||
for (let source of dataSources) {
|
||||
await setupPath(`${blocksDirectory}${blockName}/data`);
|
||||
|
||||
await fs.promises.writeFile(`${blocksDirectory}${blockName}/data/${source.name}.json`, JSON.stringify(source.data, null, 2));
|
||||
|
||||
if (typeof source.preview_images !== 'undefined' && source.preview_images.length) {
|
||||
for (let preview_image of source.preview_images) {
|
||||
await setupPath(`${blocksDirectory}${blockName}/design`);
|
||||
await downloadFile(preview_image.url, `${blocksDirectory}${blockName}/design/${source.name}-${preview_image.width}.${preview_image.extension}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
async function setupPath(path) {
|
||||
if (!fs.existsSync(path)) {
|
||||
await fs.promises.mkdir(path);
|
||||
}
|
||||
}
|
||||
|
||||
async function downloadFile(url, fileName) {
|
||||
const file = fs.createWriteStream(fileName);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocol = url.startsWith('https://') ? https : http;
|
||||
protocol.get(url, function (response) {
|
||||
response.pipe(file);
|
||||
|
||||
// Loading Indicator.
|
||||
const loadingInterval = setInterval(() => console.log('🕐 Download in progress...'), 3000);
|
||||
|
||||
// after download completed close filestream
|
||||
file.on("finish", () => {
|
||||
clearInterval(loadingInterval);
|
||||
|
||||
file.close();
|
||||
resolve(fileName);
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
import path from 'path';
|
||||
import Generator from "yeoman-generator";
|
||||
import mkdirp from "mkdirp";
|
||||
import {fileURLToPath} from 'url';
|
||||
import {capitalize} from "../../helpers.js";
|
||||
import memFs from 'mem-fs';
|
||||
import editor from 'mem-fs-editor';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const baseDir = path.join(__dirname, '../../');
|
||||
|
||||
export const defaultGitRepo = 'git+https://git.devdevdev.life/axe-web-public/create-block.git#master';
|
||||
|
||||
export default class extends Generator {
|
||||
async prompting() {
|
||||
this.data = await this.prompt([
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Block Name",
|
||||
validate: (str) => {
|
||||
const matches = str.match(/\d+/g);
|
||||
|
||||
if (matches != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!str;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "group",
|
||||
message: "Company/Organization Name",
|
||||
validate: (str) => {
|
||||
const matches = str.match(/\d+/g);
|
||||
|
||||
if (matches != null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!str;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
name: "baseView",
|
||||
message: "View Template",
|
||||
default: 'container',
|
||||
choices: ['container', 'alignfull'],
|
||||
},
|
||||
{
|
||||
type: "number",
|
||||
name: "remToPx",
|
||||
message: "Provide declaration of 1rem:",
|
||||
default: 16
|
||||
},
|
||||
{
|
||||
type: 'input',
|
||||
name: 'devToolSource',
|
||||
message: 'DevTool Version/Source (master)',
|
||||
default: defaultGitRepo
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
writing() {
|
||||
const title = capitalize(this.data.name);
|
||||
const group = capitalize(this.data.group);
|
||||
const data = Object.assign(this.data, {
|
||||
title,
|
||||
version: '1.0.0',
|
||||
blockFilename: title.toLowerCase().replace(/ /ig, '-'),
|
||||
blockGroupName: group.toLowerCase().replace(/ /ig, '-'),
|
||||
blockClassName: title.toLowerCase().replace(/ /ig, '_'),
|
||||
});
|
||||
|
||||
const pathDist = path.join(baseDir, 'blocks', data.blockFilename);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('config/default.cjs'),
|
||||
this.destinationPath(path.join(pathDist, 'config', 'default.cjs')),
|
||||
data
|
||||
);
|
||||
|
||||
|
||||
// SRC Template files
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/template.template.hbs'),
|
||||
this.destinationPath(path.join(pathDist, 'src', data.blockFilename + '.template.hbs')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/styles/template.scss'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'styles', data.blockFilename + '.scss')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/scripts/template.js'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'scripts', data.blockFilename + '.js')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('src/images/demo.jpeg'),
|
||||
this.destinationPath(path.join(pathDist, 'src', 'images', 'demo.jpeg')),
|
||||
data
|
||||
);
|
||||
|
||||
// Design Directory
|
||||
mkdirp.sync(path.join(pathDist, 'design'));
|
||||
|
||||
// Data Files
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/default.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'default.json')),
|
||||
data
|
||||
);
|
||||
|
||||
this.fs.copyTpl(
|
||||
this.templatePath('data/advanced.json'),
|
||||
this.destinationPath(path.join(pathDist, 'data', 'advanced.json')),
|
||||
data
|
||||
);
|
||||
|
||||
// Technical Project files.
|
||||
createTechnicalFiles(data, baseDir, `blocks/${data.name}`).then();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createTechnicalFiles(data, baseDir, distPath) {
|
||||
const pathDist = distPath; //path.join(baseDir, distPath);
|
||||
const generatorsPath = path.join(baseDir, 'generators/block/templates');
|
||||
|
||||
const store = memFs.create();
|
||||
const filesystem = editor.create(store);
|
||||
|
||||
const files = ['package.json', 'README.md', '.editorconfig', {from: 'gitignore', to: '.gitignore'}, 'block.json'];
|
||||
|
||||
for (let file of files) {
|
||||
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`;
|
||||
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`;
|
||||
|
||||
await filesystem.copyTplAsync(from, to, data);
|
||||
}
|
||||
|
||||
return filesystem.commit(); // Promise
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
# This file is for unifying the coding style for different editors and IDEs
|
||||
# editorconfig.org
|
||||
|
||||
# WordPress Coding Standards
|
||||
# https://make.wordpress.org/core/handbook/coding-standards/
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.json]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
# Basic
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
vendor
|
||||
|
||||
# Custom
|
||||
@@ -1,167 +0,0 @@
|
||||
# Development Setup
|
||||
|
||||
Install required modules:
|
||||
|
||||
```sh
|
||||
npm install
|
||||
```
|
||||
|
||||
Run development environment:
|
||||
|
||||
```sh
|
||||
npm start
|
||||
```
|
||||
|
||||
## How it works ⚙️
|
||||
|
||||
The project runs `gulp` and `browsersync` beyond the scenes.
|
||||
Each change in template, styling or script files will reload the page. The idea is to have a comfortable development
|
||||
environment.
|
||||
|
||||
# Block Structure
|
||||
|
||||
You will find all the block files in `/src` folder.
|
||||
|
||||
```
|
||||
/src/images/
|
||||
*.png, *jpg, *.svg
|
||||
|
||||
/src/scritps/
|
||||
*.mjs, *js
|
||||
|
||||
/src/styles/
|
||||
*.scss
|
||||
|
||||
/src/*.template.hbs
|
||||
```
|
||||
|
||||
# Block rules
|
||||
|
||||
We have a “blocks system”, means you will have to follow strict rules and use listed technologies:
|
||||
|
||||
## Template Layout & Data
|
||||
|
||||
### Data
|
||||
|
||||
You will find multiple `*.json` files in `/data` folder after the first run of `npm start`.
|
||||
|
||||
These files are used as data sources in layout. By reviewing these files you can understand what parameters you have and
|
||||
how the data is stored.
|
||||
|
||||
There are multiple data sources since the block can be reused in different situations with different data. We have to
|
||||
make sure that the block is always rendered properly.
|
||||
|
||||
> Don't change or edit these files - use the data as it is!
|
||||
|
||||
### Handlebars
|
||||
|
||||
[Handlebars](https://handlebarsjs.com/guide/ "What is Handlebars") used as a template engine in the project.
|
||||
|
||||
All the template layout must be in a single `*.hbs` file that located in `/src` folder.
|
||||
|
||||
> Use the data that is available in `/data` folder.
|
||||
> Avoid using any kind of "freestyle" input/content. Your template file must contain only HTML tags and Handlebars parameters.
|
||||
|
||||
### Static Media Files 🖼️
|
||||
|
||||
Use the `/src/images` folder in case you need to upload images or video files to the template.
|
||||
These files will be available by the next URL: http://localhost:3000/images/${filename}
|
||||
|
||||
### Class Naming Convention
|
||||
|
||||
Use BEM naming system for class names: http://getbem.com/naming/
|
||||
|
||||
## Styling rules 🎨
|
||||
|
||||
### Mobile First 📱
|
||||
|
||||
Use Mobile First approach. Start with mobile device styling and then focus on other screen sizes by using @media (
|
||||
min-width: ...px).
|
||||
|
||||
### CSS Units
|
||||
|
||||
Use `rem` units instead of pixels.
|
||||
For example, `1rem = 16pixels` depends on project.
|
||||
|
||||
No need to use rem in situations of `1px`, `2px` or `3px`. (usually used for borders width).
|
||||
It's important to mention that the block is "elastic". By using `rem` units we are able to scale the layout and keep the
|
||||
aspect ratio.
|
||||
|
||||
### Global Styling
|
||||
|
||||
The development environment includes CSS rules of the original project. This is including fonts, CSS variables,
|
||||
container logic etc...
|
||||
|
||||
The CSS file is already embedded in served HTML.
|
||||
|
||||
# JavaScript
|
||||
|
||||
## Sliders
|
||||
|
||||
For any kind of slides animations - use [SwiperJS](https://swiperjs.com/get-started).
|
||||
|
||||
The lib is included in the project and ready for use. Test with `window.Swiper` in browser console.
|
||||
|
||||
# Summary
|
||||
|
||||
## Short Video Review
|
||||
Check the [Video Guide](https://www.loom.com/share/1c707a4ea14e48b7a35a49d7b0a6f1e0)
|
||||
> PRO TIP
|
||||
> Use x2 speed in the player 😜
|
||||
|
||||
|
||||
## Testing ✅
|
||||
|
||||
### Development Toolbar ✔️
|
||||
|
||||
Use development toolbar to switch between data sources.
|
||||
|
||||
### Conditional Statements
|
||||
Make sure the data is available in Handlebars template.
|
||||
If there is a chance that some data/property will be missing - wrap the HTML layout with conditional verification:
|
||||
|
||||
Example:
|
||||
Render the link only if URL exists
|
||||
```
|
||||
{{#if link_cta_url }}
|
||||
<a href="{{link_cta_url}}">Click Here</a>
|
||||
{{ endif }}
|
||||
```
|
||||
|
||||
### Hover/Focus States ✔️
|
||||
|
||||
Make sure all "clickable" elements have `&:hover` & `&:focus` states in CSS rules.
|
||||
This might be related to links, buttons or any other interactive elements.
|
||||
|
||||
### Responsiveness ✔️
|
||||
|
||||
Make sure the layout is rendered correctly on all standard breakpoint size:
|
||||
|
||||
1. 1920px in width...
|
||||
2. 1680px
|
||||
3. 1440px
|
||||
4. 1360px
|
||||
5. 1280px
|
||||
6. 1024px
|
||||
7. 980px
|
||||
8. 768px
|
||||
9. 600px
|
||||
10. 414px
|
||||
11. 375px
|
||||
|
||||
If you follow the rules of REM unit - your layout must be "elastic" and most of the breakpoints should be rendered
|
||||
properly.
|
||||
|
||||
### Pixel Perfect Test ✔️
|
||||
|
||||
Compare the final result with provided screenshots.
|
||||
|
||||
You can use
|
||||
available [Browser Extensions](https://chrome.google.com/webstore/detail/perfectpixel-by-welldonec/dkaagdgjmgdmbnecmcefdhjekcoceebi?hl=en)
|
||||
or any other tool/approach that you're familiar with.
|
||||
The idea is to have "pretty close" result to requested design.
|
||||
|
||||
## Delivery
|
||||
Once you finished with the task, before letting me know - please review the next [Google Form link](https://forms.gle/w4sJjAyRDSSBRuGLA](https://forms.gle/w4sJjAyRDSSBRuGLA)
|
||||
|
||||
If everything is OK - send a Pull Request to the git project or share a ZIP file with me directly.
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "<%= blockGroupName %>/<%= blockFilename %>",
|
||||
"version": "<%= version %>",
|
||||
"title": "<%= title %>",
|
||||
"categories": [],
|
||||
"icon": "shortcode",
|
||||
"preview_image": "",
|
||||
"field_groups": [
|
||||
{
|
||||
"name": "content",
|
||||
"label": "Content",
|
||||
"fields": {
|
||||
"title": {
|
||||
"type": "text",
|
||||
"label": "Title"
|
||||
},
|
||||
"content": {
|
||||
"type": "wysiwyg",
|
||||
"label": "Content"
|
||||
},
|
||||
"cta": {
|
||||
"type": "link",
|
||||
"label": "CTA"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "styling",
|
||||
"label": "Styling",
|
||||
"fields": {
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
cssUrl: "https://",
|
||||
jsUrl: "https://",
|
||||
blockName: "<%= blockFilename %>",
|
||||
baseView: "<%= baseView %>",
|
||||
remToPx: <%= remToPx %>,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"title": "Option Two",
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>",
|
||||
"image_url": "https://fakeimg.pl/400x400/",
|
||||
"cta_text": "Our Success Stories",
|
||||
"url": "https://google.com"
|
||||
}
|
||||
|
||||
/**
|
||||
* Tips for good data.json example:
|
||||
* - Provide Short and Long version of text (title/content).
|
||||
* - Put <a> and <strong> elements in content.
|
||||
* - Upload different sizes and ratios of images.
|
||||
* - Provide options with and without CTA to test conditional logic.
|
||||
*/
|
||||
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"title": "As a Global Salesforce Partner,\n we deliver Service Cloud solutions\n and complex Field Service Management\n in diverse industries worldwide",
|
||||
"content": "<p><b>Delivering top results to industry leaders:</b></p>",
|
||||
"image_url": "https://fakeimg.pl/400x400/",
|
||||
"cta_text": "Our Success Stories",
|
||||
"url": "https://google.com"
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
# Basic
|
||||
.idea
|
||||
.DS_Store
|
||||
node_modules
|
||||
vendor
|
||||
|
||||
# Custom
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "<%= blockFilename %>",
|
||||
"version": "<%= version %>",
|
||||
"scripts": {
|
||||
"start": "component-dev",
|
||||
"build": "component-build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"create-block-dev-tool": "<%= devToolSource %>"
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 9.3 KiB |
@@ -1,7 +0,0 @@
|
||||
(function ($) {
|
||||
|
||||
console.log('Ready!');
|
||||
console.log('jQuery =', $);
|
||||
console.log('Swiper =', window.Swiper);
|
||||
|
||||
})(window.jQuery);
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Use "rem" instead of pixels. 1rem = <%= remToPx %>pixels
|
||||
* No need to use rem in situations of "1px". (usually used for borders width).
|
||||
*
|
||||
* Use BEM naming system for class names: http://getbem.com/naming/
|
||||
*
|
||||
* Use Mobile First approach.
|
||||
* Start with mobile device styling and then focus on other screen sizes by using @media (min-width: ...px).
|
||||
*
|
||||
*/
|
||||
|
||||
.<%= blockClassName %> {
|
||||
|
||||
&__header {
|
||||
// Header Mobile.
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
// Header Desktop.
|
||||
}
|
||||
|
||||
&-heading {
|
||||
// Child Element.
|
||||
}
|
||||
}
|
||||
|
||||
&__visual {
|
||||
&-image {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
|
||||
}
|
||||
|
||||
&__footer {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
<section class="<%= blockClassName %> <%= blockClassName %>--{{ config.activeDataFile }}">
|
||||
|
||||
{{!
|
||||
Remove Everything Below:
|
||||
}}
|
||||
|
||||
<style>
|
||||
code {
|
||||
background-color: #eee;
|
||||
color: #333;
|
||||
padding: 1rem 1.5rem;
|
||||
border: 2px solid #d8d8d8;
|
||||
display: inline-block;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
white-space: pre-line;
|
||||
}
|
||||
</style>
|
||||
|
||||
<header class="<%= blockClassName %>__header">
|
||||
<h1 class="<%= blockClassName %>__header-heading">Ready!</h1>
|
||||
</header>
|
||||
|
||||
<div class="<%= blockClassName %>__content">
|
||||
<p>
|
||||
Review the `/data` folder with JSON data files.<br>
|
||||
Don't change data JSON files - use the data as it is.
|
||||
</p>
|
||||
<p>Build the layout based on provided data structure.</p>
|
||||
|
||||
<br><br>
|
||||
<hr>
|
||||
|
||||
<h2>Image Example</h2>
|
||||
<div>
|
||||
<img src="/images/demo.jpeg" alt="">
|
||||
<br>
|
||||
<code><img src="/images/demo.jpeg" alt="">
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<br><br>
|
||||
<hr>
|
||||
|
||||
<h2>Available Buttons Styling</h2>
|
||||
|
||||
<div>
|
||||
<a href="#" class="btn btn--primary">Primary</a>
|
||||
<br>
|
||||
<code><a href="#" class="btn btn--primary">Primary</a>
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div>
|
||||
<a href="#" class="btn btn--secondary">Secondary</a>
|
||||
<br>
|
||||
<code><a href="#" class="btn btn--secondary">Secondary</a>
|
||||
</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!
|
||||
Remove END
|
||||
}}
|
||||
|
||||
</section>
|
||||
@@ -1,3 +1,98 @@
|
||||
import path from 'path';
|
||||
import config from 'config';
|
||||
import {fileURLToPath} from 'url';
|
||||
import memFs from 'mem-fs';
|
||||
import editor from 'mem-fs-editor';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
export function getConfigs() {
|
||||
const isDev = process.env.NODE_ENV === 'development'; // Check README file in case you get "missing files" error.
|
||||
const developmentBlockName = process.env.BLOCK_NAME;
|
||||
|
||||
return {
|
||||
isDev,
|
||||
developmentBlockName,
|
||||
modulesPath: isDev ? '' : 'node_modules/block-dev-tool',
|
||||
projectPath: isDev ? path.join('blocks', developmentBlockName) : '',
|
||||
};
|
||||
}
|
||||
|
||||
import fsExtra from "fs-extra";
|
||||
|
||||
export async function readJSONFile(jsonFile) {
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
data = await fsExtra.readJson(jsonFile);
|
||||
} catch (e) {
|
||||
return {
|
||||
error: true, errorMessage: getErrorHtml("JSON Syntax error. Please make sure the dataFile is valid.", e),
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function getErrorHtml(message = '', errorMessage = '') {
|
||||
return `<div style="padding: 10px 15px; font-family: Arial, sans-serif">
|
||||
<p>${message}</p>
|
||||
<pre style="padding: 10px 15px; background-color: #ffd0d0; border: 1px solid red;">${errorMessage}</pre>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
export async function getBlockConfigs(jsonFileName = 'default',
|
||||
{includeConfigs, projectPath, modulesPath, dataFiles} = {}) {
|
||||
let data = await readJSONFile(path.join(projectPath, 'data', `${jsonFileName}.json`));
|
||||
if (data.error) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (includeConfigs) {
|
||||
Object.assign(data, {
|
||||
config: Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
|
||||
{
|
||||
projectDir: modulesPath, activeDataFile: jsonFileName, dataFiles: dataFiles.map((name) => {
|
||||
return {
|
||||
name, active: jsonFileName === name,
|
||||
};
|
||||
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
export function getBlockName(name = '') {
|
||||
if (name.startsWith('@')) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
|
||||
const arr = name.split('/');
|
||||
|
||||
return {
|
||||
project: arr[0],
|
||||
name: arr[1],
|
||||
};
|
||||
}
|
||||
|
||||
export async function createFiles(data, files = [], {pathDist, generatorsPath}) {
|
||||
generatorsPath = generatorsPath ?? path.join(__dirname, 'generators/block/templates');
|
||||
|
||||
const store = memFs.create();
|
||||
const filesystem = editor.create(store);
|
||||
for (let file of files) {
|
||||
const from = typeof file !== 'string' ? `${generatorsPath}/${file.from}` : `${generatorsPath}/${file}`;
|
||||
const to = typeof file !== 'string' ? `${pathDist}/${file.to}` : `${pathDist}/${file}`;
|
||||
|
||||
await filesystem.copyTplAsync(from, to, data);
|
||||
}
|
||||
|
||||
return filesystem.commit(); // Promise
|
||||
}
|
||||
|
||||
export function capitalize(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return '';
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<html lang="en">
|
||||
|
||||
{{> (include_partial "layouts/partials/head") }}
|
||||
|
||||
<body>
|
||||
|
||||
<main>
|
||||
<div class="container" style="max-width: 1280px; margin: 1rem auto;">
|
||||
<h1>Syntax Error:</h1>
|
||||
<pre style="padding: 10px 15px; background-color: #ffe6e6; border: 1px solid red; border-radius: 0.25rem; overflow: auto">{{err}}</pre>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
{{> (include_partial "layouts/partials/scripts") }}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -13,10 +13,13 @@
|
||||
|
||||
<script>
|
||||
window.devTool = {
|
||||
previewFrameUrl: 'http://localhost:{{ port }}/view/{{ baseView }}',
|
||||
previewFrameUrl: '{{ previewFrameUrl }}',
|
||||
};
|
||||
</script>
|
||||
<iframe id="preview_frame" src="http://localhost:{{ port }}/view/{{ baseView }}" class="breakpoint"></iframe>
|
||||
|
||||
<div class="preview">
|
||||
<iframe id="preview_frame" src="{{ previewFrameUrl }}" class="breakpoint"></iframe>
|
||||
</div>
|
||||
|
||||
<script src="/scripts/dist/index.min.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
<script src="/scripts/frame-index.js"></script>
|
||||
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
|
||||
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js"></script>{{#if config.jsUrl }}
|
||||
{{#each config.jsUrl }}<script src="{{ this }}"></script>
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
<header class="page_toolbar">
|
||||
|
||||
<div class="page_toolbar__left">#</div>
|
||||
<div class="page_toolbar__left"></div>
|
||||
|
||||
<div class="page_toolbar__middle">
|
||||
<div class="page_toolbar__data_options">
|
||||
<label for="data-options">Data Options: </label>
|
||||
|
||||
<select name="data" id="data-options">
|
||||
{{#each config.dataFiles }}
|
||||
<option value="{{ name }}" {{#if active }}selected="selected"{{/if}}>{{ name }}</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Sizes: <b>1rem = {{ config.remToPx }}px</b>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page_toolbar__right"></div>
|
||||
<div class="page_toolbar__right">
|
||||
<a href="{{ previewFrameUrl }}" target="_blank" class="open_in_new_tab"></a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M12.45 38.7 9.3 35.55 20.85 24 9.3 12.5l3.15-3.2L24 20.8 35.55 9.3l3.15 3.2L27.2 24l11.5 11.55-3.15 3.15L24 27.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
|
||||
<path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393
|
||||
c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393
|
||||
s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M28.6 40.75V36.2h4.75q1.2 0 2.05-.825.85-.825.85-2.075v-3.85q0-1.8 1.075-3.225T40.15 24.2v-.4q-1.75-.55-2.825-2-1.075-1.45-1.075-3.3v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85H28.6V7.2h5.95q2.6 0 4.425 1.825Q40.8 10.85 40.8 13.5v3.85q0 1.2.825 2.05.825.85 2.075.85h1.1v7.5h-1.1q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 2.65-1.825 4.45-1.825 1.8-4.425 1.8Zm-15.1 0q-2.7 0-4.475-1.8-1.775-1.8-1.775-4.45v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85h-1.1v-7.5h1.1q1.2 0 2.05-.85.85-.85.85-2.05V13.5q0-2.65 1.8-4.475Q10.85 7.2 13.5 7.2h5.95v4.55H14.7q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 1.85-1.1 3.3-1.1 1.45-2.85 2v.4q1.75.6 2.85 2.025 1.1 1.425 1.1 3.225v3.85q0 1.25.825 2.075.825.825 2.075.825h4.75v4.55Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 803 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M9.5 43.05q-1.85 0-3.2-1.35t-1.35-3.2v-29q0-1.9 1.35-3.25T9.5 4.9h13.35v4.6H9.5v29h29V25.15h4.6V38.5q0 1.85-1.35 3.2t-3.25 1.35ZM20.3 30.9l-3.15-3.2 18.2-18.2h-9.5V4.9H43.1v17.25h-4.6V12.7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m31.7 26.4-3.85-3.8q1.05-2.2-.625-3.45t-3.125-.3l-3.5-3.55q.75-.4 1.65-.6.9-.2 1.75-.2 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 .8-.2 1.8t-.6 1.6Zm7.4 7.5-2.75-2.8q2.2-1.75 3.825-3.85T42.8 23q-2.6-5.5-7.525-8.75Q30.35 11 24.5 11q-2.1 0-4.1.35-2 .35-2.95.8l-3.3-3.3q1.75-.8 4.625-1.4 2.875-.6 5.475-.6 7.35 0 13.6 4.25t9.3 11.9q-1.25 3.3-3.375 6.075Q41.65 31.85 39.1 33.9Zm1.25 11.35-7.7-7.6q-1.75.7-4 1.1-2.25.4-4.65.4-7.55 0-13.825-4.275Q3.9 30.6.85 23q.9-2.55 2.75-5.15 1.85-2.6 4.3-5L1.75 6.8l2.5-2.6L42.7 42.65ZM10.8 15.7Q9 17.15 7.5 19.125 6 21.1 5.2 23q2.6 5.55 7.65 8.775Q17.9 35 24.4 35q1.35 0 2.775-.15 1.425-.15 2.225-.55l-3.2-3.2q-.4.2-1.025.3-.625.1-1.175.1-3.5 0-6-2.45T15.5 23q0-.55.075-1.15.075-.6.225-1.05Zm16.15 6.4Zm-6.85 3.45Z"/></svg>
|
||||
|
After Width: | Height: | Size: 825 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-3.7q-2 0-3.4-1.4T19.2 23q0-2 1.4-3.4t3.4-1.4q2 0 3.4 1.4t1.4 3.4q0 2-1.4 3.4T24 27.8Zm0 11.35q-7.7 0-13.9-4.5T.85 23q3.05-7.15 9.25-11.65T24 6.85q7.7 0 13.9 4.5T47.15 23q-3.05 7.15-9.25 11.65T24 39.15ZM24 23Zm0 12q6 0 11.05-3.275Q40.1 28.45 42.75 23q-2.65-5.45-7.675-8.725Q30.05 11 24 11q-6 0-11.05 3.275Q7.9 17.55 5.2 23q2.7 5.45 7.725 8.725Q17.95 35 24 35Z"/></svg>
|
||||
|
After Width: | Height: | Size: 604 B |
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
|
||||
const heightLimit = window.outerHeight * 2;
|
||||
let height = getCurrentHeight();
|
||||
setupResizeListener();
|
||||
|
||||
///
|
||||
|
||||
function setupResizeListener() {
|
||||
const resizeObserver = new ResizeObserver(handleHeightChange);
|
||||
resizeObserver.observe(document.body);
|
||||
}
|
||||
|
||||
function handleHeightChange(entries) {
|
||||
const updatedHeight = getCurrentHeight();
|
||||
if (height === updatedHeight || updatedHeight > heightLimit) {
|
||||
return;
|
||||
}
|
||||
|
||||
const RESIZE_CODE = 'resize:';
|
||||
window.parent.postMessage(RESIZE_CODE + JSON.stringify({height}), '*');
|
||||
height = updatedHeight;
|
||||
}
|
||||
|
||||
function getCurrentHeight() {
|
||||
return document.querySelector('body > main').scrollHeight;
|
||||
}
|
||||
@@ -2,32 +2,23 @@
|
||||
|
||||
import {setupResponsiveness} from './toolbar/responsive.jsx';
|
||||
import {setupPublish} from "./toolbar/publish.jsx";
|
||||
import {setupDataOptions} from "./toolbar/data-options/DataOptions.jsx";
|
||||
|
||||
const previewFrame = document.getElementById('preview_frame');
|
||||
const rootAttributes = {
|
||||
previewFrame: document.getElementById('preview_frame'),
|
||||
previewFrame,
|
||||
}
|
||||
|
||||
setupResponsiveness(rootAttributes);
|
||||
setupDataOptions(rootAttributes);
|
||||
setupPublish(rootAttributes)
|
||||
|
||||
// const responsiveness = connectResponsiveness(rootAttributes);
|
||||
// setTimeout(() => responsiveness.selectMode('tablet'), 5000)
|
||||
// setTimeout(() => responsiveness.selectMode('mobile'), 10000)
|
||||
|
||||
const previewFrame = rootAttributes.previewFrame;
|
||||
initDataOptions();
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
|
||||
function initDataOptions() {
|
||||
const dataOptionsSelect = document.getElementById('data-options');
|
||||
if (!dataOptionsSelect) {
|
||||
window.addEventListener('message', function (e) {
|
||||
const RESIZE_CODE = 'resize:';
|
||||
if (typeof e.data !== 'string' || !e.data.startsWith(RESIZE_CODE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
dataOptionsSelect.addEventListener('change', function () {
|
||||
previewFrame.src = window.devTool.previewFrameUrl + '?data=' + this.value;
|
||||
const data = JSON.parse(e.data.substring(RESIZE_CODE.length))
|
||||
previewFrame.style.height = data.height + 'px';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import * as ReactDOM from 'react-dom/client';
|
||||
import {
|
||||
SidebarButtonToggleStyle,
|
||||
SidebarCloseButtonStyle, SidebarDataOptionsStyle,
|
||||
SidebarHeaderStyle,
|
||||
SidebarStyle,
|
||||
} from "./data-options.style.js";
|
||||
import {isClickOutside, isEscHit} from "../responsive-button/ResponsiveButton.jsx";
|
||||
import {DesignPreview} from "./DesignPreview.jsx";
|
||||
|
||||
function DataOptions(props = {}) {
|
||||
props.rootAttributes = props.rootAttributes ?? {};
|
||||
|
||||
const initialState = {dataName: 'default', data: {}, dataOptions: [], designPreview: []};
|
||||
const [state, setState] = useState(initialState);
|
||||
const [previewOption, setPreviewOption] = useState(getDesignPreviewImage(state.dataName, state.designPreview));
|
||||
const updateState = (update) => setState(Object.assign({}, state, update));
|
||||
|
||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||
|
||||
useEffect(async () => {
|
||||
const data = await fetchDataOptions(state.dataName);
|
||||
updateState(data);
|
||||
}, []);
|
||||
|
||||
const handleCloseSidebarEscEvent = useCallback((e) => {
|
||||
if (isEscHit(e)) {
|
||||
(() => {
|
||||
closeSidebar()
|
||||
})();
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener("keydown", handleCloseSidebarEscEvent);
|
||||
|
||||
// Unsubscribe from ESC listener.
|
||||
return () => {
|
||||
document.removeEventListener("keydown", handleCloseSidebarEscEvent);
|
||||
}
|
||||
}, [handleCloseSidebarEscEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('message', responsiveModeChangesHandler);
|
||||
return () => window.removeEventListener('message', responsiveModeChangesHandler);
|
||||
}, [state]);
|
||||
|
||||
useEffect(() => {
|
||||
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
|
||||
}, [state.designPreview]);
|
||||
|
||||
const handleBlur = async (e) => await isClickOutside(e) ? closeSidebar() : null;
|
||||
|
||||
return <div style={{display: 'flex', gap: '1rem', alignItems: 'center'}}>
|
||||
<SidebarButtonToggleStyle onClick={() => openSidebar()} title="Open a Sidebar with Data Options"/>
|
||||
|
||||
<DesignPreview previewOption={previewOption}/>
|
||||
|
||||
<SidebarStyle className={sidebarOpen ? 'active sidebar-active' : ''} tabIndex='0' onBlur={handleBlur}>
|
||||
<SidebarHeaderStyle>
|
||||
<SidebarCloseButtonStyle onClick={() => closeSidebar()}></SidebarCloseButtonStyle>
|
||||
</SidebarHeaderStyle>
|
||||
|
||||
{state.dataOptions && !!state.dataOptions.length &&
|
||||
<SidebarDataOptionsStyle>
|
||||
<label htmlFor="data-options">Data Options</label>
|
||||
|
||||
<select name="data" id="data-options" onChange={(e) => changeDataOption(e)} value={state.dataName}>
|
||||
{state.dataOptions.map((item) => {
|
||||
const isSelected = state.dataName === item;
|
||||
return <option value={item} selected={isSelected}>{item}</option>
|
||||
})}
|
||||
</select>
|
||||
</SidebarDataOptionsStyle>
|
||||
}
|
||||
|
||||
{state.data &&
|
||||
<pre>{JSON.stringify(state.data, null, 2)}</pre>
|
||||
}
|
||||
|
||||
<button className='btn btn--secondary' onClick={(e) => copyToClipboard(e, state.data)}>Copy to Clipboard</button>
|
||||
</SidebarStyle>
|
||||
</div>;
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
function openSidebar() {
|
||||
setSidebarOpen(true);
|
||||
setTimeout(() => document.querySelector('.sidebar-active').focus());
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
setSidebarOpen(false);
|
||||
}
|
||||
|
||||
async function changeDataOption(e) {
|
||||
const dataName = e.target.value;
|
||||
props.rootAttributes.previewFrame.src = window.devTool.previewFrameUrl + '?data=' + dataName;
|
||||
|
||||
const dataOption = await fetchDataOptions(dataName);
|
||||
updateState(Object.assign({}, dataOption, {dataName}));
|
||||
}
|
||||
|
||||
async function fetchDataOptions(name = 'default') {
|
||||
const queryParameters = new URLSearchParams({name});
|
||||
const response = await fetch(`/data?${queryParameters}`);
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
function getDesignPreviewImage(currentDataName, designPreviewOptions = []) {
|
||||
const dataOptions = designPreviewOptions.filter((item) => {
|
||||
return item.dataSource === currentDataName;
|
||||
});
|
||||
|
||||
dataOptions.sort((a, b) => b.widthDimension - a.widthDimension);
|
||||
let size = window.responsiveState.breakpoint;
|
||||
if (size === '100%') {
|
||||
size = window.innerWidth;
|
||||
}
|
||||
|
||||
return dataOptions.find((item) => {
|
||||
return size >= item.widthDimension;
|
||||
});
|
||||
}
|
||||
|
||||
function responsiveModeChangesHandler(e) {
|
||||
if (e.data !== 'responsiveUpdate') {
|
||||
return;
|
||||
}
|
||||
|
||||
setPreviewOption(getDesignPreviewImage(state.dataName, state.designPreview));
|
||||
}
|
||||
}
|
||||
|
||||
function copyToClipboard(e, context) {
|
||||
e.stopPropagation();
|
||||
|
||||
if (typeof context !== 'string') {
|
||||
context = JSON.stringify(context, null, 2);
|
||||
}
|
||||
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// This case will not work on non-https pages.
|
||||
return navigator.clipboard.writeText(context);
|
||||
} else {
|
||||
let textArea = document.createElement("textarea");
|
||||
textArea.value = context;
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.left = "-999999px";
|
||||
textArea.style.top = "-999999px";
|
||||
document.body.appendChild(textArea);
|
||||
// textArea.focus();
|
||||
textArea.select();
|
||||
return new Promise((res, rej) => {
|
||||
document.execCommand('copy') ? res() : rej();
|
||||
textArea.remove();
|
||||
e.target.focus()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function setupDataOptions(rootAttributes) {
|
||||
// INIT
|
||||
const wrapper = document.createElement('div');
|
||||
document.querySelector('.page_toolbar__left').prepend(wrapper)
|
||||
|
||||
const root = ReactDOM.createRoot(wrapper);
|
||||
const html = (<DataOptions rootAttributes={rootAttributes}/>);
|
||||
root.render(html);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
import React, {createRef, useCallback, useEffect, useState} from 'react';
|
||||
import {PreviewButtonStyle, PreviewStyle} from "./design-preview.style.js";
|
||||
|
||||
let startDragPosition = {x: 0, y: 0};
|
||||
|
||||
export function DesignPreview({previewOption = {widthDimension: 0}}) {
|
||||
const reference = createRef();
|
||||
const [active, setActive] = useState(false);
|
||||
const [position, setPosition] = useState({x: getInitialXPosition(previewOption), y: 0});
|
||||
const [opacity, setOpacity] = useState(85);
|
||||
|
||||
const keyDownHandler = useCallback(
|
||||
function (e) {
|
||||
if (!active) {
|
||||
return;
|
||||
}
|
||||
|
||||
const step = e.shiftKey ? 10 : 1;
|
||||
if (e.key === 'ArrowUp') {
|
||||
updatePosition({y: step * -1}, true)
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
updatePosition({y: step}, true)
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
updatePosition({x: step * -1}, true)
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
updatePosition({x: step}, true)
|
||||
}
|
||||
|
||||
}, [position, active]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', keyDownHandler);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', keyDownHandler)
|
||||
}
|
||||
}, [keyDownHandler]);
|
||||
|
||||
useEffect(() => {
|
||||
updatePosition({x: getInitialXPosition(previewOption), y: 0});
|
||||
|
||||
window.addEventListener('message', responsiveModeChangesHandler);
|
||||
return () => window.removeEventListener('message', responsiveModeChangesHandler);
|
||||
}, [previewOption.widthDimension]);
|
||||
|
||||
if (!previewOption) {
|
||||
return;
|
||||
}
|
||||
|
||||
const disabled = !previewOption.url;
|
||||
|
||||
const classNames = [];
|
||||
if (!disabled && active) {
|
||||
classNames.push('active');
|
||||
}
|
||||
|
||||
if (disabled) {
|
||||
classNames.push('disabled');
|
||||
}
|
||||
|
||||
const handleOpacityChange = (e) => {
|
||||
setOpacity(e.target.value);
|
||||
}
|
||||
|
||||
return <>
|
||||
<PreviewButtonStyle onClick={() => toggle()} className={classNames.join(' ')} disabled={disabled}/>
|
||||
|
||||
{active && !disabled &&
|
||||
<>
|
||||
<input type="range" value={opacity} min="0" max="100" onChange={handleOpacityChange}/>
|
||||
<PreviewStyle onMouseUp={e => stop(e)} style={{opacity: opacity / 100}}>
|
||||
<img src={previewOption.url} alt="" ref={reference}
|
||||
onMouseDown={e => start(e)}
|
||||
draggable={false}
|
||||
style={{top: position.y + 'px', left: position.x + 'px'}}
|
||||
/>
|
||||
</PreviewStyle>
|
||||
</>
|
||||
}
|
||||
</>
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
function start(e) {
|
||||
startDragPosition.x = e.clientX;
|
||||
startDragPosition.y = e.clientY;
|
||||
|
||||
document.body.addEventListener('mousemove', handler);
|
||||
}
|
||||
|
||||
function stop(e) {
|
||||
const target = reference.current;
|
||||
updatePosition({x: target.offsetLeft, y: target.offsetTop});
|
||||
|
||||
document.body.removeEventListener('mousemove', handler);
|
||||
}
|
||||
|
||||
function handler(e) {
|
||||
const target = reference.current;
|
||||
|
||||
if (target) {
|
||||
const updatedPosition = {
|
||||
x: target.offsetLeft - (startDragPosition.x - e.clientX),
|
||||
y: target.offsetTop - (startDragPosition.y - e.clientY),
|
||||
};
|
||||
|
||||
target.style.left = updatedPosition.x + 'px';
|
||||
target.style.top = updatedPosition.y + 'px';
|
||||
|
||||
startDragPosition.x = e.clientX;
|
||||
startDragPosition.y = e.clientY;
|
||||
}
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
setActive(!active);
|
||||
}
|
||||
|
||||
function updatePosition({x, y}, relative = false) {
|
||||
x = typeof x === 'undefined' ? position.x : relative ? position.x + x : x;
|
||||
y = typeof y === 'undefined' ? position.y : relative ? position.y + y : y;
|
||||
|
||||
setPosition({x, y})
|
||||
}
|
||||
|
||||
function getInitialXPosition(previewOption) {
|
||||
if (!previewOption) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const scrollbarOffset = 7;
|
||||
return window.innerWidth / 2 - previewOption.widthDimension / 2 - scrollbarOffset;
|
||||
}
|
||||
|
||||
function responsiveModeChangesHandler(e) {
|
||||
if (e.data !== 'responsiveUpdate') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.responsiveState.breakpoint === '100%') {
|
||||
updatePosition({x: getInitialXPosition(previewOption), y: 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const SidebarStyle = styled.div`
|
||||
--sidebarWidth: 360px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: calc(var(--sidebarWidth) * -1);
|
||||
width: var(--sidebarWidth);
|
||||
background-color: #E2E8F0;
|
||||
border-right: 1px solid #CBD5E0;
|
||||
padding: 0 0.75rem 0.75rem;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
transition: left .2s ease-in-out, visibility .2s ease-in-out;
|
||||
visibility: hidden;
|
||||
color: #333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
&.active {
|
||||
left: 0;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
pre {
|
||||
overflow-x: auto;
|
||||
padding: 0.5rem;
|
||||
background-color: #EDF2F7;
|
||||
border-radius: 4px;
|
||||
color: #333;
|
||||
border: 1px solid #cbd5e0;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SidebarHeaderStyle = styled.header`
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
padding: 0.5rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
export const SidebarButtonToggleStyle = styled.button`
|
||||
--size: 1.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-json.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center center;
|
||||
background-color: initial;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
export const SidebarCloseButtonStyle = styled.button`
|
||||
--size: 1.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-close.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center center;
|
||||
background-color: initial;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
export const SidebarDataOptionsStyle = styled.div`
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.5rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
select {
|
||||
flex: 1 1;
|
||||
display: block;
|
||||
appearance: none;
|
||||
border: 1px solid #cbd5e0;
|
||||
padding: 0.5rem;
|
||||
color: #333;
|
||||
border-radius: 4px;
|
||||
|
||||
background-color: #edf2f7;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-dropdown-arrow.svg");
|
||||
background-position: right 0.75rem center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 0.5rem;
|
||||
}
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,60 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
export const PreviewButtonStyle = styled.button`
|
||||
--size: 1.5rem;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-preview.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center center;
|
||||
background-color: initial;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
|
||||
&.active {
|
||||
background-image: url("/scripts/dist/toolbar/images/icon-preview-disabled.svg");
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.25;
|
||||
cursor: default;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PreviewStyle = styled.div`
|
||||
position: fixed;
|
||||
top: calc(var(--top_panel_height) - 2px);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 14px; // Scrollbars
|
||||
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
align-items: baseline;
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
height: auto;
|
||||
display: block;
|
||||
cursor: move;
|
||||
position: absolute;
|
||||
}
|
||||
`;
|
||||
|
||||
export const PreviewImageStyle = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
background-repeat: no-repeat;
|
||||
background-position: top;
|
||||
cursor: move;
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M12.45 38.7 9.3 35.55 20.85 24 9.3 12.5l3.15-3.2L24 20.8 35.55 9.3l3.15 3.2L27.2 24l11.5 11.55-3.15 3.15L24 27.2Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 218 B |
@@ -0,0 +1,7 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 330 330" style="enable-background:new 0 0 330 330;" xml:space="preserve">
|
||||
<path id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393
|
||||
c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393
|
||||
s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 553 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M28.6 40.75V36.2h4.75q1.2 0 2.05-.825.85-.825.85-2.075v-3.85q0-1.8 1.075-3.225T40.15 24.2v-.4q-1.75-.55-2.825-2-1.075-1.45-1.075-3.3v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85H28.6V7.2h5.95q2.6 0 4.425 1.825Q40.8 10.85 40.8 13.5v3.85q0 1.2.825 2.05.825.85 2.075.85h1.1v7.5h-1.1q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 2.65-1.825 4.45-1.825 1.8-4.425 1.8Zm-15.1 0q-2.7 0-4.475-1.8-1.775-1.8-1.775-4.45v-3.85q0-1.2-.85-2.05-.85-.85-2.05-.85h-1.1v-7.5h1.1q1.2 0 2.05-.85.85-.85.85-2.05V13.5q0-2.65 1.8-4.475Q10.85 7.2 13.5 7.2h5.95v4.55H14.7q-1.25 0-2.075.85-.825.85-.825 2.05v3.85q0 1.85-1.1 3.3-1.1 1.45-2.85 2v.4q1.75.6 2.85 2.025 1.1 1.425 1.1 3.225v3.85q0 1.25.825 2.075.825.825 2.075.825h4.75v4.55Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 803 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48" fill="#21252d">
|
||||
<path
|
||||
d="M9.5 43.05q-1.85 0-3.2-1.35t-1.35-3.2v-29q0-1.9 1.35-3.25T9.5 4.9h13.35v4.6H9.5v29h29V25.15h4.6V38.5q0 1.85-1.35 3.2t-3.25 1.35ZM20.3 30.9l-3.15-3.2 18.2-18.2h-9.5V4.9H43.1v17.25h-4.6V12.7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 294 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="m31.7 26.4-3.85-3.8q1.05-2.2-.625-3.45t-3.125-.3l-3.5-3.55q.75-.4 1.65-.6.9-.2 1.75-.2 3.55 0 6.025 2.475Q32.5 19.45 32.5 23q0 .8-.2 1.8t-.6 1.6Zm7.4 7.5-2.75-2.8q2.2-1.75 3.825-3.85T42.8 23q-2.6-5.5-7.525-8.75Q30.35 11 24.5 11q-2.1 0-4.1.35-2 .35-2.95.8l-3.3-3.3q1.75-.8 4.625-1.4 2.875-.6 5.475-.6 7.35 0 13.6 4.25t9.3 11.9q-1.25 3.3-3.375 6.075Q41.65 31.85 39.1 33.9Zm1.25 11.35-7.7-7.6q-1.75.7-4 1.1-2.25.4-4.65.4-7.55 0-13.825-4.275Q3.9 30.6.85 23q.9-2.55 2.75-5.15 1.85-2.6 4.3-5L1.75 6.8l2.5-2.6L42.7 42.65ZM10.8 15.7Q9 17.15 7.5 19.125 6 21.1 5.2 23q2.6 5.55 7.65 8.775Q17.9 35 24.4 35q1.35 0 2.775-.15 1.425-.15 2.225-.55l-3.2-3.2q-.4.2-1.025.3-.625.1-1.175.1-3.5 0-6-2.45T15.5 23q0-.55.075-1.15.075-.6.225-1.05Zm16.15 6.4Zm-6.85 3.45Z"/></svg>
|
||||
|
After Width: | Height: | Size: 825 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48" width="48"><path d="M24 31.5q3.55 0 6.025-2.475Q32.5 26.55 32.5 23q0-3.55-2.475-6.025Q27.55 14.5 24 14.5q-3.55 0-6.025 2.475Q15.5 19.45 15.5 23q0 3.55 2.475 6.025Q20.45 31.5 24 31.5Zm0-3.7q-2 0-3.4-1.4T19.2 23q0-2 1.4-3.4t3.4-1.4q2 0 3.4 1.4t1.4 3.4q0 2-1.4 3.4T24 27.8Zm0 11.35q-7.7 0-13.9-4.5T.85 23q3.05-7.15 9.25-11.65T24 6.85q7.7 0 13.9 4.5T47.15 23q-3.05 7.15-9.25 11.65T24 39.15ZM24 23Zm0 12q6 0 11.05-3.275Q40.1 28.45 42.75 23q-2.65-5.45-7.675-8.725Q30.05 11 24 11q-6 0-11.05 3.275Q7.9 17.55 5.2 23q2.7 5.45 7.725 8.725Q17.95 35 24 35Z"/></svg>
|
||||
|
After Width: | Height: | Size: 604 B |
@@ -39,7 +39,7 @@ function Publish(props = {}) {
|
||||
export function setupPublish(rootAttributes) {
|
||||
// INIT
|
||||
const wrapper = document.createElement('div');
|
||||
document.querySelector('.page_toolbar__right').prepend(wrapper)
|
||||
document.querySelector('.page_toolbar__right').append(wrapper)
|
||||
|
||||
const root = ReactDOM.createRoot(wrapper);
|
||||
const html = (<Publish rootAttributes={rootAttributes}/>);
|
||||
|
||||
@@ -22,14 +22,12 @@ function Responsive(props = {}) {
|
||||
props.rootAttributes = props.rootAttributes ?? {};
|
||||
|
||||
const initialState = {mode: 'default', breakpoint: '100%'}
|
||||
|
||||
const [state, setState] = useState(initialState);
|
||||
const updateState = (update) => setState(Object.assign({}, state, update));
|
||||
const [mode, setMode] = useState(initialState.mode);
|
||||
const [breakpoint, setBreakpoint] = useState(initialState.breakpoint);
|
||||
|
||||
useEffect(() => {
|
||||
// Update the document title using the browser API
|
||||
updateController(state);
|
||||
});
|
||||
updateController();
|
||||
}, [mode, breakpoint]);
|
||||
|
||||
const previewFrame = props.rootAttributes.previewFrame;
|
||||
return render();
|
||||
@@ -50,22 +48,30 @@ function Responsive(props = {}) {
|
||||
|
||||
function selectMode(mode, breakpoint) {
|
||||
if (mode === 'reset') {
|
||||
updateState(initialState);
|
||||
setMode(initialState.mode);
|
||||
setBreakpoint(initialState.breakpoint);
|
||||
return;
|
||||
}
|
||||
|
||||
updateState({mode, breakpoint})
|
||||
setMode(mode);
|
||||
setBreakpoint(breakpoint);
|
||||
}
|
||||
|
||||
|
||||
function isActive(mode) {
|
||||
return mode === state.mode;
|
||||
function isActive(requestedMode) {
|
||||
return mode === requestedMode;
|
||||
}
|
||||
|
||||
function updateController() {
|
||||
const unit = typeof state.breakpoint === 'string' ? '' : 'px';
|
||||
previewFrame.style.setProperty('--breakpoint', state.breakpoint + unit);
|
||||
let frameBreakpoint = breakpoint;
|
||||
if (typeof frameBreakpoint !== 'string') {
|
||||
frameBreakpoint = frameBreakpoint + 'px';
|
||||
}
|
||||
|
||||
previewFrame.style.setProperty('--breakpoint', frameBreakpoint);
|
||||
previewFrame.classList.add('has-breakpoint');
|
||||
window.postMessage('responsiveUpdate');
|
||||
window.responsiveState = {mode, breakpoint};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
--btn-background-color: white;
|
||||
display: inline-block;
|
||||
padding: calc(0.375rem - 2px) 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
@@ -22,4 +22,10 @@
|
||||
--btn-color: white;
|
||||
--btn-background-color: #333;
|
||||
}
|
||||
|
||||
&--secondary {
|
||||
border-color: #cbd5e0;
|
||||
--btn-color: #333;
|
||||
--btn-background-color: #edf2f7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
.preview {
|
||||
overflow-y: scroll;
|
||||
height: calc(100% - var(--top_panel_height));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#preview_frame {
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
min-height: 100%;
|
||||
|
||||
--top_spacing: 0px;
|
||||
--breakpoint_top_spacing: 30px;
|
||||
|
||||
margin-top: var(--top_spacing);
|
||||
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
|
||||
border: 1px solid #E2E8F0;
|
||||
border: 0;
|
||||
outline: 1px solid #E2E8F0;
|
||||
|
||||
transition: max-width .3s ease-in-out, width .3s ease-in-out, margin-top .3s ease-in-out;
|
||||
|
||||
@@ -19,7 +27,6 @@
|
||||
|
||||
width: 100%;
|
||||
max-width: var(--breakpoint);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,7 +14,7 @@ body {
|
||||
--btn-background-color: white;
|
||||
display: inline-block;
|
||||
padding: calc(0.375rem - 2px) 0.75rem;
|
||||
font-size: 1rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
@@ -32,6 +32,11 @@ body {
|
||||
--btn-color: white;
|
||||
--btn-background-color: #333;
|
||||
}
|
||||
.btn--secondary {
|
||||
border-color: #cbd5e0;
|
||||
--btn-color: #333;
|
||||
--btn-background-color: #edf2f7;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
@@ -50,28 +55,32 @@ body {
|
||||
.page_toolbar {
|
||||
--gap: 2.5rem;
|
||||
font-size: 14px;
|
||||
min-height: 50px;
|
||||
box-sizing: border-box;
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #EDF2F7;
|
||||
padding: var(--top_panel_vertical_height) 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle {
|
||||
.page_toolbar__middle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--gap);
|
||||
align-items: center;
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle > div {
|
||||
.page_toolbar__middle > div {
|
||||
position: relative;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.page_toolbar .page_toolbar__middle > div {
|
||||
.page_toolbar__middle > div {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle > div:not(:first-child):after {
|
||||
.page_toolbar__middle > div:not(:first-child):after {
|
||||
content: "|";
|
||||
position: absolute;
|
||||
left: calc(var(--gap) / 2 * -1);
|
||||
@@ -80,7 +89,7 @@ body {
|
||||
line-height: 1;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
.page_toolbar .page_toolbar__middle > div select {
|
||||
.page_toolbar__middle > div select {
|
||||
font-size: 0.85em;
|
||||
margin: 0;
|
||||
display: inline-block;
|
||||
@@ -90,28 +99,63 @@ body {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
@media (max-width: 1024px) {
|
||||
.page_toolbar .page_toolbar__middle__data_options {
|
||||
.page_toolbar__middle__data_options {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
.page_toolbar__left, .page_toolbar__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-width: 480px;
|
||||
}
|
||||
.page_toolbar__right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.preview {
|
||||
overflow-y: scroll;
|
||||
height: calc(100% - var(--top_panel_height));
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#preview_frame {
|
||||
display: block;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
min-height: 100%;
|
||||
--top_spacing: 0px;
|
||||
--breakpoint_top_spacing: 30px;
|
||||
margin-top: var(--top_spacing);
|
||||
height: calc(100% - var(--top_panel_height) - var(--top_spacing));
|
||||
height: 100%;
|
||||
background-color: white;
|
||||
border: 1px solid #E2E8F0;
|
||||
border: 0;
|
||||
outline: 1px solid #E2E8F0;
|
||||
transition: max-width 0.3s ease-in-out, width 0.3s ease-in-out, margin-top 0.3s ease-in-out;
|
||||
}
|
||||
#preview_frame.has-breakpoint {
|
||||
--breakpoint: 100%;
|
||||
width: 100%;
|
||||
max-width: var(--breakpoint);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.open_in_new_tab {
|
||||
--size: 1.5rem;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
background-image: url("/scripts/toolbar/images/icon-open-new-tab.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center center;
|
||||
background-color: initial;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--main.css.map */
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--main.scss","_buttons.scss","_overlay.scss","_page--breakpoints.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EAEA;;;ACTF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;;ACtBJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AFIF;EACE;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EADF;IAEI;;;;AG/DR;EACE;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EAEA;EAEA;;AAEA;EACE;EAEA;EACA;EACA","file":"page--main.css"}
|
||||
{"version":3,"sourceRoot":"","sources":["page--main.scss","_buttons.scss","_overlay.scss","_page--preview.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA;EAEA;;;ACTF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AC5BJ;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;AFIF;EACE;EACA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;EACA;EAEA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAEA;EAHF;IAII;;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKF;EADF;IAEI;;;AAKN;EAEE;EACA;EACA;EACA;;AAGF;EACE;;;AGlFJ;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EAEA;EACA;EAEA;EACA;EACA;EAEA;EACA;EAEA;;AAEA;EACE;EAEA;EACA;;;AH6DJ;EACE;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EAEA;EACA;EACA;EACA;EACA;EACA;EACA;EACA","file":"page--main.css"}
|
||||
@@ -16,6 +16,8 @@ body {
|
||||
.page_toolbar {
|
||||
--gap: 2.5rem;
|
||||
font-size: 14px;
|
||||
min-height: 50px;
|
||||
box-sizing: border-box;
|
||||
|
||||
font-family: Arial, sans-serif;
|
||||
background-color: #EDF2F7;
|
||||
@@ -25,7 +27,10 @@ body {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.page_toolbar__middle {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
&__middle {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: var(--gap);
|
||||
@@ -66,7 +71,39 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
&__left,
|
||||
&__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
min-width: 480px;
|
||||
}
|
||||
|
||||
@import "page--breakpoints";
|
||||
&__right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@import "page--preview";
|
||||
|
||||
.open_in_new_tab {
|
||||
--size: 1.5rem;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
|
||||
background-image: url("/scripts/toolbar/images/icon-open-new-tab.svg");
|
||||
background-repeat: no-repeat;
|
||||
background-size: calc(var(--size) - 0.15rem);
|
||||
background-position: center center;
|
||||
background-color: initial;
|
||||
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
font-size: 1px;
|
||||
color: rgba(0, 0, 0, 0);
|
||||
line-height: 1;
|
||||
display: block;
|
||||
border-radius: 0.25rem;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1920px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
/*# sourceMappingURL=page--view.css.map */
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;;;AAGF;EACE;EACA;EACA;;;AAGF;EACE;EACA;EACA;EACA;EACA","file":"page--view.css"}
|
||||
{"version":3,"sourceRoot":"","sources":["page--view.scss"],"names":[],"mappings":"AAAA;EACE;EACA;;;AAGF;EACE;EACA","file":"page--view.css"}
|
||||
@@ -1,17 +1,18 @@
|
||||
body {
|
||||
margin: 0;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
max-width: 1920px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
// Container rules must be provided by the projectStyles (theme).
|
||||
//.container {
|
||||
// max-width: 1200px;
|
||||
// margin-left: auto;
|
||||
// margin-right: auto;
|
||||
// padding-left: 15px;
|
||||
// padding-right: 15px;
|
||||
//}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@axe-web/create-block",
|
||||
"version": "1.0.12",
|
||||
"name": "@axe-web/block-dev-tool",
|
||||
"version": "1.0.18",
|
||||
"author": {
|
||||
"name": "AXE-WEB",
|
||||
"email": "office@axe-web.com",
|
||||
@@ -8,10 +8,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "component-dev",
|
||||
"dev": "NODE_ENV=development node server.js",
|
||||
"create-block": "node ./create-block.js pull",
|
||||
"dev": "NODE_ENV=development NODE_CONFIG_DIR=blocks/team/config BLOCK_NAME=team node server.js",
|
||||
"build": "rollup --config rollup.config.js",
|
||||
"build-platform": "NODE_ENV=development node ./build.js",
|
||||
"build-platform": "NODE_ENV=development NODE_CONFIG_DIR=blocks/team/config BLOCK_NAME=team node ./build.js",
|
||||
"build-platform-cli": "component-build",
|
||||
"dev-js": "rollup --config rollup.config.js --watch"
|
||||
},
|
||||
@@ -21,7 +20,6 @@
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"archiver": "^5.3.1",
|
||||
"browser-sync": "^2.27.9",
|
||||
"commander": "^9.4.1",
|
||||
"config": "^3.3.7",
|
||||
"escape-html": "^1.0.3",
|
||||
"express": "^4.17.3",
|
||||
@@ -36,12 +34,13 @@
|
||||
"lodash-es": "^4.17.21",
|
||||
"mem-fs": "^2.2.1",
|
||||
"mem-fs-editor": "^9.5.0",
|
||||
"mkdirp": "^1.0.4",
|
||||
"node-fetch": "^3.2.10",
|
||||
"open": "^8.4.0",
|
||||
"plugin-error": "^2.0.0",
|
||||
"sanitize-html": "^2.7.1",
|
||||
"sass": "^1.50.1",
|
||||
"unzipper": "^0.10.11"
|
||||
"yeoman-environment": "^3.10.0",
|
||||
"yeoman-generator": "^5.6.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
@@ -55,19 +54,15 @@
|
||||
"rollup": "^2.77.2",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-jsx": "^1.0.3",
|
||||
"styled-components": "^5.3.5",
|
||||
"yeoman-environment": "^3.10.0",
|
||||
"yeoman-generator": "^5.6.1"
|
||||
"styled-components": "^5.3.5"
|
||||
},
|
||||
"bin": {
|
||||
"create-block": "./create-block.js",
|
||||
"component-dev": "./server.js",
|
||||
"component-build": "./build.js"
|
||||
},
|
||||
"files": [
|
||||
"generators/block/templates/.gitignore",
|
||||
"generators/block/**/*",
|
||||
"helpers.js",
|
||||
"layouts/**/*",
|
||||
"helpers.js"
|
||||
"platforms/**/*"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {readFile, writeFile, mkdir, copyFile} from "fs/promises";
|
||||
import {capitalize} from "../../helpers.js";
|
||||
|
||||
export async function buildHubspot(blockName) {
|
||||
const distPath = `./exports/hubspot/${blockName}.module`;
|
||||
@@ -225,3 +224,18 @@ function convertToHubspotField(field = {}) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function capitalize(str) {
|
||||
if (typeof str !== 'string') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return str
|
||||
.toLowerCase()
|
||||
.split(/[ -_]/g)
|
||||
.filter((word) => !!word)
|
||||
.map((word) => {
|
||||
return word.charAt(0).toUpperCase() + word.slice(1);
|
||||
})
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
@@ -54,14 +54,15 @@ class Component_Builder {
|
||||
public string $component_name = '';
|
||||
private string $module_path = '';
|
||||
|
||||
function __construct( $component_name, $module_path ) {
|
||||
function __construct( $component_name, $module_path, $project_path ) {
|
||||
$this->module_path = $module_path;
|
||||
$this->project_path = $project_path;
|
||||
$this->component_name = $component_name;
|
||||
$this->register_default_handlebar_helpers();
|
||||
}
|
||||
|
||||
function build(): void {
|
||||
$root_path = __DIR__ . '/' . $this->module_path . '/../..';
|
||||
$root_path = __DIR__ . '/' . $this->module_path . '/../../' . $this->project_path;
|
||||
$file_name = $this->get_handlebars_template( "$root_path/src/$this->component_name.template.hbs" );
|
||||
|
||||
$output_folder = $root_path . '/exports/wordpress/templates';
|
||||
@@ -122,4 +123,4 @@ class Component_Builder {
|
||||
}
|
||||
}
|
||||
|
||||
( new Component_Builder( $argv[1], $argv[2] ) )->build();
|
||||
( new Component_Builder( $argv[1], $argv[2], $argv[3] ) )->build();
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
use Core\Global_Functions;
|
||||
|
||||
class <%= blockClassModel %>_Component {
|
||||
|
||||
public function __construct() {
|
||||
add_action( 'wp_enqueue_scripts', [ $this, 'register_assets' ] );
|
||||
}
|
||||
|
||||
function register_assets(): void {
|
||||
wp_enqueue_style( '<%= blockFilename %>',
|
||||
get_template_directory_uri() . '/components/partials/<%= blockFilename %>/templates/styles/<%= blockFilename %>.min.css',
|
||||
[ 'style-wp' ],
|
||||
Global_Functions::get_current_version_number()
|
||||
);
|
||||
|
||||
wp_enqueue_script( '<%= blockFilename %>',
|
||||
get_template_directory_uri() . '/components/partials/<%= blockFilename %>/templates/scripts/<%= blockFilename %>.min.js',
|
||||
[ 'jquery', 'swiper' ],
|
||||
Global_Functions::get_current_version_number(),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public function render( $args = [] ): void {
|
||||
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( array_merge( [], $args ), self::class );
|
||||
echo apply_filters( 'the_content', wpautop( $output ) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>;
|
||||
|
||||
class <%= blockClassModel %>_Component extends \Core\Component {
|
||||
|
||||
public function get_content( $args = [] ): string {
|
||||
$args = array_merge( Helpers\<%= blockClassModel %>_Defaults::default_args(), $args );
|
||||
$args = Helpers\<%= blockClassModel %>_API::prepare_args( $args );
|
||||
|
||||
<% if (templateFormat === 'hbs') { %>return $this->get_component_template( __DIR__ . '/templates/<%= blockFilename %>.template.hbs', $args );<% } %>
|
||||
<% if (templateFormat === 'php') { %>
|
||||
$output = ( include( __DIR__ . '/templates/<%= blockFilename %>.template.php' ) )( array_merge( [], $args ), self::class );
|
||||
echo apply_filters( 'the_content', wpautop( $output ) );
|
||||
<% } %>
|
||||
}
|
||||
|
||||
<% if (!include_acf_block && !include_native_gutenberg_block) { %>
|
||||
public function register_assets() {
|
||||
$this->add_style( __DIR__ . '/templates/styles/<%= blockFilename %>.min.css' );
|
||||
<% if (include_script) { %>$this->add_script( __DIR__ . '/templates/scripts/<%= blockFilename %>.js' );<% } %>
|
||||
}
|
||||
|
||||
<% } %><% if (include_elementor_widget) { %> function register_custom_logic(): void {
|
||||
add_action( 'elementor/widgets/widgets_registered', [ $this, 'register_elementor_widget' ] );
|
||||
}
|
||||
|
||||
<% } %><% if (include_elementor_widget) { %> function register_elementor_widget( $widgets_manager ) {
|
||||
$widgets_manager->register_widget_type( new Helpers\<%= blockClassModel %>_Elementor_Widget() );
|
||||
}
|
||||
|
||||
<% } %><% if (include_acf_block) { %> function register_acf_block() {
|
||||
$this->register_block( __DIR__ . "/<%= blockFilename %>.block.json", [
|
||||
'enqueue_assets' => function () {
|
||||
wp_enqueue_style( '<%= blockFilename %>', \Core\Global_Functions::get_file_url( __DIR__ . '/templates/styles/<%= blockFilename %>.min.css', true ), [], get_blocks_version() );<% if (include_script) { %>
|
||||
wp_enqueue_script( '<%= blockFilename %>', \Core\Global_Functions::get_file_url( __DIR__ . '/templates/scripts/<%= blockFilename %>.js', true ), ['assets-script'], get_blocks_version() );<% } %>
|
||||
},
|
||||
'default' => Helpers\<%= blockClassModel %>_Defaults::default_args(),
|
||||
'supports' => [
|
||||
//'jsx' => true,
|
||||
'color' => [
|
||||
'background' => true,
|
||||
'text' => true,
|
||||
],
|
||||
'spacing' => [
|
||||
'margin' => [ 'top', 'bottom' ],
|
||||
'padding' => [ 'top', 'bottom' ]
|
||||
],
|
||||
]
|
||||
] );
|
||||
}
|
||||
|
||||
<% } %><% if (include_native_gutenberg_block) { %> function register_native_gutenberg_block() {
|
||||
register_block_type( __DIR__ . '/templates/gutenberg-block/block.json' );<% if (include_script) { %>
|
||||
|
||||
add_action( 'wp_enqueue_scripts', function () {
|
||||
$asset_file_front = include( plugin_dir_path( __FILE__ ) . '/templates/gutenberg-block/build/front.asset.php' );
|
||||
wp_enqueue_script(
|
||||
'gutenberg-<%= blockFilename %>-scripts-front',
|
||||
plugins_url( 'templates/gutenberg-block/build/front.js', __FILE__ ),
|
||||
$asset_file_front['dependencies'],
|
||||
$asset_file_front['version'],
|
||||
true
|
||||
);
|
||||
} );<% } %>
|
||||
}<% } %>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
|
||||
|
||||
class <%= blockClassModel %>_API {
|
||||
|
||||
static function prepare_args( $args = [] ) {
|
||||
|
||||
// $args = array_merge( [], $args );
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace AXEWEB_Blocks\Blocks\<%= ownerClass %>\<%= blockClassModel %>\Helpers;
|
||||
|
||||
/**
|
||||
* Component's default args.
|
||||
*/
|
||||
|
||||
class <%= blockClassModel %>_Defaults {
|
||||
public static function default_args(): array {
|
||||
return [
|
||||
'title' => '<%= title %>',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import path from "path";
|
||||
import {mkdir, copyFile} from "fs/promises";
|
||||
import {capitalize, createFiles, getBlockName, getConfigs, readJSONFile} from "../../helpers.js";
|
||||
import {fileURLToPath} from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const {projectPath} = getConfigs();
|
||||
|
||||
export async function buildWordPress(isBlock = false) {
|
||||
const distPath = path.join(projectPath, 'exports', 'wordpress');
|
||||
await mkdir(distPath, {recursive: true})
|
||||
|
||||
const jsonFilePath = path.join(projectPath, 'block.json');
|
||||
await copyFile(jsonFilePath, `${distPath}/block.json`)
|
||||
|
||||
let data = await readJSONFile(jsonFilePath);
|
||||
Object.assign(data, getBlockName(data.name));
|
||||
|
||||
// let data = await readJSONFile(path.join(projectPath, `block.json`));
|
||||
const title = capitalize(data.name);
|
||||
const owner = capitalize(data.project);
|
||||
|
||||
data = Object.assign(data, {
|
||||
title,
|
||||
blockClassModel: title.replace(/ /ig, '_'),
|
||||
blockFilename: title.toLowerCase().replace(/ /ig, '-'),
|
||||
blockClassName: title.toLowerCase().replace(/ /ig, '_'),
|
||||
owner,
|
||||
ownerClass: owner.replace(/ /ig, '_'),
|
||||
ownerFilename: owner.toLowerCase().replace(/ /ig, '-'),
|
||||
templateFormat: 'php',
|
||||
include_acf_block: false,
|
||||
include_native_gutenberg_block: false,
|
||||
include_script: true,
|
||||
include_elementor_widget: false,
|
||||
});
|
||||
|
||||
if (isBlock) {
|
||||
await createFiles(data, [{
|
||||
from: `templates/Template_Component.php`,
|
||||
to: `${data.blockClassModel}_Component.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
|
||||
await createFiles(data, [{
|
||||
from: `templates/helpers/Template_API.php`,
|
||||
to: `helpers/${data.blockClassModel}_API.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
|
||||
await createFiles(data, [{
|
||||
from: `templates/helpers/Template_Defaults.php`,
|
||||
to: `helpers/${data.blockClassModel}_Defaults.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
} else {
|
||||
await createFiles(data, [{
|
||||
from: `templates/Template_Basic_Component.php`,
|
||||
to: `${data.blockClassModel}_Component.php`,
|
||||
}], {
|
||||
pathDist: distPath,
|
||||
generatorsPath: path.join(__dirname)
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import path from 'path';
|
||||
import fetch from "node-fetch";
|
||||
import express from 'express';
|
||||
import {create} from 'express-handlebars';
|
||||
@@ -19,27 +20,25 @@ import {sanitizeUrl} from "@braintree/sanitize-url";
|
||||
import sanitizeHtml from 'sanitize-html';
|
||||
import {escape} from "lodash-es";
|
||||
import archiver from 'archiver';
|
||||
import {getBlockConfigs, getConfigs, readJSONFile} from "./helpers.js";
|
||||
import PluginError from 'plugin-error';
|
||||
|
||||
/**
|
||||
* Constants
|
||||
*/
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development' || (config.has('isDev') && config.get('isDev')); // Check README file in case you get "missing files" error.
|
||||
const {isDev, modulesPath, projectPath, developmentBlockName} = getConfigs();
|
||||
const blocksRegistry = isDev ? 'http://localhost:3020' : 'https://axe-web-blocks-registry.captain.devdevdev.life';
|
||||
const modulePath = isDev ? '' : 'node_modules/create-block-dev-tool/';
|
||||
const projectDir = modulePath;
|
||||
|
||||
const sass = gulpSass(dartSass);
|
||||
|
||||
buildStyleFiles()
|
||||
buildScriptFiles()
|
||||
|
||||
/**
|
||||
* Init server
|
||||
*/
|
||||
|
||||
let port = 3000;
|
||||
let port = 3000; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
|
||||
let previewFrameUrl = `http://localhost:${port}`; // This variable is used in `*.hbs` and it will be updated once BrowserSync is ready.
|
||||
const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
|
||||
const app = express();
|
||||
const sass = gulpSass(dartSass);
|
||||
|
||||
const hbs = create({
|
||||
extname: '.hbs', defaultLayout: false, partialsDir: ['.'], helpers: {
|
||||
@@ -58,21 +57,27 @@ const hbs = create({
|
||||
|
||||
app.engine('.hbs', hbs.engine);
|
||||
app.set('view engine', '.hbs');
|
||||
app.set('views', projectDir + 'layouts');
|
||||
app.set('views', path.join(modulesPath, 'layouts'));
|
||||
|
||||
const dataFiles = prepareListOfDataFiles(await fs.readdir('./data'));
|
||||
//
|
||||
// Routes
|
||||
//
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
let jsonFileName = req.query.data ? req.query.data : 'default';
|
||||
const data = await getBlockConfigs(jsonFileName);
|
||||
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
|
||||
if (data.error && data.errorMessage) {
|
||||
return res.send(data.errorMessage);
|
||||
}
|
||||
|
||||
const baseView = config.has('baseView') ? config.get('baseView') : 'container';
|
||||
const baseViewUrl = `view/${baseView}`;
|
||||
|
||||
data.helpers = {
|
||||
port: port,
|
||||
include_partial: (path) => projectDir + path,
|
||||
baseView: config.has('baseView') ? config.get('baseView') : 'container',
|
||||
port,
|
||||
include_partial: (filesPath) => path.join(modulesPath, filesPath),
|
||||
baseView,
|
||||
previewFrameUrl: `${previewFrameUrl}/${baseViewUrl}`,
|
||||
}
|
||||
|
||||
res.render('index', data);
|
||||
@@ -80,14 +85,18 @@ app.get('/', async (req, res) => {
|
||||
|
||||
app.get('/view/:baseView', async (req, res) => {
|
||||
let jsonFileName = req.query.data ? req.query.data : 'default';
|
||||
const data = await getBlockConfigs(jsonFileName);
|
||||
const data = await getBlockConfigs(jsonFileName, {includeConfigs: true, projectPath, modulesPath, dataFiles});
|
||||
if (data.error && data.errorMessage) {
|
||||
return res.send(data.errorMessage);
|
||||
}
|
||||
|
||||
const blockName = config.has('blockName') ? config.get('blockName') : developmentBlockName;
|
||||
|
||||
data.helpers = {
|
||||
include_partial: (path) => projectDir + path,
|
||||
include_block_template: (path) => 'src/' + (config.has('blockName') ? config.get('blockName') : 'development') + '.template',
|
||||
include_partial: (filesPath) => path.join(modulesPath, filesPath),
|
||||
include_block_template: () => path.join(projectPath, 'src', `${blockName}.template`),
|
||||
section_class: `${blockName}--${jsonFileName}`,
|
||||
base_url: '/'
|
||||
}
|
||||
|
||||
const baseView = req.params.baseView ?? 'container';
|
||||
@@ -96,7 +105,7 @@ app.get('/view/:baseView', async (req, res) => {
|
||||
});
|
||||
|
||||
app.get('/publish', async (req, res) => {
|
||||
const data = await readJSONFile('./block.json');
|
||||
const data = await readJSONFile(path.join(projectPath, `block.json`));
|
||||
let responseData;
|
||||
|
||||
try {
|
||||
@@ -119,7 +128,7 @@ app.get('/publish', async (req, res) => {
|
||||
|
||||
if (responseData.uploadUrl) {
|
||||
await zipProject();
|
||||
const body = await fs.readFile('./dist.zip');
|
||||
const body = await fs.readFile(path.join(projectPath, 'dist.zip'));
|
||||
const response = await fetch(`${responseData.uploadUrl}`, {
|
||||
method: 'PUT',
|
||||
body,
|
||||
@@ -129,7 +138,7 @@ app.get('/publish', async (req, res) => {
|
||||
if (response.status !== 200) {
|
||||
res.json({success: false, message: "Can't upload the archive, permissions error."});
|
||||
// TODO: Need to update the registry server.
|
||||
await fs.unlink('./dist.zip');
|
||||
await fs.unlink(path.join(projectPath, 'dist.zip'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -137,48 +146,117 @@ app.get('/publish', async (req, res) => {
|
||||
|
||||
res.json({success: true});
|
||||
|
||||
await fs.unlink('./dist.zip');
|
||||
await fs.unlink(path.join(projectPath, 'dist.zip'));
|
||||
});
|
||||
|
||||
app.use(express.static('src'));
|
||||
app.use(express.static(projectDir + 'layouts'));
|
||||
app.get('/data', async (req, res) => {
|
||||
let jsonDataFileName = req.query.name ? req.query.name : 'default';
|
||||
|
||||
const dataFiles = prepareListOfDataFiles(await fs.readdir(path.join(projectPath, 'data')));
|
||||
const data = await getBlockConfigs(jsonDataFileName, {projectPath, modulesPath, dataFiles});
|
||||
const designPreviewFiles = getListOfDesignPreviewFiles(jsonDataFileName, await fs.readdir(path.join(projectPath, 'design', 'preview')));
|
||||
|
||||
return res.json({
|
||||
dataOptions: dataFiles,
|
||||
designPreview: designPreviewFiles,
|
||||
data,
|
||||
});
|
||||
});
|
||||
|
||||
// Errors handler
|
||||
app.use(handleSyntaxErrors);
|
||||
|
||||
// Static Files
|
||||
app.use(express.static(path.join(projectPath, 'src')));
|
||||
app.use(express.static(path.join(projectPath, 'design')));
|
||||
app.use(express.static(path.join(modulesPath, 'layouts')));
|
||||
|
||||
// Setup Gulp
|
||||
await buildAssetFiles();
|
||||
|
||||
// BrowserSync
|
||||
const bsOptions = await startBrowserSync();
|
||||
port = bsOptions.port;
|
||||
previewFrameUrl = bsOptions.previewFrameUrl;
|
||||
await open(bsOptions.devToolUrl);
|
||||
|
||||
|
||||
//
|
||||
// Functions
|
||||
//
|
||||
|
||||
function getListOfDesignPreviewFiles(jsonDataFileName, previewFiles) {
|
||||
return previewFiles
|
||||
.filter(fileName => {
|
||||
return fileName.startsWith(jsonDataFileName + '.');
|
||||
})
|
||||
.map(fileName => {
|
||||
const fileData = fileName.split('.');
|
||||
const fileFormat = fileData.pop();
|
||||
const previewSize = fileData.pop();
|
||||
|
||||
return {
|
||||
dataSource: jsonDataFileName,
|
||||
widthDimension: Number.parseInt(previewSize, 10),
|
||||
url: `/preview/${fileName}`,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function startBrowserSync() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const listener = app.listen(0, async () => {
|
||||
const PORT = listener.address().port;
|
||||
await open(`http://localhost:${PORT}`);
|
||||
|
||||
console.log(`The web server has started on port ${PORT}`);
|
||||
|
||||
const bs = browserSync.create();
|
||||
|
||||
gulp.watch(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'], {delay: 400}, gulp.series([buildScriptFiles, function (cb) {
|
||||
const files = getJSBundleFiles();
|
||||
gulp.watch(files, {delay: 400}, gulp.series(['build-script-files', function (cb) {
|
||||
browserSyncReload(bs, 'js', 'Script Files Change');
|
||||
return cb();
|
||||
}]));
|
||||
|
||||
gulp.watch('src/**/*.scss', {delay: 400}, gulp.series([buildStyleFiles, function (cb) {
|
||||
gulp.watch(path.join(projectPath, 'src/**/*.scss'), {delay: 400}, gulp.series(['build-styling-files', function (cb) {
|
||||
browserSyncReload(bs, 'css', 'Style Files Change');
|
||||
return cb();
|
||||
}]));
|
||||
|
||||
// bs.watch("src/**/*.js", function (event, file) {});
|
||||
// bs.watch("src/**/*.css", function (event, file) {});
|
||||
|
||||
bs.watch("src/**/*.hbs", function (event, file) {
|
||||
browserSyncReload(bs, '', 'Template File Change: ' + file)
|
||||
});
|
||||
|
||||
bs.init({
|
||||
proxy: `http://localhost:${PORT}`, open: false
|
||||
proxy: `http://localhost:${PORT}`,
|
||||
open: false
|
||||
}, (err, bs) => {
|
||||
port = bs.server._connectionKey.split(':').pop();
|
||||
});
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
const options = bs.getOptions().toJS();
|
||||
|
||||
/**
|
||||
* Functions
|
||||
*/
|
||||
const urls = {
|
||||
devTool: options.urls.local.replace(options.port, options.proxy.url.port),
|
||||
previewFrame: options.urls.local,
|
||||
};
|
||||
|
||||
// If local network is available.
|
||||
if (options.urls.external) {
|
||||
urls.devTool = options.urls.external.replace(options.port, options.proxy.url.port);
|
||||
urls.previewFrame = options.urls.external;
|
||||
}
|
||||
|
||||
resolve({
|
||||
devToolUrl: urls.devTool,
|
||||
previewFrameUrl: urls.previewFrame,
|
||||
port: options.port
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function browserSyncReload(bs, extension = '', message = '') {
|
||||
if (isDev) {
|
||||
@@ -193,26 +271,58 @@ function browserSyncReload(bs, extension = '', message = '') {
|
||||
bs.reload(extension);
|
||||
}
|
||||
|
||||
function buildScriptFiles() {
|
||||
return gulp.src(['src/**/*.js', 'src/**/*.mjs', '!src/**/*.min.js'])
|
||||
function getJSBundleFiles() {
|
||||
return [path.join(projectPath, 'src/**/*.js'), path.join(projectPath, 'src/**/*.mjs'), '!' + path.join(projectPath, 'src/**/*.min.js')];
|
||||
}
|
||||
|
||||
function buildScriptFiles(done) {
|
||||
const files = getJSBundleFiles();
|
||||
return gulp.src(files)
|
||||
.pipe(sourcemaps.init({}))
|
||||
.pipe(babel())
|
||||
.pipe(gulp.src('vendor/*.js'))
|
||||
.pipe(babel()).on('error', function (error) {
|
||||
showError(new PluginError('JavaScript', error).toString());
|
||||
done();
|
||||
})
|
||||
.pipe(gulp.src(path.join(projectPath, 'vendor/*.js')))
|
||||
// .pipe(gulp.dest('src/'))
|
||||
.pipe(uglify())
|
||||
.pipe(rename({extname: '.min.js'}))
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('src/'));
|
||||
.pipe(gulp.dest(path.join(projectPath, 'src/')));
|
||||
}
|
||||
|
||||
function buildStyleFiles() {
|
||||
return gulp.src('src/**/*.scss')
|
||||
function buildStyleFiles(done) {
|
||||
return gulp.src(path.join(projectPath, 'src/**/*.scss'))
|
||||
.pipe(sourcemaps.init({}))
|
||||
.pipe(sass.sync().on('error', sass.logError))
|
||||
.pipe(sass.sync({outputStyle: 'compressed'}).on('error', function (error) {
|
||||
showError(new PluginError('SCSS', error.messageFormatted).toString());
|
||||
// sass.logError(error);
|
||||
done();
|
||||
}))
|
||||
// .pipe(gulp.dest('src/'))
|
||||
.pipe(rename({extname: '.min.css'}))
|
||||
.pipe(sourcemaps.write('.'))
|
||||
.pipe(gulp.dest('src/'))
|
||||
.pipe(sourcemaps.write('.', {}))
|
||||
.pipe(gulp.dest(path.join(projectPath, 'src')))
|
||||
}
|
||||
|
||||
function buildAssetFiles() {
|
||||
// Register tasks.
|
||||
gulp.task('build-script-files', buildScriptFiles);
|
||||
gulp.task('build-styling-files', buildStyleFiles);
|
||||
|
||||
// Run first build.
|
||||
return new Promise((resolve) => {
|
||||
gulp.series('build-script-files', 'build-styling-files', function (cb) {
|
||||
resolve();
|
||||
})();
|
||||
});
|
||||
}
|
||||
|
||||
function showError(errorMessage) {
|
||||
console.log(errorMessage);
|
||||
|
||||
// TODO: Send this message to browser.
|
||||
// So the developer can understand there is an error.
|
||||
}
|
||||
|
||||
function prepareListOfDataFiles(dataFiles) {
|
||||
@@ -226,46 +336,6 @@ function prepareListOfDataFiles(dataFiles) {
|
||||
.sort();
|
||||
}
|
||||
|
||||
async function readJSONFile(jsonFile) {
|
||||
let data = {};
|
||||
|
||||
try {
|
||||
data = await fsExtra.readJson(jsonFile);
|
||||
} catch (e) {
|
||||
return {
|
||||
error: true, errorMessage: getErrorHtml("JSON Syntax error. Please make sure the dataFile is valid.", e),
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function getBlockConfigs(jsonFileName = 'default') {
|
||||
let data = await readJSONFile(`./data/${jsonFileName}.json`);
|
||||
if (data.error) {
|
||||
return data;
|
||||
}
|
||||
|
||||
Object.assign(data, {
|
||||
config: Object.assign(JSON.parse(JSON.stringify(config)), // The entire config object.
|
||||
{
|
||||
projectDir, activeDataFile: jsonFileName, dataFiles: dataFiles.map((name) => {
|
||||
return {
|
||||
name, active: jsonFileName === name,
|
||||
};
|
||||
}), remToPx: config.has('remToPx') ? config.get('remToPx') : 16,
|
||||
})
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function getErrorHtml(message = '', errorMessage = '') {
|
||||
return `<div style="padding: 10px 15px; font-family: Arial, sans-serif">
|
||||
<p>${message}</p>
|
||||
<pre style="padding: 10px 15px; background-color: #ffd0d0; border: 1px solid red;">${errorMessage}</pre>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function zipProject() {
|
||||
// create a file to stream archive data to.
|
||||
@@ -304,10 +374,23 @@ async function zipProject() {
|
||||
// pipe archive data to the file
|
||||
archive.pipe(output);
|
||||
|
||||
// append files from a sub-directory, putting its contents at the root of archive
|
||||
archive.directory('src/', false);
|
||||
// append files from a subdirectory, putting its contents at the root of archive
|
||||
archive.directory(path.join(projectPath, 'src', '/'), false);
|
||||
|
||||
// finalize the archive (ie we are done appending files but streams have to finish yet)
|
||||
// 'close', 'end' or 'finish' may be fired right after calling this method so register to them beforehand
|
||||
await archive.finalize();
|
||||
}
|
||||
|
||||
function handleSyntaxErrors(err, req, res, next) {
|
||||
if (err) {
|
||||
return res.render('error', {
|
||||
helpers: {
|
||||
include_partial: (filesPath) => path.join(modulesPath, filesPath),
|
||||
},
|
||||
err
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||