From 0a43e308724572afe1bf8ae000ebe634c5878df2 Mon Sep 17 00:00:00 2001 From: Sean Morris <640101+seanmorris@users.noreply.github.com> Date: Tue, 14 Nov 2023 18:42:58 -0500 Subject: [PATCH 1/5] RunSqlFileStep. --- .../blueprints/public/blueprint-schema.json | 2568 +++++++++-------- .../blueprints/src/lib/steps/common.ts | 4 + .../blueprints/src/lib/steps/handlers.ts | 1 + .../blueprints/src/lib/steps/index.ts | 3 + .../blueprints/src/lib/steps/run-sql-file.ts | 47 + 5 files changed, 1419 insertions(+), 1204 deletions(-) create mode 100644 packages/playground/blueprints/src/lib/steps/run-sql-file.ts diff --git a/packages/playground/blueprints/public/blueprint-schema.json b/packages/playground/blueprints/public/blueprint-schema.json index 562d8fbdf5..522e034fb1 100644 --- a/packages/playground/blueprints/public/blueprint-schema.json +++ b/packages/playground/blueprints/public/blueprint-schema.json @@ -1,1205 +1,1365 @@ { - "$schema": "http://json-schema.org/schema", - "$ref": "#/definitions/Blueprint", - "definitions": { - "Blueprint": { - "type": "object", - "properties": { - "landingPage": { - "type": "string", - "description": "The URL to navigate to after the blueprint has been run." - }, - "preferredVersions": { - "type": "object", - "properties": { - "php": { - "anyOf": [ - { - "$ref": "#/definitions/SupportedPHPVersion" - }, - { - "type": "string", - "const": "latest" - } - ], - "description": "The preferred PHP version to use. If not specified, the latest supported version will be used" - }, - "wp": { - "type": "string", - "description": "The preferred WordPress version to use. If not specified, the latest supported version will be used" - } - }, - "required": ["php", "wp"], - "additionalProperties": false, - "description": "The preferred PHP and WordPress versions to use." - }, - "features": { - "type": "object", - "properties": { - "networking": { - "type": "boolean", - "description": "Should boot with support for network request via wp_safe_remote_get?" - } - }, - "additionalProperties": false - }, - "phpExtensionBundles": { - "type": "array", - "items": { - "$ref": "#/definitions/SupportedPHPExtensionBundle" - }, - "description": "The PHP extensions to use." - }, - "steps": { - "type": "array", - "items": { - "anyOf": [ - { - "$ref": "#/definitions/StepDefinition" - }, - { - "type": "string" - }, - { - "not": {} - }, - { - "type": "boolean", - "const": false - }, - { - "type": "null" - } - ] - }, - "description": "The steps to run." - }, - "$schema": { - "type": "string" - } - }, - "additionalProperties": false - }, - "SupportedPHPVersion": { - "type": "string", - "enum": ["8.2", "8.1", "8.0", "7.4", "7.3", "7.2", "7.1", "7.0"] - }, - "SupportedPHPExtensionBundle": { - "type": "string", - "const": "kitchen-sink" - }, - "StepDefinition": { - "type": "object", - "discriminator": { - "propertyName": "step" - }, - "required": ["step"], - "oneOf": [ - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "activatePlugin" - }, - "pluginPath": { - "type": "string", - "description": "Path to the plugin directory as absolute path (/wordpress/wp-content/plugins/plugin-name); or the plugin entry file relative to the plugins directory (plugin-name/plugin-name.php)." - }, - "pluginName": { - "type": "string", - "description": "Optional. Plugin name to display in the progress bar." - } - }, - "required": ["pluginPath", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "activateTheme" - }, - "themeFolderName": { - "type": "string", - "description": "The name of the theme folder inside wp-content/themes/" - } - }, - "required": ["step", "themeFolderName"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "applyWordPressPatches" - }, - "siteUrl": { - "type": "string" - }, - "wordpressPath": { - "type": "string" - }, - "addPhpInfo": { - "type": "boolean" - }, - "patchSecrets": { - "type": "boolean" - }, - "disableSiteHealth": { - "type": "boolean" - }, - "disableWpNewBlogNotification": { - "type": "boolean" - }, - "makeEditorFrameControlled": { - "type": "boolean" - }, - "prepareForRunningInsideWebBrowser": { - "type": "boolean" - } - }, - "required": ["step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "cp" - }, - "fromPath": { - "type": "string", - "description": "Source path" - }, - "toPath": { - "type": "string", - "description": "Target path" - } - }, - "required": ["fromPath", "step", "toPath"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "defineWpConfigConsts" - }, - "consts": { - "type": "object", - "additionalProperties": {}, - "description": "The constants to define" - }, - "virtualize": { - "type": "boolean", - "description": "Enables the virtualization of wp-config.php and playground-consts.json files, leaving the local system files untouched. The variables defined in the /vfs-blueprints/playground-consts.json file are loaded via the auto_prepend_file directive in the php.ini file.", - "default": false - } - }, - "required": ["consts", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "defineSiteUrl" - }, - "siteUrl": { - "type": "string", - "description": "The URL" - } - }, - "required": ["siteUrl", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "importFile" - }, - "file": { - "$ref": "#/definitions/FileReference", - "description": "The file to import" - } - }, - "required": ["file", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "installPlugin", - "description": "The step identifier." - }, - "pluginZipFile": { - "$ref": "#/definitions/FileReference", - "description": "The plugin zip file to install." - }, - "options": { - "$ref": "#/definitions/InstallPluginOptions", - "description": "Optional installation options." - } - }, - "required": ["pluginZipFile", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "installTheme", - "description": "The step identifier." - }, - "themeZipFile": { - "$ref": "#/definitions/FileReference", - "description": "The theme zip file to install." - }, - "options": { - "type": "object", - "properties": { - "activate": { - "type": "boolean", - "description": "Whether to activate the theme after installing it." - } - }, - "additionalProperties": false, - "description": "Optional installation options." - } - }, - "required": ["step", "themeZipFile"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "login" - }, - "username": { - "type": "string", - "description": "The user to log in as. Defaults to 'admin'." - }, - "password": { - "type": "string", - "description": "The password to log in with. Defaults to 'password'." - } - }, - "required": ["step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "mkdir" - }, - "path": { - "type": "string", - "description": "The path of the directory you want to create" - } - }, - "required": ["path", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "mv" - }, - "fromPath": { - "type": "string", - "description": "Source path" - }, - "toPath": { - "type": "string", - "description": "Target path" - } - }, - "required": ["fromPath", "step", "toPath"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "request" - }, - "request": { - "$ref": "#/definitions/PHPRequest", - "description": "Request details (See /wordpress-playground/api/universal/interface/PHPRequest)" - } - }, - "required": ["request", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "replaceSite" - }, - "fullSiteZip": { - "$ref": "#/definitions/FileReference", - "description": "The zip file containing the new WordPress site" - } - }, - "required": ["fullSiteZip", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "rm" - }, - "path": { - "type": "string", - "description": "The path to remove" - } - }, - "required": ["path", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "rmdir" - }, - "path": { - "type": "string", - "description": "The path to remove" - } - }, - "required": ["path", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "runPHP", - "description": "The step identifier." - }, - "code": { - "type": "string", - "description": "The PHP code to run." - } - }, - "required": ["code", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "runPHPWithOptions" - }, - "options": { - "$ref": "#/definitions/PHPRunOptions", - "description": "Run options (See /wordpress-playground/api/universal/interface/PHPRunOptions)" - } - }, - "required": ["options", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "runWpInstallationWizard" - }, - "options": { - "$ref": "#/definitions/WordPressInstallationOptions" - } - }, - "required": ["options", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "setPhpIniEntry" - }, - "key": { - "type": "string", - "description": "Entry name e.g. \"display_errors\"" - }, - "value": { - "type": "string", - "description": "Entry value as a string e.g. \"1\"" - } - }, - "required": ["key", "step", "value"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "setSiteOptions", - "description": "The name of the step. Must be \"setSiteOptions\"." - }, - "options": { - "type": "object", - "additionalProperties": {}, - "description": "The options to set on the site." - } - }, - "required": ["options", "step"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "unzip" - }, - "zipPath": { - "type": "string", - "description": "The zip file to extract" - }, - "extractToPath": { - "type": "string", - "description": "The path to extract the zip file to" - } - }, - "required": ["extractToPath", "step", "zipPath"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "updateUserMeta" - }, - "meta": { - "type": "object", - "additionalProperties": {}, - "description": "An object of user meta values to set, e.g. { \"first_name\": \"John\" }" - }, - "userId": { - "type": "number", - "description": "User ID" - } - }, - "required": ["meta", "step", "userId"] - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "progress": { - "type": "object", - "properties": { - "weight": { - "type": "number" - }, - "caption": { - "type": "string" - } - }, - "additionalProperties": false - }, - "step": { - "type": "string", - "const": "writeFile" - }, - "path": { - "type": "string", - "description": "The path of the file to write to" - }, - "data": { - "anyOf": [ - { - "$ref": "#/definitions/FileReference" - }, - { - "type": "string" - }, - { - "type": "object", - "properties": { - "BYTES_PER_ELEMENT": { - "type": "number" - }, - "buffer": { - "type": "object", - "properties": { - "byteLength": { - "type": "number" - } - }, - "required": ["byteLength"], - "additionalProperties": false - }, - "byteLength": { - "type": "number" - }, - "byteOffset": { - "type": "number" - }, - "length": { - "type": "number" - } - }, - "required": [ - "BYTES_PER_ELEMENT", - "buffer", - "byteLength", - "byteOffset", - "length" - ], - "additionalProperties": { - "type": "number" - } - } - ], - "description": "The data to write" - } - }, - "required": ["data", "path", "step"] - } - ] - }, - "FileReference": { - "anyOf": [ - { - "$ref": "#/definitions/VFSReference" - }, - { - "$ref": "#/definitions/LiteralReference" - }, - { - "$ref": "#/definitions/CoreThemeReference" - }, - { - "$ref": "#/definitions/CorePluginReference" - }, - { - "$ref": "#/definitions/UrlReference" - } - ] - }, - "VFSReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "vfs", - "description": "Identifies the file resource as Virtual File System (VFS)" - }, - "path": { - "type": "string", - "description": "The path to the file in the VFS" - } - }, - "required": ["resource", "path"], - "additionalProperties": false - }, - "LiteralReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "literal", - "description": "Identifies the file resource as a literal file" - }, - "name": { - "type": "string", - "description": "The name of the file" - }, - "contents": { - "anyOf": [ - { - "type": "string" - }, - { - "type": "object", - "properties": { - "BYTES_PER_ELEMENT": { - "type": "number" - }, - "buffer": { - "type": "object", - "properties": { - "byteLength": { - "type": "number" - } - }, - "required": ["byteLength"], - "additionalProperties": false - }, - "byteLength": { - "type": "number" - }, - "byteOffset": { - "type": "number" - }, - "length": { - "type": "number" - } - }, - "required": [ - "BYTES_PER_ELEMENT", - "buffer", - "byteLength", - "byteOffset", - "length" - ], - "additionalProperties": { - "type": "number" - } - } - ], - "description": "The contents of the file" - } - }, - "required": ["resource", "name", "contents"], - "additionalProperties": false - }, - "CoreThemeReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "wordpress.org/themes", - "description": "Identifies the file resource as a WordPress Core theme" - }, - "slug": { - "type": "string", - "description": "The slug of the WordPress Core theme" - } - }, - "required": ["resource", "slug"], - "additionalProperties": false - }, - "CorePluginReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "wordpress.org/plugins", - "description": "Identifies the file resource as a WordPress Core plugin" - }, - "slug": { - "type": "string", - "description": "The slug of the WordPress Core plugin" - } - }, - "required": ["resource", "slug"], - "additionalProperties": false - }, - "UrlReference": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "const": "url", - "description": "Identifies the file resource as a URL" - }, - "url": { - "type": "string", - "description": "The URL of the file" - }, - "caption": { - "type": "string", - "description": "Optional caption for displaying a progress message" - } - }, - "required": ["resource", "url"], - "additionalProperties": false - }, - "InstallPluginOptions": { - "type": "object", - "properties": { - "activate": { - "type": "boolean", - "description": "Whether to activate the plugin after installing it." - } - }, - "additionalProperties": false - }, - "PHPRequest": { - "type": "object", - "properties": { - "method": { - "$ref": "#/definitions/HTTPMethod", - "description": "Request method. Default: `GET`." - }, - "url": { - "type": "string", - "description": "Request path or absolute URL." - }, - "headers": { - "$ref": "#/definitions/PHPRequestHeaders", - "description": "Request headers." - }, - "files": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "size": { - "type": "number" - }, - "type": { - "type": "string" - }, - "lastModified": { - "type": "number" - }, - "name": { - "type": "string" - }, - "webkitRelativePath": { - "type": "string" - } - }, - "required": [ - "lastModified", - "name", - "size", - "type", - "webkitRelativePath" - ], - "additionalProperties": false - }, - "description": "Uploaded files" - }, - "body": { - "type": "string", - "description": "Request body without the files." - }, - "formData": { - "type": "object", - "additionalProperties": {}, - "description": "Form data. If set, the request body will be ignored and the content-type header will be set to `application/x-www-form-urlencoded`." - } - }, - "required": ["url"], - "additionalProperties": false - }, - "HTTPMethod": { - "type": "string", - "enum": ["GET", "POST", "HEAD", "OPTIONS", "PATCH", "PUT", "DELETE"] - }, - "PHPRequestHeaders": { - "type": "object", - "additionalProperties": { - "type": "string" - } - }, - "PHPRunOptions": { - "type": "object", - "properties": { - "relativeUri": { - "type": "string", - "description": "Request path following the domain:port part." - }, - "scriptPath": { - "type": "string", - "description": "Path of the .php file to execute." - }, - "protocol": { - "type": "string", - "description": "Request protocol." - }, - "method": { - "$ref": "#/definitions/HTTPMethod", - "description": "Request method. Default: `GET`." - }, - "headers": { - "$ref": "#/definitions/PHPRequestHeaders", - "description": "Request headers." - }, - "body": { - "type": "string", - "description": "Request body without the files." - }, - "fileInfos": { - "type": "array", - "items": { - "$ref": "#/definitions/FileInfo" - }, - "description": "Uploaded files." - }, - "code": { - "type": "string", - "description": "The code snippet to eval instead of a php file." - } - }, - "additionalProperties": false - }, - "FileInfo": { - "type": "object", - "properties": { - "key": { - "type": "string" - }, - "name": { - "type": "string" - }, - "type": { - "type": "string" - }, - "data": { - "type": "object", - "properties": { - "BYTES_PER_ELEMENT": { - "type": "number" - }, - "buffer": { - "type": "object", - "properties": { - "byteLength": { - "type": "number" - } - }, - "required": ["byteLength"], - "additionalProperties": false - }, - "byteLength": { - "type": "number" - }, - "byteOffset": { - "type": "number" - }, - "length": { - "type": "number" - } - }, - "required": [ - "BYTES_PER_ELEMENT", - "buffer", - "byteLength", - "byteOffset", - "length" - ], - "additionalProperties": { - "type": "number" - } - } - }, - "required": ["key", "name", "type", "data"], - "additionalProperties": false - }, - "WordPressInstallationOptions": { - "type": "object", - "properties": { - "adminUsername": { - "type": "string" - }, - "adminPassword": { - "type": "string" - } - }, - "additionalProperties": false - } - } -} + "$schema": "http://json-schema.org/schema", + "$ref": "#/definitions/Blueprint", + "definitions": { + "Blueprint": { + "type": "object", + "properties": { + "landingPage": { + "type": "string", + "description": "The URL to navigate to after the blueprint has been run." + }, + "preferredVersions": { + "type": "object", + "properties": { + "php": { + "anyOf": [ + { + "$ref": "#/definitions/SupportedPHPVersion" + }, + { + "type": "string", + "const": "latest" + } + ], + "description": "The preferred PHP version to use. If not specified, the latest supported version will be used" + }, + "wp": { + "type": "string", + "description": "The preferred WordPress version to use. If not specified, the latest supported version will be used" + } + }, + "required": [ + "php", + "wp" + ], + "additionalProperties": false, + "description": "The preferred PHP and WordPress versions to use." + }, + "features": { + "type": "object", + "properties": { + "networking": { + "type": "boolean", + "description": "Should boot with support for network request via wp_safe_remote_get?" + } + }, + "additionalProperties": false + }, + "phpExtensionBundles": { + "type": "array", + "items": { + "$ref": "#/definitions/SupportedPHPExtensionBundle" + }, + "description": "The PHP extensions to use." + }, + "steps": { + "type": "array", + "items": { + "anyOf": [ + { + "$ref": "#/definitions/StepDefinition" + }, + { + "type": "string" + }, + { + "not": {} + }, + { + "type": "boolean", + "const": false + }, + { + "type": "null" + } + ] + }, + "description": "The steps to run." + }, + "$schema": { + "type": "string" + } + }, + "additionalProperties": false + }, + "SupportedPHPVersion": { + "type": "string", + "enum": [ + "8.2", + "8.1", + "8.0", + "7.4", + "7.3", + "7.2", + "7.1", + "7.0" + ] + }, + "SupportedPHPExtensionBundle": { + "type": "string", + "const": "kitchen-sink" + }, + "StepDefinition": { + "type": "object", + "discriminator": { + "propertyName": "step" + }, + "required": [ + "step" + ], + "oneOf": [ + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "activatePlugin" + }, + "pluginPath": { + "type": "string", + "description": "Path to the plugin directory as absolute path (/wordpress/wp-content/plugins/plugin-name); or the plugin entry file relative to the plugins directory (plugin-name/plugin-name.php)." + }, + "pluginName": { + "type": "string", + "description": "Optional. Plugin name to display in the progress bar." + } + }, + "required": [ + "pluginPath", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "activateTheme" + }, + "themeFolderName": { + "type": "string", + "description": "The name of the theme folder inside wp-content/themes/" + } + }, + "required": [ + "step", + "themeFolderName" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "applyWordPressPatches" + }, + "siteUrl": { + "type": "string" + }, + "wordpressPath": { + "type": "string" + }, + "addPhpInfo": { + "type": "boolean" + }, + "patchSecrets": { + "type": "boolean" + }, + "disableSiteHealth": { + "type": "boolean" + }, + "disableWpNewBlogNotification": { + "type": "boolean" + }, + "makeEditorFrameControlled": { + "type": "boolean" + }, + "prepareForRunningInsideWebBrowser": { + "type": "boolean" + }, + "addFetchNetworkTransport": { + "type": "boolean" + } + }, + "required": [ + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "cp" + }, + "fromPath": { + "type": "string", + "description": "Source path" + }, + "toPath": { + "type": "string", + "description": "Target path" + } + }, + "required": [ + "fromPath", + "step", + "toPath" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "defineWpConfigConsts" + }, + "consts": { + "type": "object", + "additionalProperties": {}, + "description": "The constants to define" + }, + "virtualize": { + "type": "boolean", + "deprecated": "This option is noop and will be removed in a future version.\nThis option is only kept in here to avoid breaking Blueprint schema validation\nfor existing apps using this option." + } + }, + "required": [ + "consts", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "defineSiteUrl" + }, + "siteUrl": { + "type": "string", + "description": "The URL" + } + }, + "required": [ + "siteUrl", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "importFile" + }, + "file": { + "$ref": "#/definitions/FileReference", + "description": "The file to import" + } + }, + "required": [ + "file", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "installPlugin", + "description": "The step identifier." + }, + "pluginZipFile": { + "$ref": "#/definitions/FileReference", + "description": "The plugin zip file to install." + }, + "options": { + "$ref": "#/definitions/InstallPluginOptions", + "description": "Optional installation options." + } + }, + "required": [ + "pluginZipFile", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "installTheme", + "description": "The step identifier." + }, + "themeZipFile": { + "$ref": "#/definitions/FileReference", + "description": "The theme zip file to install." + }, + "options": { + "type": "object", + "properties": { + "activate": { + "type": "boolean", + "description": "Whether to activate the theme after installing it." + } + }, + "additionalProperties": false, + "description": "Optional installation options." + } + }, + "required": [ + "step", + "themeZipFile" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "login" + }, + "username": { + "type": "string", + "description": "The user to log in as. Defaults to 'admin'." + }, + "password": { + "type": "string", + "description": "The password to log in with. Defaults to 'password'." + } + }, + "required": [ + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "mkdir" + }, + "path": { + "type": "string", + "description": "The path of the directory you want to create" + } + }, + "required": [ + "path", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "mv" + }, + "fromPath": { + "type": "string", + "description": "Source path" + }, + "toPath": { + "type": "string", + "description": "Target path" + } + }, + "required": [ + "fromPath", + "step", + "toPath" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "request" + }, + "request": { + "$ref": "#/definitions/PHPRequest", + "description": "Request details (See /wordpress-playground/api/universal/interface/PHPRequest)" + } + }, + "required": [ + "request", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "replaceSite" + }, + "fullSiteZip": { + "$ref": "#/definitions/FileReference", + "description": "The zip file containing the new WordPress site" + } + }, + "required": [ + "fullSiteZip", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "rm" + }, + "path": { + "type": "string", + "description": "The path to remove" + } + }, + "required": [ + "path", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "rmdir" + }, + "path": { + "type": "string", + "description": "The path to remove" + } + }, + "required": [ + "path", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "runPHP", + "description": "The step identifier." + }, + "code": { + "type": "string", + "description": "The PHP code to run." + } + }, + "required": [ + "code", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "runPHPWithOptions" + }, + "options": { + "$ref": "#/definitions/PHPRunOptions", + "description": "Run options (See /wordpress-playground/api/universal/interface/PHPRunOptions)" + } + }, + "required": [ + "options", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "runWpInstallationWizard" + }, + "options": { + "$ref": "#/definitions/WordPressInstallationOptions" + } + }, + "required": [ + "options", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "runSqlFile", + "description": "The step identifier." + }, + "path": { + "type": "string", + "description": "The plugin zip file to install." + } + }, + "required": [ + "path", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "setPhpIniEntry" + }, + "key": { + "type": "string", + "description": "Entry name e.g. \"display_errors\"" + }, + "value": { + "type": "string", + "description": "Entry value as a string e.g. \"1\"" + } + }, + "required": [ + "key", + "step", + "value" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "setSiteOptions", + "description": "The name of the step. Must be \"setSiteOptions\"." + }, + "options": { + "type": "object", + "additionalProperties": {}, + "description": "The options to set on the site." + } + }, + "required": [ + "options", + "step" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "unzip" + }, + "zipPath": { + "type": "string", + "description": "The zip file to extract" + }, + "extractToPath": { + "type": "string", + "description": "The path to extract the zip file to" + } + }, + "required": [ + "extractToPath", + "step", + "zipPath" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "updateUserMeta" + }, + "meta": { + "type": "object", + "additionalProperties": {}, + "description": "An object of user meta values to set, e.g. { \"first_name\": \"John\" }" + }, + "userId": { + "type": "number", + "description": "User ID" + } + }, + "required": [ + "meta", + "step", + "userId" + ] + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "progress": { + "type": "object", + "properties": { + "weight": { + "type": "number" + }, + "caption": { + "type": "string" + } + }, + "additionalProperties": false + }, + "step": { + "type": "string", + "const": "writeFile" + }, + "path": { + "type": "string", + "description": "The path of the file to write to" + }, + "data": { + "anyOf": [ + { + "$ref": "#/definitions/FileReference" + }, + { + "type": "string" + }, + { + "type": "object", + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "type": "object", + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "additionalProperties": false + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "additionalProperties": { + "type": "number" + } + } + ], + "description": "The data to write" + } + }, + "required": [ + "data", + "path", + "step" + ] + } + ] + }, + "FileReference": { + "anyOf": [ + { + "$ref": "#/definitions/VFSReference" + }, + { + "$ref": "#/definitions/LiteralReference" + }, + { + "$ref": "#/definitions/CoreThemeReference" + }, + { + "$ref": "#/definitions/CorePluginReference" + }, + { + "$ref": "#/definitions/UrlReference" + } + ] + }, + "VFSReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "vfs", + "description": "Identifies the file resource as Virtual File System (VFS)" + }, + "path": { + "type": "string", + "description": "The path to the file in the VFS" + } + }, + "required": [ + "resource", + "path" + ], + "additionalProperties": false + }, + "LiteralReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "literal", + "description": "Identifies the file resource as a literal file" + }, + "name": { + "type": "string", + "description": "The name of the file" + }, + "contents": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "object", + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "type": "object", + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "additionalProperties": false + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "additionalProperties": { + "type": "number" + } + } + ], + "description": "The contents of the file" + } + }, + "required": [ + "resource", + "name", + "contents" + ], + "additionalProperties": false + }, + "CoreThemeReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "wordpress.org/themes", + "description": "Identifies the file resource as a WordPress Core theme" + }, + "slug": { + "type": "string", + "description": "The slug of the WordPress Core theme" + } + }, + "required": [ + "resource", + "slug" + ], + "additionalProperties": false + }, + "CorePluginReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "wordpress.org/plugins", + "description": "Identifies the file resource as a WordPress Core plugin" + }, + "slug": { + "type": "string", + "description": "The slug of the WordPress Core plugin" + } + }, + "required": [ + "resource", + "slug" + ], + "additionalProperties": false + }, + "UrlReference": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "const": "url", + "description": "Identifies the file resource as a URL" + }, + "url": { + "type": "string", + "description": "The URL of the file" + }, + "caption": { + "type": "string", + "description": "Optional caption for displaying a progress message" + } + }, + "required": [ + "resource", + "url" + ], + "additionalProperties": false + }, + "InstallPluginOptions": { + "type": "object", + "properties": { + "activate": { + "type": "boolean", + "description": "Whether to activate the plugin after installing it." + } + }, + "additionalProperties": false + }, + "PHPRequest": { + "type": "object", + "properties": { + "method": { + "$ref": "#/definitions/HTTPMethod", + "description": "Request method. Default: `GET`." + }, + "url": { + "type": "string", + "description": "Request path or absolute URL." + }, + "headers": { + "$ref": "#/definitions/PHPRequestHeaders", + "description": "Request headers." + }, + "files": { + "type": "object", + "additionalProperties": { + "type": "object", + "properties": { + "size": { + "type": "number" + }, + "type": { + "type": "string" + }, + "lastModified": { + "type": "number" + }, + "name": { + "type": "string" + }, + "webkitRelativePath": { + "type": "string" + } + }, + "required": [ + "lastModified", + "name", + "size", + "type", + "webkitRelativePath" + ], + "additionalProperties": false + }, + "description": "Uploaded files" + }, + "body": { + "type": "string", + "description": "Request body without the files." + }, + "formData": { + "type": "object", + "additionalProperties": {}, + "description": "Form data. If set, the request body will be ignored and the content-type header will be set to `application/x-www-form-urlencoded`." + } + }, + "required": [ + "url" + ], + "additionalProperties": false + }, + "HTTPMethod": { + "type": "string", + "enum": [ + "GET", + "POST", + "HEAD", + "OPTIONS", + "PATCH", + "PUT", + "DELETE" + ] + }, + "PHPRequestHeaders": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "PHPRunOptions": { + "type": "object", + "properties": { + "relativeUri": { + "type": "string", + "description": "Request path following the domain:port part." + }, + "scriptPath": { + "type": "string", + "description": "Path of the .php file to execute." + }, + "protocol": { + "type": "string", + "description": "Request protocol." + }, + "method": { + "$ref": "#/definitions/HTTPMethod", + "description": "Request method. Default: `GET`." + }, + "headers": { + "$ref": "#/definitions/PHPRequestHeaders", + "description": "Request headers." + }, + "body": { + "type": "string", + "description": "Request body without the files." + }, + "fileInfos": { + "type": "array", + "items": { + "$ref": "#/definitions/FileInfo" + }, + "description": "Uploaded files." + }, + "code": { + "type": "string", + "description": "The code snippet to eval instead of a php file." + } + }, + "additionalProperties": false + }, + "FileInfo": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "BYTES_PER_ELEMENT": { + "type": "number" + }, + "buffer": { + "type": "object", + "properties": { + "byteLength": { + "type": "number" + } + }, + "required": [ + "byteLength" + ], + "additionalProperties": false + }, + "byteLength": { + "type": "number" + }, + "byteOffset": { + "type": "number" + }, + "length": { + "type": "number" + } + }, + "required": [ + "BYTES_PER_ELEMENT", + "buffer", + "byteLength", + "byteOffset", + "length" + ], + "additionalProperties": { + "type": "number" + } + } + }, + "required": [ + "key", + "name", + "type", + "data" + ], + "additionalProperties": false + }, + "WordPressInstallationOptions": { + "type": "object", + "properties": { + "adminUsername": { + "type": "string" + }, + "adminPassword": { + "type": "string" + } + }, + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/packages/playground/blueprints/src/lib/steps/common.ts b/packages/playground/blueprints/src/lib/steps/common.ts index 0dae687f47..4e9b4d2b3c 100644 --- a/packages/playground/blueprints/src/lib/steps/common.ts +++ b/packages/playground/blueprints/src/lib/steps/common.ts @@ -25,6 +25,10 @@ export async function fileToUint8Array(file: File) { return new Uint8Array(await file.arrayBuffer()); } +export async function fileToString(file: File) { + return new TextDecoder("utf-8").decode(new Uint8Array(await file.arrayBuffer())); +} + /** * Polyfill the File class in JSDOM which lacks arrayBuffer() method * diff --git a/packages/playground/blueprints/src/lib/steps/handlers.ts b/packages/playground/blueprints/src/lib/steps/handlers.ts index c410bc0a64..fe493d5466 100644 --- a/packages/playground/blueprints/src/lib/steps/handlers.ts +++ b/packages/playground/blueprints/src/lib/steps/handlers.ts @@ -3,6 +3,7 @@ export { activateTheme } from './activate-theme'; export { applyWordPressPatches } from './apply-wordpress-patches'; export { runPHP } from './run-php'; export { runPHPWithOptions } from './run-php-with-options'; +export { runSqlFile } from './run-sql-file'; export { setPhpIniEntry } from './set-php-ini-entry'; export { request } from './request'; export { cp } from './cp'; diff --git a/packages/playground/blueprints/src/lib/steps/index.ts b/packages/playground/blueprints/src/lib/steps/index.ts index 79210cf82c..2e42a21a68 100644 --- a/packages/playground/blueprints/src/lib/steps/index.ts +++ b/packages/playground/blueprints/src/lib/steps/index.ts @@ -16,6 +16,7 @@ import { SetSiteOptionsStep, UpdateUserMetaStep } from './site-data'; import { RmStep } from './rm'; import { CpStep } from './cp'; import { RmdirStep } from './rmdir'; +import { RunSqlFileStep } from './run-sql-file'; import { MkdirStep } from './mkdir'; import { MvStep } from './mv'; import { SetPhpIniEntryStep } from './set-php-ini-entry'; @@ -58,6 +59,7 @@ export type GenericStep = | RunPHPStep | RunPHPWithOptionsStep | RunWpInstallationWizardStep + | RunSqlFileStep | SetPhpIniEntryStep | SetSiteOptionsStep | UnzipStep @@ -86,6 +88,7 @@ export type { RunPHPStep, RunPHPWithOptionsStep, RunWpInstallationWizardStep, + RunSqlFileStep, WordPressInstallationOptions, SetPhpIniEntryStep, SetSiteOptionsStep, diff --git a/packages/playground/blueprints/src/lib/steps/run-sql-file.ts b/packages/playground/blueprints/src/lib/steps/run-sql-file.ts new file mode 100644 index 0000000000..a06d7b8a86 --- /dev/null +++ b/packages/playground/blueprints/src/lib/steps/run-sql-file.ts @@ -0,0 +1,47 @@ +import { StepHandler } from '.'; + +/** + * @inheritDoc runSqlFile + * @hasRunnableExample + * @example + * + * + * { + * "step": "runSqlFile", + * "path": "wordpress.org/plugins" + * } + * + */ +export interface RunSqlFileStep { + /** + * The step identifier. + */ + step: 'runSqlFile'; + /** + * The plugin zip file to install. + */ + path: string; +} + +/** + * Installs a WordPress plugin in the Playground. + * + * @param playground The playground client. + * @param pluginZipFile The plugin zip file. + * @param options Optional. Set `activate` to false if you don't want to activate the plugin. + */ +export const runSqlFile: StepHandler = async ( + playground, + { path }, + progress? +) => { + progress?.tracker.setCaption(`Executing SQL`); + + const runPhp = await playground.run({code:` Date: Wed, 22 Nov 2023 13:35:48 -0500 Subject: [PATCH 2/5] @sejas' original builder. --- .../playground/website/builder/index.html | 37 ++++++++ packages/playground/website/builder/index.js | 84 +++++++++++++++++++ packages/playground/website/builder/style.css | 69 +++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 packages/playground/website/builder/index.html create mode 100644 packages/playground/website/builder/index.js create mode 100644 packages/playground/website/builder/style.css diff --git a/packages/playground/website/builder/index.html b/packages/playground/website/builder/index.html new file mode 100644 index 0000000000..d7b1f2cb95 --- /dev/null +++ b/packages/playground/website/builder/index.html @@ -0,0 +1,37 @@ + + + + + + WordPress Playground Blueprint Builder + + + + + + + + +
+
+

+      
+
+ +
+
+ +
+
+ + diff --git a/packages/playground/website/builder/index.js b/packages/playground/website/builder/index.js new file mode 100644 index 0000000000..087331dc87 --- /dev/null +++ b/packages/playground/website/builder/index.js @@ -0,0 +1,84 @@ +function init() { + const parser = + really_relaxed_json.tv.twelvetone.rjson.RJsonParserFactory.Companion.getDefault().createParser(); + const iframeSrc = "https://playground.wordpress.net/?mode=seamless"; + const iframe = document.querySelector("iframe"); + const textarea = document.querySelector("#jsontext"); + const button = document.querySelector("button"); + const urlPreviewLink = document.querySelector("#url-preview a"); + + window.test = { + iframeSrc, + iframe, + textarea, + button, + }; + + function formatJson(el, jsonObject = {}) { + el.innerHTML = prettyPrintJson.toHtml(jsonObject, { + quoteKeys: true, + trailingComma: false, + }); + } + + function appendBlueprint(opts = {}) { + const { fromButton = false } = opts; + try { + let blueprintString = parser.stringToJson( + textarea.innerText.replace(/\n/g, "").replace(/\\t/g, "") + ); + const blueprintJsonObject = JSON.parse(blueprintString); + blueprintString = JSON.stringify(blueprintJsonObject); + console.log("blueprintString", blueprintString); + + if (fromButton) { + formatJson(textarea, blueprintJsonObject); + } + + iframe.src = `${iframeSrc}?time=${new Date().getTime()}#${blueprintString}`; + history.pushState(null, null, `#${blueprintString}`); + const playgroundURL = iframe.src.replace(/\?mode=.*\#/, "#"); + urlPreviewLink.href = playgroundURL; + urlPreviewLink.innerText = playgroundURL; + } catch (error) { + console.error(error); + } + } + + textarea.addEventListener("input", appendBlueprint); + button.addEventListener("click", () => appendBlueprint({ fromButton: true })); + + let defaultBlueprint = { + landingPage: "/wp-admin/", + preferredVersions: { + php: "7.4", + wp: "5.9", + }, + steps: [ + { + step: "login", + username: "admin", + password: "password", + }, + ], + }; + if (window.location.hash) { + const hash = decodeURI(window.location.hash.replace(/^#/, "")); + try { + const hashJson = JSON.parse(hash); + defaultBlueprint = hashJson; + } catch (error) { + console.error(error); + } + } + + formatJson(textarea, defaultBlueprint, { + quoteKeys: true, + trailingComma: false, + }); + + appendBlueprint(); +} + +// on ready run init() +document.addEventListener("DOMContentLoaded", init); diff --git a/packages/playground/website/builder/style.css b/packages/playground/website/builder/style.css new file mode 100644 index 0000000000..025bf8939c --- /dev/null +++ b/packages/playground/website/builder/style.css @@ -0,0 +1,69 @@ +main { + display: flex; + flex-direction: row; + justify-content: stretch; + align-items: center; + height: 100vh; + width: 100vw; + } + .column { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 100%; + justify-content: stretch; + align-items: stretch; + flex: 2; + max-width: 50%; + } + .column.editor { + flex: 1; + } + .separator { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 60px; + } + iframe { + border: none; + width: 100%; + height: 100%; + } + button { + width: 100%; + height: 100%; + font-size: 24px; + font-weight: bold; + background: none; + border: none; + cursor: pointer; + } + textarea, + .jsontext { + width: 100%; + height: 100%; + } + .json-container { + word-break: break-all; + white-space: break-spaces; + padding-left: 30px; + } + #url-preview { + background: #1e2327; + white-space: nowrap; + padding: 13px; + overflow: hidden; + border-radius: 20px; + margin: 0 8px; + max-width: 100%; + font-family: arial; + text-overflow: ellipsis; + } + #url-preview a { + color: white; + } \ No newline at end of file From 6f09130829cdbf3bd5b6be6ed106bfffddf35443 Mon Sep 17 00:00:00 2001 From: Sean Morris <640101+seanmorris@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:40:47 -0500 Subject: [PATCH 3/5] @artpi's changes --- .../playground/website/builder/download.svg | 1 + packages/playground/website/builder/edit.svg | 1 + .../playground/website/builder/index.html | 76 +++-- packages/playground/website/builder/index.js | 230 ++++++++++----- .../website/builder/new-tab-button.svg | 6 + packages/playground/website/builder/open.svg | 12 + .../website/builder/play-button.svg | 6 + packages/playground/website/builder/save.svg | 10 + packages/playground/website/builder/style.css | 276 +++++++++++++----- 9 files changed, 460 insertions(+), 158 deletions(-) create mode 100644 packages/playground/website/builder/download.svg create mode 100644 packages/playground/website/builder/edit.svg create mode 100644 packages/playground/website/builder/new-tab-button.svg create mode 100644 packages/playground/website/builder/open.svg create mode 100644 packages/playground/website/builder/play-button.svg create mode 100644 packages/playground/website/builder/save.svg diff --git a/packages/playground/website/builder/download.svg b/packages/playground/website/builder/download.svg new file mode 100644 index 0000000000..6f71b2b0e5 --- /dev/null +++ b/packages/playground/website/builder/download.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/playground/website/builder/edit.svg b/packages/playground/website/builder/edit.svg new file mode 100644 index 0000000000..3d563c709a --- /dev/null +++ b/packages/playground/website/builder/edit.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/playground/website/builder/index.html b/packages/playground/website/builder/index.html index d7b1f2cb95..ab913a7747 100644 --- a/packages/playground/website/builder/index.html +++ b/packages/playground/website/builder/index.html @@ -5,33 +5,61 @@ WordPress Playground Blueprint Builder - - - + + + -
- https://playground.wordpress.net/ +
+
+
+
+
+ ⌘ ctrl + space: show autocomplete + + ⌘ ctrl + enter: + +
+

Describe your website

+ +
+
+

Blueprint Schema

+

+            

+          
+
+
+
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + +
+ +
+
+
-
-
-

-      
-
- -
-
- -
-
diff --git a/packages/playground/website/builder/index.js b/packages/playground/website/builder/index.js index 087331dc87..0c47c16c7c 100644 --- a/packages/playground/website/builder/index.js +++ b/packages/playground/website/builder/index.js @@ -1,11 +1,118 @@ -function init() { - const parser = - really_relaxed_json.tv.twelvetone.rjson.RJsonParserFactory.Companion.getDefault().createParser(); - const iframeSrc = "https://playground.wordpress.net/?mode=seamless"; +//*/ // Prod +const importStartPlaygroundWeb = import('https://unpkg.com/@wp-playground/client/index.js'); +const fetchBlueprintSchema = fetch('https://unpkg.com/@wp-playground/blueprints/blueprint-schema.json').then(r=>r.json()); +/*/ // Dev +const importStartPlaygroundWeb = import('http://localhost:8080/client/index.js'); +const fetchBlueprintSchema = fetch('http://localhost:8080/blueprints/blueprint-schema.json').then(r=>r.json()); +//*/ + + + +let debounce = null; +let starting = null; + + + +let errorTag; +const showError = (error) => { + console.error(error); + if(!errorTag) errorTag = document.getElementById('error-output'); + errorTag.innerText = String(error); +} +const clearError = (error) => { + if(!errorTag) errorTag = document.getElementById('error-output'); + errorTag.innerText = ''; +} + +const formatJson = ( jsonObject = {}) => { + //const existing = editor.getSession().getValue(); + const formatted = JSON.stringify(jsonObject, null, 2) + "\n"; + // if(formatted !== existing) { + // editor.getSession().setValue(formatted) + // } + document.getElementById('jsontext').innerText = formatted; +}; + +function getCurrentBlueprint() { + const blueprint = JSON.parse( document.getElementById('jsontext').innerText.replace(/\n/g, '') ); + if ( blueprint.features && blueprint.features.networking === false ) { + blueprint.features.networking = true; + } + return blueprint; +} + +const fetchBluePrintFromAI = async () => { + const description = document.getElementById('prompt').value; + const blueprint = getCurrentBlueprint(); + document.body.setAttribute('data-starting', true); + document.getElementById( 'prompt' ).setAttribute( 'disabled', true ); + + console.log( 'Calling AI', description, blueprint ); + const response = await fetch('https://public-api.wordpress.com/wpcom/v2/playground/ai/blueprint', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + description, + blueprint, + }), + }); + const json = await response.json(); + document.getElementById( 'prompt' ).setAttribute( 'disabled', false ); + document.getElementById( 'prompt' ).innerText = ''; + console.log( 'Returned AI blueprint', json ); + return json; +}; + +const runBlueprint = async (editor) => { + if (starting) { + return; + } + document.body.setAttribute('data-starting', true); + try { + clearError(); + window.location.hash = JSON.stringify( getCurrentBlueprint() ); + const blueprintCopy = JSON.parse( JSON.stringify( getCurrentBlueprint() ) ); + delete blueprintCopy.features; // I am getting error otherwise + + //window.location.hash = JSON.stringify(JSON.parse(editor.getValue())); + // const blueprintJsonObject = JSON.parse(editor.getValue()); + // formatJson(editor, blueprintJsonObject); + const startPlaygroundWeb = (await importStartPlaygroundWeb).startPlaygroundWeb; + starting = startPlaygroundWeb({ + iframe: document.getElementById('wp-playground'), + remoteUrl: `https://playground.wordpress.net/remote.html`, + blueprint: blueprintCopy, + }); + await starting; + starting = null; + } + catch (error) { + showError(error); + } + finally { + document.body.setAttribute('data-starting', false); + + } + +}; + +const loadFromHash = () => { + const hash = decodeURI(window.location.hash.substr(1)); + try { + formatJson(JSON.parse(hash)); + } catch (error) { + console.error(error); + } +}; + +document.addEventListener("DOMContentLoaded", () => { + const iframeSrc = "https://playground.wordpress.net/"; const iframe = document.querySelector("iframe"); const textarea = document.querySelector("#jsontext"); - const button = document.querySelector("button"); - const urlPreviewLink = document.querySelector("#url-preview a"); + const button = document.querySelector("button#run"); + const newTab = document.querySelector("button#new-tab"); window.test = { iframeSrc, @@ -14,71 +121,62 @@ function init() { button, }; - function formatJson(el, jsonObject = {}) { - el.innerHTML = prettyPrintJson.toHtml(jsonObject, { - quoteKeys: true, - trailingComma: false, - }); - } - - function appendBlueprint(opts = {}) { - const { fromButton = false } = opts; + button.addEventListener('click', () => { try { - let blueprintString = parser.stringToJson( - textarea.innerText.replace(/\n/g, "").replace(/\\t/g, "") - ); - const blueprintJsonObject = JSON.parse(blueprintString); - blueprintString = JSON.stringify(blueprintJsonObject); - console.log("blueprintString", blueprintString); - - if (fromButton) { - formatJson(textarea, blueprintJsonObject); - } - - iframe.src = `${iframeSrc}?time=${new Date().getTime()}#${blueprintString}`; - history.pushState(null, null, `#${blueprintString}`); - const playgroundURL = iframe.src.replace(/\?mode=.*\#/, "#"); - urlPreviewLink.href = playgroundURL; - urlPreviewLink.innerText = playgroundURL; - } catch (error) { - console.error(error); + clearError(); + fetchBluePrintFromAI().then( blueprint => { + formatJson(blueprint); + runBlueprint(); + } ); } - } + catch (error) { + showError(error); + } + }); - textarea.addEventListener("input", appendBlueprint); - button.addEventListener("click", () => appendBlueprint({ fromButton: true })); + let prevWin; - let defaultBlueprint = { - landingPage: "/wp-admin/", - preferredVersions: { - php: "7.4", - wp: "5.9", - }, - steps: [ - { - step: "login", - username: "admin", - password: "password", - }, - ], - }; - if (window.location.hash) { - const hash = decodeURI(window.location.hash.replace(/^#/, "")); - try { - const hashJson = JSON.parse(hash); - defaultBlueprint = hashJson; - } catch (error) { - console.error(error); - } - } + newTab.addEventListener('click', () => { + runBlueprint(); + const query = new URLSearchParams(); - formatJson(textarea, defaultBlueprint, { - quoteKeys: true, - trailingComma: false, + query.set('mode', 'seamless'); + // query.set('php', blueprint?.preferredVersions?.php); + // query.set('wp', blueprint?.preferredVersions?.wp); + const url = `https://playground.wordpress.net/?${query}#` + JSON.stringify(getCurrentBlueprint()); + if (prevWin) { + prevWin.close(); + } + prevWin = window.open(url, '_blank'); }); - appendBlueprint(); -} + if (window.location.hash) { + loadFromHash(); + } + else { + formatJson( { + landingPage: "/wp-admin/", + phpExtensionBundles: [ + "kitchen-sink" + ], + preferredVersions: { + php: "7.4", + wp: "5.9", + }, + steps: [ + { + step: "login", + username: "admin", + password: "password", + }, + ], + }); + } + + runBlueprint(); -// on ready run init() -document.addEventListener("DOMContentLoaded", init); + window.addEventListener('hashchange', () => { + loadFromHash(); + runBlueprint(); + }); +}); diff --git a/packages/playground/website/builder/new-tab-button.svg b/packages/playground/website/builder/new-tab-button.svg new file mode 100644 index 0000000000..645a41020b --- /dev/null +++ b/packages/playground/website/builder/new-tab-button.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/playground/website/builder/open.svg b/packages/playground/website/builder/open.svg new file mode 100644 index 0000000000..eae4d9dfbc --- /dev/null +++ b/packages/playground/website/builder/open.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/packages/playground/website/builder/play-button.svg b/packages/playground/website/builder/play-button.svg new file mode 100644 index 0000000000..9e7577819a --- /dev/null +++ b/packages/playground/website/builder/play-button.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/playground/website/builder/save.svg b/packages/playground/website/builder/save.svg new file mode 100644 index 0000000000..b68bd97293 --- /dev/null +++ b/packages/playground/website/builder/save.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/playground/website/builder/style.css b/packages/playground/website/builder/style.css index 025bf8939c..ee8ebcd6ea 100644 --- a/packages/playground/website/builder/style.css +++ b/packages/playground/website/builder/style.css @@ -1,69 +1,209 @@ +html { + background-color: #aaa; + font-family: sans-serif; +} + +html, body, .playground-wrapper { + height: 100%; +} + +.playground-wrapper { + display: flex; + flex-direction: column; + padding: 10px; + box-sizing: border-box; + background-color: #1e2327; + box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.75); +} + +body { + padding: 1em; + padding: 0; + box-sizing: border-box; + margin: 0; +} + main { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: center; - height: 100vh; - width: 100vw; - } - .column { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100%; - width: 100%; - justify-content: stretch; - align-items: stretch; - flex: 2; - max-width: 50%; - } - .column.editor { - flex: 1; - } - .separator { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100%; - width: 60px; - } - iframe { - border: none; - width: 100%; - height: 100%; - } - button { - width: 100%; - height: 100%; - font-size: 24px; - font-weight: bold; - background: none; - border: none; - cursor: pointer; - } - textarea, - .jsontext { - width: 100%; - height: 100%; - } - .json-container { - word-break: break-all; - white-space: break-spaces; - padding-left: 30px; - } - #url-preview { - background: #1e2327; - white-space: nowrap; - padding: 13px; - overflow: hidden; - border-radius: 20px; - margin: 0 8px; - max-width: 100%; - font-family: arial; - text-overflow: ellipsis; - } - #url-preview a { - color: white; - } \ No newline at end of file + display: flex; + flex-direction: row; + justify-content: stretch; + align-items: center; + flex: 1; +} + +.column { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 100%; + justify-content: stretch; + align-items: stretch; + flex: 2; + max-width: 50%; + background-color: #24292e; + border: 1px solid #40464d;; + box-sizing: border-box; + position: relative; +} + +.column.editor { + flex: 1; +} + +.separator { + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 10px; +} + +.frame-wrapper { + position: relative; + width: 100%; + height: 100%; +} + +.frame-center { + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.frame-center > svg { + filter: brightness(0.333) contrast(2) brightness(3); + mix-blend-mode: lighten; +} + +iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + box-sizing: border-box; + border: 0; +} + +button { + font-weight: bold; + background: none; + border: none; + cursor: pointer; + background-color: #ccc; + padding: 8px; + margin: 7px; + box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.25); +} + +button:active { + position: relative; + top: 1px; + left: 1px; + box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.125); + filter: brightness(0.94); +} + +button:hover:not(:active) { + filter: brightness(1.06); +} + +button.cta { + background-color: #eaaa00; +} + +button.run { + background-image: url('./play-button.svg'); + background-size: auto 55%; + background-position: 0.325em center; + background-repeat: no-repeat; + padding-left: 1.85em; +} + +button#new-tab { + background-image: url('./new-tab-button.svg'); + background-size: auto 55%; + background-position: calc(100% + -0.5em) center; + background-repeat: no-repeat; + padding-right: 2.1em; +} + +.jsontext, #error-output { + width: 100%; + height: 100%; + transition: height 0.25s ease-out, padding 0.25s ease-out, background-color 0.25s ease-in; +} + +#error-output { + background-color: rgba(0, 0, 0, 0.25); + margin: 0; + padding: 1rem; + box-sizing: border-box; + color: #F00; + white-space: pre-wrap; +} + +#error-output:empty { + background-color: rgba(255, 255, 255, 0.75); + height: 0%; + padding: 0rem; +} + +.json-container { + word-break: break-all; + white-space: break-spaces; + padding-left: 30px; + height: 100%; +} + +.toolbar { + background-color: #40464d; + text-align: right; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; +} + +.toolbar span { + color: white; + font-size: 0.8rem; + font-style: italic; + white-space: pre; + margin-left: 1rem; +} + +.toolbar span.spacer { + flex: 1; + display: inline-block; +} + +pre { + margin-bottom: 0; + margin-top: 10px; +} + +button { + transition: opacity 0.5s ease-in-out; +} + +[data-starting=true] button.run { + pointer-events: none; + filter: saturate(0.25); + opacity: 0.5; +} + +[data-starting=true] button.run { + animation: button-pulse infinite alternate 0.5s; +} + +@keyframes button-pulse { + from { opacity: 1; } + to { opacity: 0.5; } +} \ No newline at end of file From edfce3c252e95e167d7dccc481a9d7d8fbd797c0 Mon Sep 17 00:00:00 2001 From: Sean Morris <640101+seanmorris@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:41:06 -0500 Subject: [PATCH 4/5] My changes. --- .../playground/website/builder/index.html | 79 +- packages/playground/website/builder/index.js | 739 ++++++++++++++++-- .../website/builder/new-tab-button.svg | 10 +- packages/playground/website/builder/style.css | 210 ++++- 4 files changed, 944 insertions(+), 94 deletions(-) diff --git a/packages/playground/website/builder/index.html b/packages/playground/website/builder/index.html index ab913a7747..7706f15a1c 100644 --- a/packages/playground/website/builder/index.html +++ b/packages/playground/website/builder/index.html @@ -4,55 +4,74 @@ WordPress Playground Blueprint Builder + + + + - - -
-
-
+
+
+ + + + + + + + + + + + + + ⌘ ctrl + space: show autocomplete + + ⌘ ctrl + enter: + + + + +
+
+
- ⌘ ctrl + space: show autocomplete + - ⌘ ctrl + enter: - +
-

Describe your website

- -
-
-

Blueprint Schema

-

-            

+          
+          

+          
+
+ + + +
- - + + - - - - - - - - - - - - - + + + + + + + +
diff --git a/packages/playground/website/builder/index.js b/packages/playground/website/builder/index.js index 0c47c16c7c..50909dda1e 100644 --- a/packages/playground/website/builder/index.js +++ b/packages/playground/website/builder/index.js @@ -6,102 +6,492 @@ const importStartPlaygroundWeb = import('http://localhost:8080/client/index.js') const fetchBlueprintSchema = fetch('http://localhost:8080/blueprints/blueprint-schema.json').then(r=>r.json()); //*/ +const FALLBACK_TIMEOUT = 30*1000; +const deref = (obj, root) => { + if (!obj || typeof obj !== 'object' || !('$ref' in obj)) { + return obj; + } + + const path = obj['$ref'].substr(2).split('/'); + let node = root; + + for (const p of path) { + if (!(p in node)) { + throw new Error(`Invalid reference: "${obj['$ref']}"`); + } + node = node[p]; + } + + return {...obj, ...node}; +}; + +const reader = Symbol('reader'); + +const getSchemaReader = (schema, root = null) => { + + if (schema[reader]) { + return schema[reader]; + } + + if (!root) { + root = schema; + } + + const proxy = new Proxy(schema, {get: (target, key, receiver) => { + const val = Reflect.get(target, key, receiver); + if (val && typeof val === 'object') { + return getSchemaReader(deref(val, root), root); + } + return val; + }}); + + schema[reader] = proxy; + + return proxy; +}; + +const getPrevKeys = (editor, {column, row}) => { + const content = editor.getValue(); + const lines = content.split("\n"); + const line = String(lines[row]); + const colon = line.indexOf(':'); + + const path = []; + + if (colon > -1 && column > colon) { + const openQuote = line.indexOf('"'); + const closeQuote = line.indexOf('"', 1 + openQuote); + path.push(line.substring(1 + openQuote, closeQuote)); + } + + let indent = 0; + + while (line[indent] == ' ' || line[indent] == "\t") { + indent++; + } + + let checkRow = -1 + row; + + while(checkRow >= 0) { + const openQuote = lines[checkRow].indexOf('"'); + const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); + if(openQuote > -1 && openQuote < indent) { + path.push(lines[checkRow].substring(1 + openQuote, closeQuote)); + indent = openQuote; + } + checkRow--; + } + + return path; +}; + +const getLastOfType = (editor, type, {column, row}, skip = 0) => { + const content = editor.getValue(); + const lines = content.split("\n"); + + let checkRow = -1 + row; + + while(checkRow >= 0) { + const openBracket = lines[checkRow].indexOf('{'); + + if(openBracket > -1) { + if(--skip < 0) { + return null; + } + checkRow--; + continue; + } + + let indent = 0; + + while (lines[checkRow][indent] == ' ' || lines[checkRow][indent] == "\t") { + indent++; + } + + const openQuote = lines[checkRow].indexOf('"'); + const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); + const openVQuote = lines[checkRow].indexOf('"', 1 + closeQuote); + const closeVQuote = lines[checkRow].indexOf('"', 1 + openVQuote); + + if(openQuote > -1 && openQuote == indent) { + const checkType = lines[checkRow].substring(1 + openQuote, closeQuote); + if (type === checkType) { + return lines[checkRow].substring(1 + openVQuote, closeVQuote); + } + } + + checkRow--; + } + + return null; +}; + +const getPrevSiblings = (editor, {column, row}) => { + const content = editor.getValue(); + const lines = content.split("\n"); + + let checkRow = -1 + row; + let indent = 0; + + while (lines[row][indent] == ' ' || lines[row][indent] == "\t") { + indent++; + } + + const siblings = []; + + while(checkRow >= 0) { + const openBracket = lines[checkRow].indexOf('{'); + + if(openBracket > -1 && openBracket < indent) { + break; + } + + const openQuote = lines[checkRow].indexOf('"'); + const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); + + if(openQuote > -1 && openQuote == indent) { + siblings.push(lines[checkRow].substring(1 + openQuote, closeQuote)) + } + + checkRow--; + } + + return siblings; +}; + +const getStepProperties = async (stepType) => { + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter(s => s.properties.step['const'] === stepType) + .map(s => s.properties) + .flat() + .pop(); +} + +const completeStepProperty = async (stepType, prefix) => { + const schema = await fetchBlueprintSchema; + return schema.definitions.StepDefinition.oneOf + .filter(s => s.properties.step['const'] === stepType) + .map(s => Object.keys(s.properties)) + .flat() + .filter(s => s.substr(0, prefix.length) === prefix) + .filter(s => !['step', 'progress'].includes(s)); +}; + +const getStepSubProperties = async(stepType, resType, property) => { + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter(s => s.properties.step['const'] === stepType) + .map(s => { + return s.properties[property].anyOf; + }) + .flat() + .filter(s => !resType || s.properties.resource.const === resType) + .map(s => s.properties) + .flat() + .pop(); +} + +const completeStepSubProperty = async (stepType, resType, property, subKey, prefix) => { + if(!resType && !subKey) { + return ['resource']; + } + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter(s => s.properties.step['const'] === stepType) + .map(s => { + return s.properties[property].anyOf; + }) + .flat() + .filter(s => !resType || s.properties.resource.const === resType) + .map(s => { + if(subKey === null) { + return Object.keys(s.properties); + } + return s.properties.resource.const; + }) + .flat() + .filter(s => !['resource'].includes(s)); +} + +const completeStep = async(prefix) => { + const schema = await fetchBlueprintSchema; + return schema.definitions.StepDefinition.oneOf + .map(s => s.properties.step['const']) + .filter(s => s.substr(0, prefix.length) === prefix); +}; + +const completePhpVersion = async (prefix) => { + const schema = await fetchBlueprintSchema; + return schema.definitions.SupportedPHPVersion.enum + .filter(s => s.substr(0, prefix.length) === prefix); +}; + +const completeRootKey = async (prefix) => { + const schema = await fetchBlueprintSchema; + return Object.keys(schema.definitions.Blueprint.properties) + .filter(s => s[0] !== '$' && s.substr(0, prefix.length) === prefix); +}; + +const completeFeature = async (prefix) => { + const schema = await fetchBlueprintSchema; + return Object.keys(schema.definitions.Blueprint.properties.features.properties) + .filter(s => s[0] !== '$' && s.substr(0, prefix.length) === prefix); +}; let debounce = null; let starting = null; +const getCompletions = async (editor, session, pos, prefix, callback) => { + const list = []; + const prevKey = getPrevKeys(editor, pos); + + const content = editor.getValue(); + const lines = content.split("\n"); + const line = String(lines[pos.row]); + const colon = line.indexOf(':'); + + const {row,column} = pos; + + const qA = (!lines[row][-1 + column] || lines[row][-1 + column] === ' ') ? '"' : ''; + const qB = (!lines[row][column] || lines[row][column] === ' ') ? '"' : ''; + + if(prevKey.length === 3 && prefix.length >= 3 && prevKey.join('<') === 'slug { + try { + const res = await fetch(`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`); + const json = await res.json(); + json?.plugins.map(p => { + var doc = new DOMParser().parseFromString(p.name, "text/html"); + const meta = doc.documentElement.textContent; + callback(null, [{name: p.slug, value: qA + p.slug + qB, score: 1, meta}]); + }); + } + finally { + document.body.setAttribute('data-loading', false); + } + }, 250); + } + + if(prevKey.length === 3 && prefix.length >= 3 && prevKey.join('<') === 'slug { + try { + const res = await fetch(`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`); + const json = await res.json(); + json?.themes.map(p => { + var doc = new DOMParser().parseFromString(p.name, "text/html"); + const meta = doc.documentElement.textContent; + callback(null, [{name: p.slug, value: qA + p.slug + qB, score: 1, meta}]); + }); + } + finally { + document.body.setAttribute('data-loading', false); + } + }, 250); + } + + switch (prevKey[0]) { + case 'preferredVersions': { + const used = await getPrevSiblings(editor, pos); + list.push(...['wp', 'php'].filter(s => !used.includes(s))); + break; + } + + case 'wp': + list.push('latest'); + break; + + case 'php': + list.push(...await completePhpVersion(prefix)); + break; + + case 'steps': { + const used = await getPrevSiblings(editor, pos); + const stepType = getLastOfType(editor, 'step', pos); + if(stepType) { + const suggestions = await completeStepProperty(stepType, prefix) + list.push(...(suggestions).filter(s => !used.includes(s))); + } + else { + list.push('step'); + } + } + break; + + case 'step': + list.push(...await completeStep(prefix)); + break; + + case 'features': + list.push(...await completeFeature(prefix)); + break; + + case undefined: + list.push(...await completeRootKey(prefix)); + break; + + default: + switch (prevKey[-1 + prevKey.length]) { + case 'steps': { + const stepType = getLastOfType(editor, 'step', pos, 1); + const resType = getLastOfType(editor, 'resource', pos, 1); + if (prevKey.length === 2) { + if (colon === -1) { + const used = await getPrevSiblings(editor, pos); + const suggestions = await completeStepSubProperty(stepType, resType, prevKey[-2 + prevKey.length], null, prefix); + list.push(...suggestions.filter(s => !used.includes(s))); + } + } + else if (prevKey.length === 3 && prevKey[0] === 'resource') { + list.push(...await completeStepSubProperty(stepType, resType, prevKey[-2 + prevKey.length], prevKey[0], prefix)); + } + } + break; + } + + break; + } + + for (const fill of list) { + callback(null, [{name: fill, value: qA + fill + qB, score: 1, meta: "Blueprint Schema"}]); + } +}; let errorTag; const showError = (error) => { console.error(error); if(!errorTag) errorTag = document.getElementById('error-output'); - errorTag.innerText = String(error); + const errDoc = `${error}`; + errorTag.setAttribute('srcdoc', errDoc); } const clearError = (error) => { if(!errorTag) errorTag = document.getElementById('error-output'); - errorTag.innerText = ''; + errorTag.setAttribute('srcdoc', ''); } -const formatJson = ( jsonObject = {}) => { - //const existing = editor.getSession().getValue(); +const formatJson = (editor, jsonObject = {} ) => { + const existing = editor.getSession().getValue(); const formatted = JSON.stringify(jsonObject, null, 2) + "\n"; - // if(formatted !== existing) { - // editor.getSession().setValue(formatted) - // } - document.getElementById('jsontext').innerText = formatted; + if(formatted !== existing) { + editor.getSession().setValue(formatted); + if(formatted !== existing) { + editor.getSession().setValue(formatted) + } + } + // document.getElementById('jsontext').innerText = formatted; }; -function getCurrentBlueprint() { - const blueprint = JSON.parse( document.getElementById('jsontext').innerText.replace(/\n/g, '') ); +function getCurrentBlueprint(editor) { + const blueprint = JSON.parse(editor.getValue()); if ( blueprint.features && blueprint.features.networking === false ) { blueprint.features.networking = true; } return blueprint; } -const fetchBluePrintFromAI = async () => { - const description = document.getElementById('prompt').value; - const blueprint = getCurrentBlueprint(); +const fetchBluePrintFromAI = async (editor) => { + const prompt = document.getElementById('prompt').value; + const blueprint = getCurrentBlueprint(editor); document.body.setAttribute('data-starting', true); - document.getElementById( 'prompt' ).setAttribute( 'disabled', true ); + document.getElementById( 'prompt' ).setAttribute('disabled', true); - console.log( 'Calling AI', description, blueprint ); + console.log( 'Calling AI', prompt, blueprint ); const response = await fetch('https://public-api.wordpress.com/wpcom/v2/playground/ai/blueprint', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - description, - blueprint, - }), + body: JSON.stringify({prompt,blueprint,}), }); const json = await response.json(); - document.getElementById( 'prompt' ).setAttribute( 'disabled', false ); + document.getElementById( 'prompt' ).removeAttribute('disabled'); document.getElementById( 'prompt' ).innerText = ''; console.log( 'Returned AI blueprint', json ); return json; }; const runBlueprint = async (editor) => { - if (starting) { - return; - } - document.body.setAttribute('data-starting', true); + const fallback = setTimeout(() => { + document.body.setAttribute('data-starting', false) + starting = false; + }, FALLBACK_TIMEOUT); try { + window.location.hash = JSON.stringify(JSON.parse(editor.getValue())); + if (starting) { + return; + } + starting = true; + document.body.setAttribute('data-starting', true); clearError(); - window.location.hash = JSON.stringify( getCurrentBlueprint() ); - const blueprintCopy = JSON.parse( JSON.stringify( getCurrentBlueprint() ) ); + const blueprintJsonObject = getCurrentBlueprint(editor); + window.location.hash = JSON.stringify( getCurrentBlueprint(editor) ); + formatJson(editor, blueprintJsonObject); + const blueprintCopy = JSON.parse(JSON.stringify(blueprintJsonObject)); delete blueprintCopy.features; // I am getting error otherwise - - //window.location.hash = JSON.stringify(JSON.parse(editor.getValue())); - // const blueprintJsonObject = JSON.parse(editor.getValue()); - // formatJson(editor, blueprintJsonObject); const startPlaygroundWeb = (await importStartPlaygroundWeb).startPlaygroundWeb; - starting = startPlaygroundWeb({ + await startPlaygroundWeb({ iframe: document.getElementById('wp-playground'), remoteUrl: `https://playground.wordpress.net/remote.html`, blueprint: blueprintCopy, }); - await starting; - starting = null; + document.body.setAttribute('data-starting', false); + clearTimeout(fallback); + starting = false; } catch (error) { + starting = false; + document.body.setAttribute('data-starting', false); showError(error); + clearTimeout(fallback); } finally { document.body.setAttribute('data-starting', false); - } - }; -const loadFromHash = () => { +const loadFromHash = (editor) => { const hash = decodeURI(window.location.hash.substr(1)); try { - formatJson(JSON.parse(hash)); + formatJson(editor, JSON.parse(hash)); } catch (error) { console.error(error); } @@ -109,11 +499,162 @@ const loadFromHash = () => { document.addEventListener("DOMContentLoaded", () => { const iframeSrc = "https://playground.wordpress.net/"; - const iframe = document.querySelector("iframe"); + const iframe = document.querySelector("iframe#wp-playground"); const textarea = document.querySelector("#jsontext"); const button = document.querySelector("button#run"); + const aiButton = document.querySelector("button#open-ai"); + const closeAi = document.querySelector("button#close-ai"); + const genButton = document.querySelector("button#generate"); const newTab = document.querySelector("button#new-tab"); + + let aiOpen = false; + + aiButton.addEventListener('click', event => { + document.body.setAttribute('data-show-ai', aiOpen = !aiOpen); + }); + + closeAi.addEventListener('click', event => { + document.body.setAttribute('data-show-ai', aiOpen = false); + }); + + const editor = ace.edit('jsontext'); + editor.setTheme("ace/theme/github_dark"); + editor.session.setMode("ace/mode/json"); + + const langTools = ace.require('ace/ext/language_tools'); + + langTools.setCompleters([]); + + langTools.addCompleter({triggerCharacters: ['"'],getCompletions}); + + editor.setOptions({ + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + enableSnippets: true, + useSoftTabs: true, + tabSize: 2, + }); + + editor.getSession().on('change', async event => { + if(event.action !== 'insert') { + return; + } + + const content = editor.getValue(); + const lines = content.split("\n"); + const quoteCount = (lines[event.start.row].match(/"/g) || []).length; + + if(event.start.row === event.end.row && 1 < Math.abs(event.start.column - event.end.column)) { + if(lines[event.end.row][event.end.column] === '"') { + editor.moveCursorTo(event.end.row, event.end.column + 1); + return; + } + } + + if (lines[event.end.row][event.end.column]) { + return; + } + + const indent = (lines[event.start.row].match(/^(\s+)/g) || [''])[0]; + + const inserted = event.lines.join("\n"); + const prevKey = getPrevKeys(editor, event.end); + + if (inserted.length > 1) { + return; + } + + if (inserted === ':') { + const colon = lines[event.start.row].indexOf(':'); + if (colon > -1 && colon < event.start.column) { + return; + } + + if(prevKey.length === 1 && prevKey[0] === 'landingPage') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if(prevKey.length === 1 && prevKey[0] === 'preferredVersions') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if(prevKey.length === 2 && prevKey[1] === 'preferredVersions') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + setTimeout(() => editor.execCommand('startAutocomplete'), 0); + } + + if(prevKey.length === 1 && (prevKey[0] === 'steps' || prevKey[0] === 'features')) { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' []'); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if(prevKey.length === 3 && prevKey[2] === 'steps') { + const stepType = getLastOfType(editor, 'step', event.end, 1); + const resType = getLastOfType(editor, 'resource', event.end); + const subProps = await getStepSubProperties(stepType, resType, prevKey[1]); + const subProp = subProps[prevKey[0]]; + + if (subProp?.type === 'string') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + editor.execCommand('startAutocomplete'); + } + else if (subProp?.type === 'object') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } + } + + if(prevKey.length === 2 && prevKey[0] === 'step' && prevKey[1] === 'steps') { + editor.getSession().insert({row: event.start.row, column: event.start.column + 1}, ' ""'); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + editor.execCommand('startAutocomplete'); + } + else if(prevKey.length === 2 && prevKey[1] === 'steps') { + const stepType = await getLastOfType(editor, 'step', event.end); + const properties = await getStepProperties(stepType); + const property = properties[ prevKey[0] ] ?? null; + const propType = property.type ?? null; + const propRef = property['$ref']; + + if (propType === 'string') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } + else if(propRef === '#/definitions/FileReference') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } + } + } + else if (inserted === '[') { + editor.getSession().insert({row: event.end.row, column: event.start.column + 1}, "]"); + return; + } + else if (inserted === '{') { + editor.getSession().insert({row: event.end.row, column: event.start.column + 1}, "}"); + return; + } + else if(inserted === ',') { + const nextIndent = (lines[1 + event.start.row].match(/^(\s+)/g) || [''])[0]; + if(nextIndent.length >= indent.length) { + return; + } + if (lines[event.start.row][-1 + event.start.column] !== '"') { + editor.getSession().insert({row: event.end.row, column: event.end.column}, "\n" + indent); + editor.moveCursorTo(1 + event.end.row, 1 + (indent.length)); + return; + } + editor.getSession().insert({row: event.end.row, column: event.end.column}, "\n" + indent + '""'); + editor.moveCursorTo(1 + event.end.row, 1 + (indent.length)); + editor.execCommand('startAutocomplete'); + } + }); + window.test = { iframeSrc, iframe, @@ -124,9 +665,19 @@ document.addEventListener("DOMContentLoaded", () => { button.addEventListener('click', () => { try { clearError(); - fetchBluePrintFromAI().then( blueprint => { - formatJson(blueprint); - runBlueprint(); + runBlueprint(editor); + } + catch (error) { + showError(error); + } + }); + + genButton.addEventListener('click', () => { + try { + clearError(); + fetchBluePrintFromAI(editor).then( ({blueprint}) => { + formatJson(editor, blueprint); + runBlueprint(editor); } ); } catch (error) { @@ -137,13 +688,13 @@ document.addEventListener("DOMContentLoaded", () => { let prevWin; newTab.addEventListener('click', () => { - runBlueprint(); + runBlueprint(editor); const query = new URLSearchParams(); query.set('mode', 'seamless'); // query.set('php', blueprint?.preferredVersions?.php); // query.set('wp', blueprint?.preferredVersions?.wp); - const url = `https://playground.wordpress.net/?${query}#` + JSON.stringify(getCurrentBlueprint()); + const url = `https://playground.wordpress.net/?${query}#` + JSON.stringify(getCurrentBlueprint(editor)); if (prevWin) { prevWin.close(); } @@ -151,10 +702,10 @@ document.addEventListener("DOMContentLoaded", () => { }); if (window.location.hash) { - loadFromHash(); + loadFromHash(editor); } else { - formatJson( { + formatJson(editor, { landingPage: "/wp-admin/", phpExtensionBundles: [ "kitchen-sink" @@ -173,10 +724,102 @@ document.addEventListener("DOMContentLoaded", () => { }); } - runBlueprint(); + runBlueprint(editor); + + const editorState = { + current: null + }; window.addEventListener('hashchange', () => { - loadFromHash(); - runBlueprint(); + loadFromHash(editor); + runBlueprint(editor); + }); + + editor.commands.addCommand({ + name: 'Run Blueprint', + bindKey: { + win: 'Ctrl-Enter|Ctrl-S', + mac: 'Command-Enter|Command-S', + }, + exec: editor => runBlueprint(editor), + readOnly: false + }); + + const zIn = document.querySelector("button#zoom-in"); + const zOut = document.querySelector("button#zoom-out"); + const zoomLevel = document.querySelector("span#zoom"); + if (zIn && zOut && zoomLevel) { + let zoom = 1; + + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + + zIn.addEventListener('click', () => { + if (zoom > 3) { + return; + } + zoom += 0.1; + iframe.style.setProperty('--zoom', zoom); + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + }); + + zOut.addEventListener('click', () => { + if (zoom < 0.35) { + return; + } + zoom -= 0.1; + iframe.style.setProperty('--zoom', zoom); + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + }); + } + + const save = document.querySelector("button#save"); + const open = document.querySelector("button#open"); + + const saveMethod = () => { + const dataUri = `data:application/json;base64,${btoa(JSON.stringify(JSON.parse(editor.getValue()), null, 2))}` + const link = document.createElement('a'); + link.setAttribute('href', dataUri); + link.setAttribute('download', `blueprint-${(new Date).toISOString()}.json`); + link.click(); + }; + + const openMethod = () => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.addEventListener('change', event => { + [...input.files].forEach(f => { + const reader = new FileReader(); + reader.addEventListener('load', () => { + editor.setValue(JSON.stringify(JSON.parse(reader.result), null, 2)); + editor.moveCursorTo(0, 0); + runBlueprint(editor); + }); + reader.readAsText(f); + }); + }); + input.click(); + }; + + save.addEventListener('click', saveMethod); + open.addEventListener('click', openMethod); + + editor.commands.addCommand({ + name: 'Save Blueprint', + bindKey: { + win: 'Ctrl-S', + mac: 'Command-S', + }, + exec: editor => saveMethod(), + readOnly: false + }); + + editor.commands.addCommand({ + name: 'Open Blueprint', + bindKey: { + win: 'Ctrl-O', + mac: 'Command-O', + }, + exec: editor => openMethod(), + readOnly: false }); }); diff --git a/packages/playground/website/builder/new-tab-button.svg b/packages/playground/website/builder/new-tab-button.svg index 645a41020b..0d7031a707 100644 --- a/packages/playground/website/builder/new-tab-button.svg +++ b/packages/playground/website/builder/new-tab-button.svg @@ -1,6 +1,6 @@ - - - - - + + + + + diff --git a/packages/playground/website/builder/style.css b/packages/playground/website/builder/style.css index ee8ebcd6ea..f9eba8e852 100644 --- a/packages/playground/website/builder/style.css +++ b/packages/playground/website/builder/style.css @@ -3,6 +3,10 @@ html { font-family: sans-serif; } +html, body { + overflow: hidden; +} + html, body, .playground-wrapper { height: 100%; } @@ -33,6 +37,7 @@ main { .column { display: flex; + flex: 1; flex-direction: column; justify-content: space-between; align-items: center; @@ -63,8 +68,6 @@ main { .frame-wrapper { position: relative; - width: 100%; - height: 100%; } .frame-center { @@ -76,7 +79,7 @@ main { align-items: center; } -.frame-center > svg { +svg { filter: brightness(0.333) contrast(2) brightness(3); mix-blend-mode: lighten; } @@ -85,13 +88,22 @@ iframe { position: absolute; top: 0; left: 0; - width: 100%; - height: 100%; + height: calc(100% / var(--zoom, 1)); + width: calc(100% / var(--zoom, 1)); + transform-origin: top left; + transform: scale(var(--zoom, 1)); box-sizing: border-box; border: 0; + background-color: rgba(0,0,0, 0.25); + transition: transform 0.5s linear, width 0.5s linear, height 0.5s linear; +} + +.column.viewer { + overflow: hidden; } button { + white-space: pre; font-weight: bold; background: none; border: none; @@ -100,6 +112,8 @@ button { padding: 8px; margin: 7px; box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.25); + user-select: none; + border-radius: 2px; } button:active { @@ -126,6 +140,29 @@ button.run { padding-left: 1.85em; } +button#open-ai { + background-image: url('./brain.svg'); + background-size: auto 75%; + background-position: 0.325em center; + background-repeat: no-repeat; + padding-left: 2.85em; +} + +button#save { + background-image: url('./save.svg'); + background-size: auto 60%; + background-position: center; + background-repeat: no-repeat; + width: 2.25rem; +} +button#open { + background-image: url('./open.svg'); + background-size: auto 50%; + background-position: center; + background-repeat: no-repeat; + width: 2.25rem; +} + button#new-tab { background-image: url('./new-tab-button.svg'); background-size: auto 55%; @@ -134,22 +171,20 @@ button#new-tab { padding-right: 2.1em; } -.jsontext, #error-output { +.jsontext, .frame-wrapper { width: 100%; height: 100%; transition: height 0.25s ease-out, padding 0.25s ease-out, background-color 0.25s ease-in; } -#error-output { +.frame-wrapper:has( #error-output ) { background-color: rgba(0, 0, 0, 0.25); margin: 0; padding: 1rem; box-sizing: border-box; - color: #F00; - white-space: pre-wrap; } -#error-output:empty { +.frame-wrapper:has( #error-output[srcdoc=""] ) { background-color: rgba(255, 255, 255, 0.75); height: 0%; padding: 0rem; @@ -163,6 +198,8 @@ button#new-tab { } .toolbar { + position: relative; + z-index: 1; background-color: #40464d; text-align: right; display: flex; @@ -176,7 +213,19 @@ button#new-tab { font-size: 0.8rem; font-style: italic; white-space: pre; - margin-left: 1rem; +} + +.toolbar svg, +.toolbar span { + margin-left: 0.5rem; +} + +.toolbar svg { + display: none; +} + +body[data-loading="true"] .toolbar svg { + display: initial; } .toolbar span.spacer { @@ -191,6 +240,30 @@ pre { button { transition: opacity 0.5s ease-in-out; + height: 2rem; +} + +button.zoom { + color: transparent; + background-repeat: no-repeat; + background-position: center; + background-size: 63%; + width: 2rem; +} + +#zoom { + font-style: normal; + text-align: center; + width: 2.25rem; + margin: 0; +} + +button#zoom-in { + background-image: url(./zoom-in.svg); +} + +button#zoom-out { + background-image: url(./zoom-out.svg); } [data-starting=true] button.run { @@ -206,4 +279,119 @@ button { @keyframes button-pulse { from { opacity: 1; } to { opacity: 0.5; } +} + +section#ai { + display: flex; + flex-direction: column; + height: 0rem; + overflow: hidden; + transition: height 0.25s ease-in; +} + +body[data-show-ai=true] section#ai { + height: 35rem; +} + +#prompt { + font-family: sans-serif; + font-size: 1.1rem; + background-color: #CCC; + width: 100%; + resize: none; + box-sizing: border-box; + margin: 0; + flex: 1; + padding: 1rem; + border-radius: 0; + border: 0; +} + +button.close { + background-image: url(./x.svg); + background-size: auto 55%; + background-position: center; + background-repeat: no-repeat; + width: 2.2rem; +} + +section.fly-in { + position: absolute; + left: 0; + top:0; + height: 100%; + min-width: 20rem; + background-color: #1e2327; + border-right: 1px solid rgba(255, 255, 255, 0.25); + z-index: 10; + color: white; + padding: 20px; + box-sizing: border-box; + transition: transform 0.125s ease-out; +} + +section.fly-in p { + margin: 0; +} + +section.fly-in ul { + padding: 0; + display: flex; + flex-direction: column; +} + +section.fly-in ul li { + list-style: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: 2rem; + white-space: pre; +} + +section.fly-in ul li span { + flex: 1; +} + +section.fly-in ul li button { + opacity: 0; + pointer-events: none; +} + +section.fly-in ul li:hover button { + opacity: 0.5; + visibility: initial; + pointer-events: initial; +} + +section.fly-in ul li:hover button:hover { + opacity: 1; + filter: invert(1); +} + +section.fly-in ul li button { + border: 0; + background: transparent; + box-shadow: none; + filter: invert(1); + + background-size: auto 55%; + background-position: center; + background-repeat: no-repeat; + width: 1.25rem; +} + +section.fly-in ul li:hover button.rename { + background-image: url(./edit.svg); +} +section.fly-in ul li:hover button.download { + background-image: url(./download.svg); +} +section.fly-in ul li:hover button.delete { + background-image: url(./x.svg); +} + +body[data-menu=false] section.fly-in { + transform: translateX(-100%); } \ No newline at end of file From 783842f43c619bd1789ac298efe2dca917ea4adf Mon Sep 17 00:00:00 2001 From: Sean Morris <640101+seanmorris@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:44:25 -0500 Subject: [PATCH 5/5] My changes. --- packages/playground/website/builder/brain.svg | 5 + packages/playground/website/builder/index.js | 1565 +++++++++-------- packages/playground/website/builder/style.css | 473 ++--- packages/playground/website/builder/x.svg | 3 + .../playground/website/builder/zoom-in.svg | 4 + .../playground/website/builder/zoom-out.svg | 4 + 6 files changed, 1128 insertions(+), 926 deletions(-) create mode 100644 packages/playground/website/builder/brain.svg create mode 100644 packages/playground/website/builder/x.svg create mode 100644 packages/playground/website/builder/zoom-in.svg create mode 100644 packages/playground/website/builder/zoom-out.svg diff --git a/packages/playground/website/builder/brain.svg b/packages/playground/website/builder/brain.svg new file mode 100644 index 0000000000..39be554915 --- /dev/null +++ b/packages/playground/website/builder/brain.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/playground/website/builder/index.js b/packages/playground/website/builder/index.js index 50909dda1e..5fc9774d61 100644 --- a/packages/playground/website/builder/index.js +++ b/packages/playground/website/builder/index.js @@ -1,12 +1,16 @@ //*/ // Prod -const importStartPlaygroundWeb = import('https://unpkg.com/@wp-playground/client/index.js'); -const fetchBlueprintSchema = fetch('https://unpkg.com/@wp-playground/blueprints/blueprint-schema.json').then(r=>r.json()); +const importStartPlaygroundWeb = import( + 'https://unpkg.com/@wp-playground/client/index.js' +); +const fetchBlueprintSchema = fetch( + 'https://unpkg.com/@wp-playground/blueprints/blueprint-schema.json' +).then((r) => r.json()); /*/ // Dev const importStartPlaygroundWeb = import('http://localhost:8080/client/index.js'); const fetchBlueprintSchema = fetch('http://localhost:8080/blueprints/blueprint-schema.json').then(r=>r.json()); //*/ -const FALLBACK_TIMEOUT = 30*1000; +const FALLBACK_TIMEOUT = 30 * 1000; const deref = (obj, root) => { if (!obj || typeof obj !== 'object' || !('$ref' in obj)) { @@ -23,13 +27,12 @@ const deref = (obj, root) => { node = node[p]; } - return {...obj, ...node}; + return { ...obj, ...node }; }; const reader = Symbol('reader'); const getSchemaReader = (schema, root = null) => { - if (schema[reader]) { return schema[reader]; } @@ -38,47 +41,49 @@ const getSchemaReader = (schema, root = null) => { root = schema; } - const proxy = new Proxy(schema, {get: (target, key, receiver) => { - const val = Reflect.get(target, key, receiver); - if (val && typeof val === 'object') { - return getSchemaReader(deref(val, root), root); - } - return val; - }}); + const proxy = new Proxy(schema, { + get: (target, key, receiver) => { + const val = Reflect.get(target, key, receiver); + if (val && typeof val === 'object') { + return getSchemaReader(deref(val, root), root); + } + return val; + }, + }); schema[reader] = proxy; return proxy; }; -const getPrevKeys = (editor, {column, row}) => { +const getPrevKeys = (editor, { column, row }) => { const content = editor.getValue(); - const lines = content.split("\n"); + const lines = content.split('\n'); const line = String(lines[row]); const colon = line.indexOf(':'); - const path = []; + const path = []; if (colon > -1 && column > colon) { const openQuote = line.indexOf('"'); - const closeQuote = line.indexOf('"', 1 + openQuote); + const closeQuote = line.indexOf('"', 1 + openQuote); path.push(line.substring(1 + openQuote, closeQuote)); } let indent = 0; - while (line[indent] == ' ' || line[indent] == "\t") { + while (line[indent] == ' ' || line[indent] == '\t') { indent++; } let checkRow = -1 + row; - while(checkRow >= 0) { + while (checkRow >= 0) { const openQuote = lines[checkRow].indexOf('"'); const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); - if(openQuote > -1 && openQuote < indent) { - path.push(lines[checkRow].substring(1 + openQuote, closeQuote)); - indent = openQuote; + if (openQuote > -1 && openQuote < indent) { + path.push(lines[checkRow].substring(1 + openQuote, closeQuote)); + indent = openQuote; } checkRow--; } @@ -86,740 +91,912 @@ const getPrevKeys = (editor, {column, row}) => { return path; }; -const getLastOfType = (editor, type, {column, row}, skip = 0) => { - const content = editor.getValue(); - const lines = content.split("\n"); - - let checkRow = -1 + row; +const getLastOfType = (editor, type, { column, row }, skip = 0) => { + const content = editor.getValue(); + const lines = content.split('\n'); - while(checkRow >= 0) { - const openBracket = lines[checkRow].indexOf('{'); + let checkRow = -1 + row; - if(openBracket > -1) { - if(--skip < 0) { - return null; - } - checkRow--; - continue; - } + while (checkRow >= 0) { + const openBracket = lines[checkRow].indexOf('{'); - let indent = 0; + if (openBracket > -1) { + if (--skip < 0) { + return null; + } + checkRow--; + continue; + } - while (lines[checkRow][indent] == ' ' || lines[checkRow][indent] == "\t") { - indent++; - } + let indent = 0; - const openQuote = lines[checkRow].indexOf('"'); - const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); - const openVQuote = lines[checkRow].indexOf('"', 1 + closeQuote); - const closeVQuote = lines[checkRow].indexOf('"', 1 + openVQuote); + while ( + lines[checkRow][indent] == ' ' || + lines[checkRow][indent] == '\t' + ) { + indent++; + } - if(openQuote > -1 && openQuote == indent) { - const checkType = lines[checkRow].substring(1 + openQuote, closeQuote); - if (type === checkType) { - return lines[checkRow].substring(1 + openVQuote, closeVQuote); - } - } + const openQuote = lines[checkRow].indexOf('"'); + const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); + const openVQuote = lines[checkRow].indexOf('"', 1 + closeQuote); + const closeVQuote = lines[checkRow].indexOf('"', 1 + openVQuote); + + if (openQuote > -1 && openQuote == indent) { + const checkType = lines[checkRow].substring( + 1 + openQuote, + closeQuote + ); + if (type === checkType) { + return lines[checkRow].substring(1 + openVQuote, closeVQuote); + } + } - checkRow--; - } + checkRow--; + } - return null; + return null; }; -const getPrevSiblings = (editor, {column, row}) => { - const content = editor.getValue(); - const lines = content.split("\n"); +const getPrevSiblings = (editor, { column, row }) => { + const content = editor.getValue(); + const lines = content.split('\n'); - let checkRow = -1 + row; - let indent = 0; + let checkRow = -1 + row; + let indent = 0; - while (lines[row][indent] == ' ' || lines[row][indent] == "\t") { - indent++; - } + while (lines[row][indent] == ' ' || lines[row][indent] == '\t') { + indent++; + } - const siblings = []; + const siblings = []; - while(checkRow >= 0) { - const openBracket = lines[checkRow].indexOf('{'); + while (checkRow >= 0) { + const openBracket = lines[checkRow].indexOf('{'); - if(openBracket > -1 && openBracket < indent) { - break; - } + if (openBracket > -1 && openBracket < indent) { + break; + } - const openQuote = lines[checkRow].indexOf('"'); - const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); + const openQuote = lines[checkRow].indexOf('"'); + const closeQuote = lines[checkRow].indexOf('"', 1 + openQuote); - if(openQuote > -1 && openQuote == indent) { - siblings.push(lines[checkRow].substring(1 + openQuote, closeQuote)) - } + if (openQuote > -1 && openQuote == indent) { + siblings.push(lines[checkRow].substring(1 + openQuote, closeQuote)); + } - checkRow--; - } + checkRow--; + } - return siblings; + return siblings; }; const getStepProperties = async (stepType) => { - const schema = await fetchBlueprintSchema; - const reader = getSchemaReader(schema); - return reader.definitions.StepDefinition.oneOf - .filter(s => s.properties.step['const'] === stepType) - .map(s => s.properties) - .flat() - .pop(); -} + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter((s) => s.properties.step['const'] === stepType) + .map((s) => s.properties) + .flat() + .pop(); +}; const completeStepProperty = async (stepType, prefix) => { - const schema = await fetchBlueprintSchema; - return schema.definitions.StepDefinition.oneOf - .filter(s => s.properties.step['const'] === stepType) - .map(s => Object.keys(s.properties)) - .flat() - .filter(s => s.substr(0, prefix.length) === prefix) - .filter(s => !['step', 'progress'].includes(s)); + const schema = await fetchBlueprintSchema; + return schema.definitions.StepDefinition.oneOf + .filter((s) => s.properties.step['const'] === stepType) + .map((s) => Object.keys(s.properties)) + .flat() + .filter((s) => s.substr(0, prefix.length) === prefix) + .filter((s) => !['step', 'progress'].includes(s)); }; -const getStepSubProperties = async(stepType, resType, property) => { - const schema = await fetchBlueprintSchema; - const reader = getSchemaReader(schema); - return reader.definitions.StepDefinition.oneOf - .filter(s => s.properties.step['const'] === stepType) - .map(s => { - return s.properties[property].anyOf; - }) - .flat() - .filter(s => !resType || s.properties.resource.const === resType) - .map(s => s.properties) - .flat() - .pop(); -} +const getStepSubProperties = async (stepType, resType, property) => { + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter((s) => s.properties.step['const'] === stepType) + .map((s) => { + return s.properties[property].anyOf; + }) + .flat() + .filter((s) => !resType || s.properties.resource.const === resType) + .map((s) => s.properties) + .flat() + .pop(); +}; -const completeStepSubProperty = async (stepType, resType, property, subKey, prefix) => { - if(!resType && !subKey) { - return ['resource']; - } - const schema = await fetchBlueprintSchema; - const reader = getSchemaReader(schema); - return reader.definitions.StepDefinition.oneOf - .filter(s => s.properties.step['const'] === stepType) - .map(s => { - return s.properties[property].anyOf; - }) - .flat() - .filter(s => !resType || s.properties.resource.const === resType) - .map(s => { - if(subKey === null) { - return Object.keys(s.properties); - } - return s.properties.resource.const; - }) - .flat() - .filter(s => !['resource'].includes(s)); -} +const completeStepSubProperty = async ( + stepType, + resType, + property, + subKey, + prefix +) => { + if (!resType && !subKey) { + return ['resource']; + } + const schema = await fetchBlueprintSchema; + const reader = getSchemaReader(schema); + return reader.definitions.StepDefinition.oneOf + .filter((s) => s.properties.step['const'] === stepType) + .map((s) => { + return s.properties[property].anyOf; + }) + .flat() + .filter((s) => !resType || s.properties.resource.const === resType) + .map((s) => { + if (subKey === null) { + return Object.keys(s.properties); + } + return s.properties.resource.const; + }) + .flat() + .filter((s) => !['resource'].includes(s)); +}; -const completeStep = async(prefix) => { - const schema = await fetchBlueprintSchema; - return schema.definitions.StepDefinition.oneOf - .map(s => s.properties.step['const']) - .filter(s => s.substr(0, prefix.length) === prefix); +const completeStep = async (prefix) => { + const schema = await fetchBlueprintSchema; + return schema.definitions.StepDefinition.oneOf + .map((s) => s.properties.step['const']) + .filter((s) => s.substr(0, prefix.length) === prefix); }; const completePhpVersion = async (prefix) => { - const schema = await fetchBlueprintSchema; - return schema.definitions.SupportedPHPVersion.enum - .filter(s => s.substr(0, prefix.length) === prefix); + const schema = await fetchBlueprintSchema; + return schema.definitions.SupportedPHPVersion.enum.filter( + (s) => s.substr(0, prefix.length) === prefix + ); }; const completeRootKey = async (prefix) => { - const schema = await fetchBlueprintSchema; - return Object.keys(schema.definitions.Blueprint.properties) - .filter(s => s[0] !== '$' && s.substr(0, prefix.length) === prefix); + const schema = await fetchBlueprintSchema; + return Object.keys(schema.definitions.Blueprint.properties).filter( + (s) => s[0] !== '$' && s.substr(0, prefix.length) === prefix + ); }; const completeFeature = async (prefix) => { - const schema = await fetchBlueprintSchema; - return Object.keys(schema.definitions.Blueprint.properties.features.properties) - .filter(s => s[0] !== '$' && s.substr(0, prefix.length) === prefix); + const schema = await fetchBlueprintSchema; + return Object.keys( + schema.definitions.Blueprint.properties.features.properties + ).filter((s) => s[0] !== '$' && s.substr(0, prefix.length) === prefix); }; let debounce = null; let starting = null; const getCompletions = async (editor, session, pos, prefix, callback) => { - const list = []; - const prevKey = getPrevKeys(editor, pos); - - const content = editor.getValue(); - const lines = content.split("\n"); - const line = String(lines[pos.row]); - const colon = line.indexOf(':'); - - const {row,column} = pos; - - const qA = (!lines[row][-1 + column] || lines[row][-1 + column] === ' ') ? '"' : ''; - const qB = (!lines[row][column] || lines[row][column] === ' ') ? '"' : ''; - - if(prevKey.length === 3 && prefix.length >= 3 && prevKey.join('<') === 'slug { - try { - const res = await fetch(`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`); - const json = await res.json(); - json?.plugins.map(p => { - var doc = new DOMParser().parseFromString(p.name, "text/html"); - const meta = doc.documentElement.textContent; - callback(null, [{name: p.slug, value: qA + p.slug + qB, score: 1, meta}]); - }); - } - finally { - document.body.setAttribute('data-loading', false); - } - }, 250); - } - - if(prevKey.length === 3 && prefix.length >= 3 && prevKey.join('<') === 'slug { - try { - const res = await fetch(`https://playground.wordpress.net/plugin-proxy.php?${proxyParams}`); - const json = await res.json(); - json?.themes.map(p => { - var doc = new DOMParser().parseFromString(p.name, "text/html"); - const meta = doc.documentElement.textContent; - callback(null, [{name: p.slug, value: qA + p.slug + qB, score: 1, meta}]); - }); - } - finally { - document.body.setAttribute('data-loading', false); - } - }, 250); - } - - switch (prevKey[0]) { - case 'preferredVersions': { - const used = await getPrevSiblings(editor, pos); - list.push(...['wp', 'php'].filter(s => !used.includes(s))); - break; - } - - case 'wp': - list.push('latest'); - break; - - case 'php': - list.push(...await completePhpVersion(prefix)); - break; - - case 'steps': { - const used = await getPrevSiblings(editor, pos); - const stepType = getLastOfType(editor, 'step', pos); - if(stepType) { - const suggestions = await completeStepProperty(stepType, prefix) - list.push(...(suggestions).filter(s => !used.includes(s))); - } - else { - list.push('step'); - } - } - break; - - case 'step': - list.push(...await completeStep(prefix)); - break; - - case 'features': - list.push(...await completeFeature(prefix)); - break; - - case undefined: - list.push(...await completeRootKey(prefix)); - break; - - default: - switch (prevKey[-1 + prevKey.length]) { - case 'steps': { - const stepType = getLastOfType(editor, 'step', pos, 1); - const resType = getLastOfType(editor, 'resource', pos, 1); - if (prevKey.length === 2) { - if (colon === -1) { - const used = await getPrevSiblings(editor, pos); - const suggestions = await completeStepSubProperty(stepType, resType, prevKey[-2 + prevKey.length], null, prefix); - list.push(...suggestions.filter(s => !used.includes(s))); - } - } - else if (prevKey.length === 3 && prevKey[0] === 'resource') { - list.push(...await completeStepSubProperty(stepType, resType, prevKey[-2 + prevKey.length], prevKey[0], prefix)); - } - } - break; - } - - break; - } - - for (const fill of list) { - callback(null, [{name: fill, value: qA + fill + qB, score: 1, meta: "Blueprint Schema"}]); - } + const list = []; + const prevKey = getPrevKeys(editor, pos); + + const content = editor.getValue(); + const lines = content.split('\n'); + const line = String(lines[pos.row]); + const colon = line.indexOf(':'); + + const { row, column } = pos; + + const qA = + !lines[row][-1 + column] || lines[row][-1 + column] === ' ' ? '"' : ''; + const qB = !lines[row][column] || lines[row][column] === ' ' ? '"' : ''; + + if ( + prevKey.length === 3 && + prefix.length >= 3 && + prevKey.join('<') === 'slug { + try { + const res = await fetch( + `https://playground.wordpress.net/plugin-proxy.php?${proxyParams}` + ); + const json = await res.json(); + json?.plugins.map((p) => { + var doc = new DOMParser().parseFromString( + p.name, + 'text/html' + ); + const meta = doc.documentElement.textContent; + callback(null, [ + { + name: p.slug, + value: qA + p.slug + qB, + score: 1, + meta, + }, + ]); + }); + } finally { + document.body.setAttribute('data-loading', false); + } + }, 250); + } + + if ( + prevKey.length === 3 && + prefix.length >= 3 && + prevKey.join('<') === 'slug { + try { + const res = await fetch( + `https://playground.wordpress.net/plugin-proxy.php?${proxyParams}` + ); + const json = await res.json(); + json?.themes.map((p) => { + var doc = new DOMParser().parseFromString( + p.name, + 'text/html' + ); + const meta = doc.documentElement.textContent; + callback(null, [ + { + name: p.slug, + value: qA + p.slug + qB, + score: 1, + meta, + }, + ]); + }); + } finally { + document.body.setAttribute('data-loading', false); + } + }, 250); + } + + switch (prevKey[0]) { + case 'preferredVersions': { + const used = await getPrevSiblings(editor, pos); + list.push(...['wp', 'php'].filter((s) => !used.includes(s))); + break; + } + + case 'wp': + list.push('latest'); + break; + + case 'php': + list.push(...(await completePhpVersion(prefix))); + break; + + case 'steps': + { + const used = await getPrevSiblings(editor, pos); + const stepType = getLastOfType(editor, 'step', pos); + if (stepType) { + const suggestions = await completeStepProperty( + stepType, + prefix + ); + list.push(...suggestions.filter((s) => !used.includes(s))); + } else { + list.push('step'); + } + } + break; + + case 'step': + list.push(...(await completeStep(prefix))); + break; + + case 'features': + list.push(...(await completeFeature(prefix))); + break; + + case undefined: + list.push(...(await completeRootKey(prefix))); + break; + + default: + switch (prevKey[-1 + prevKey.length]) { + case 'steps': + { + const stepType = getLastOfType(editor, 'step', pos, 1); + const resType = getLastOfType( + editor, + 'resource', + pos, + 1 + ); + if (prevKey.length === 2) { + if (colon === -1) { + const used = await getPrevSiblings(editor, pos); + const suggestions = + await completeStepSubProperty( + stepType, + resType, + prevKey[-2 + prevKey.length], + null, + prefix + ); + list.push( + ...suggestions.filter( + (s) => !used.includes(s) + ) + ); + } + } else if ( + prevKey.length === 3 && + prevKey[0] === 'resource' + ) { + list.push( + ...(await completeStepSubProperty( + stepType, + resType, + prevKey[-2 + prevKey.length], + prevKey[0], + prefix + )) + ); + } + } + break; + } + + break; + } + + for (const fill of list) { + callback(null, [ + { + name: fill, + value: qA + fill + qB, + score: 1, + meta: 'Blueprint Schema', + }, + ]); + } }; let errorTag; const showError = (error) => { - console.error(error); - if(!errorTag) errorTag = document.getElementById('error-output'); - const errDoc = `${error}`; - errorTag.setAttribute('srcdoc', errDoc); -} + console.error(error); + if (!errorTag) errorTag = document.getElementById('error-output'); + const errDoc = `${error}`; + errorTag.setAttribute('srcdoc', errDoc); +}; const clearError = (error) => { - if(!errorTag) errorTag = document.getElementById('error-output'); - errorTag.setAttribute('srcdoc', ''); -} + if (!errorTag) errorTag = document.getElementById('error-output'); + errorTag.setAttribute('srcdoc', ''); +}; -const formatJson = (editor, jsonObject = {} ) => { - const existing = editor.getSession().getValue(); - const formatted = JSON.stringify(jsonObject, null, 2) + "\n"; - if(formatted !== existing) { - editor.getSession().setValue(formatted); - if(formatted !== existing) { - editor.getSession().setValue(formatted) - } - } - // document.getElementById('jsontext').innerText = formatted; +const formatJson = (editor, jsonObject = {}) => { + const existing = editor.getSession().getValue(); + const formatted = JSON.stringify(jsonObject, null, 2) + '\n'; + if (formatted !== existing) { + editor.getSession().setValue(formatted); + if (formatted !== existing) { + editor.getSession().setValue(formatted); + } + } + // document.getElementById('jsontext').innerText = formatted; }; function getCurrentBlueprint(editor) { - const blueprint = JSON.parse(editor.getValue()); - if ( blueprint.features && blueprint.features.networking === false ) { - blueprint.features.networking = true; - } - return blueprint; + const blueprint = JSON.parse(editor.getValue()); + if (blueprint.features && blueprint.features.networking === false) { + blueprint.features.networking = true; + } + return blueprint; } const fetchBluePrintFromAI = async (editor) => { - const prompt = document.getElementById('prompt').value; - const blueprint = getCurrentBlueprint(editor); - document.body.setAttribute('data-starting', true); - document.getElementById( 'prompt' ).setAttribute('disabled', true); - - console.log( 'Calling AI', prompt, blueprint ); - const response = await fetch('https://public-api.wordpress.com/wpcom/v2/playground/ai/blueprint', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({prompt,blueprint,}), - }); - const json = await response.json(); - document.getElementById( 'prompt' ).removeAttribute('disabled'); - document.getElementById( 'prompt' ).innerText = ''; - console.log( 'Returned AI blueprint', json ); - return json; + const prompt = document.getElementById('prompt').value; + const blueprint = getCurrentBlueprint(editor); + document.body.setAttribute('data-starting', true); + document.getElementById('prompt').setAttribute('disabled', true); + + console.log('Calling AI', prompt, blueprint); + const response = await fetch( + 'https://public-api.wordpress.com/wpcom/v2/playground/ai/blueprint', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ prompt, blueprint }), + } + ); + const json = await response.json(); + document.getElementById('prompt').removeAttribute('disabled'); + document.getElementById('prompt').innerText = ''; + console.log('Returned AI blueprint', json); + return json; }; const runBlueprint = async (editor) => { - const fallback = setTimeout(() => { - document.body.setAttribute('data-starting', false) - starting = false; - }, FALLBACK_TIMEOUT); - try { - window.location.hash = JSON.stringify(JSON.parse(editor.getValue())); - if (starting) { - return; - } - starting = true; - document.body.setAttribute('data-starting', true); - clearError(); - const blueprintJsonObject = getCurrentBlueprint(editor); - window.location.hash = JSON.stringify( getCurrentBlueprint(editor) ); - formatJson(editor, blueprintJsonObject); - const blueprintCopy = JSON.parse(JSON.stringify(blueprintJsonObject)); - delete blueprintCopy.features; // I am getting error otherwise - const startPlaygroundWeb = (await importStartPlaygroundWeb).startPlaygroundWeb; - await startPlaygroundWeb({ - iframe: document.getElementById('wp-playground'), - remoteUrl: `https://playground.wordpress.net/remote.html`, - blueprint: blueprintCopy, - }); - document.body.setAttribute('data-starting', false); - clearTimeout(fallback); - starting = false; - } - catch (error) { - starting = false; - document.body.setAttribute('data-starting', false); - showError(error); - clearTimeout(fallback); - } - finally { - document.body.setAttribute('data-starting', false); - } + const fallback = setTimeout(() => { + document.body.setAttribute('data-starting', false); + starting = false; + }, FALLBACK_TIMEOUT); + try { + window.location.hash = JSON.stringify(JSON.parse(editor.getValue())); + if (starting) { + return; + } + starting = true; + document.body.setAttribute('data-starting', true); + clearError(); + const blueprintJsonObject = getCurrentBlueprint(editor); + window.location.hash = JSON.stringify(getCurrentBlueprint(editor)); + formatJson(editor, blueprintJsonObject); + const blueprintCopy = JSON.parse(JSON.stringify(blueprintJsonObject)); + delete blueprintCopy.features; // I am getting error otherwise + const startPlaygroundWeb = (await importStartPlaygroundWeb) + .startPlaygroundWeb; + await startPlaygroundWeb({ + iframe: document.getElementById('wp-playground'), + remoteUrl: `https://playground.wordpress.net/remote.html`, + blueprint: blueprintCopy, + }); + document.body.setAttribute('data-starting', false); + clearTimeout(fallback); + starting = false; + } catch (error) { + starting = false; + document.body.setAttribute('data-starting', false); + showError(error); + clearTimeout(fallback); + } finally { + document.body.setAttribute('data-starting', false); + } }; const loadFromHash = (editor) => { - const hash = decodeURI(window.location.hash.substr(1)); - try { - formatJson(editor, JSON.parse(hash)); - } catch (error) { - console.error(error); - } + const hash = decodeURI(window.location.hash.substr(1)); + try { + formatJson(editor, JSON.parse(hash)); + } catch (error) { + console.error(error); + } }; -document.addEventListener("DOMContentLoaded", () => { - const iframeSrc = "https://playground.wordpress.net/"; - const iframe = document.querySelector("iframe#wp-playground"); - const textarea = document.querySelector("#jsontext"); - const button = document.querySelector("button#run"); - const aiButton = document.querySelector("button#open-ai"); - const closeAi = document.querySelector("button#close-ai"); - const genButton = document.querySelector("button#generate"); - const newTab = document.querySelector("button#new-tab"); - - - let aiOpen = false; - - aiButton.addEventListener('click', event => { - document.body.setAttribute('data-show-ai', aiOpen = !aiOpen); - }); - - closeAi.addEventListener('click', event => { - document.body.setAttribute('data-show-ai', aiOpen = false); - }); - - const editor = ace.edit('jsontext'); - editor.setTheme("ace/theme/github_dark"); - editor.session.setMode("ace/mode/json"); - - const langTools = ace.require('ace/ext/language_tools'); - - langTools.setCompleters([]); - - langTools.addCompleter({triggerCharacters: ['"'],getCompletions}); - - editor.setOptions({ - enableBasicAutocompletion: true, - enableLiveAutocompletion: true, - enableSnippets: true, - useSoftTabs: true, - tabSize: 2, - }); - - editor.getSession().on('change', async event => { - if(event.action !== 'insert') { - return; - } - - const content = editor.getValue(); - const lines = content.split("\n"); - const quoteCount = (lines[event.start.row].match(/"/g) || []).length; - - if(event.start.row === event.end.row && 1 < Math.abs(event.start.column - event.end.column)) { - if(lines[event.end.row][event.end.column] === '"') { - editor.moveCursorTo(event.end.row, event.end.column + 1); - return; - } - } - - if (lines[event.end.row][event.end.column]) { - return; - } - - const indent = (lines[event.start.row].match(/^(\s+)/g) || [''])[0]; - - const inserted = event.lines.join("\n"); - const prevKey = getPrevKeys(editor, event.end); - - if (inserted.length > 1) { - return; - } - - if (inserted === ':') { - const colon = lines[event.start.row].indexOf(':'); - if (colon > -1 && colon < event.start.column) { - return; - } - - if(prevKey.length === 1 && prevKey[0] === 'landingPage') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); - editor.moveCursorTo(event.end.row, 1 + event.end.column); - } - - if(prevKey.length === 1 && prevKey[0] === 'preferredVersions') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); - editor.moveCursorTo(event.end.row, 1 + event.end.column); - } - - if(prevKey.length === 2 && prevKey[1] === 'preferredVersions') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); - editor.moveCursorTo(event.end.row, 1 + event.end.column); - setTimeout(() => editor.execCommand('startAutocomplete'), 0); - } - - if(prevKey.length === 1 && (prevKey[0] === 'steps' || prevKey[0] === 'features')) { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' []'); - editor.moveCursorTo(event.end.row, 1 + event.end.column); - } - - if(prevKey.length === 3 && prevKey[2] === 'steps') { - const stepType = getLastOfType(editor, 'step', event.end, 1); - const resType = getLastOfType(editor, 'resource', event.end); - const subProps = await getStepSubProperties(stepType, resType, prevKey[1]); - const subProp = subProps[prevKey[0]]; - - if (subProp?.type === 'string') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); - editor.moveCursorTo(event.end.row, 2 + event.end.column); - editor.execCommand('startAutocomplete'); - } - else if (subProp?.type === 'object') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); - editor.moveCursorTo(event.end.row, 2 + event.end.column); - } - } - - if(prevKey.length === 2 && prevKey[0] === 'step' && prevKey[1] === 'steps') { - editor.getSession().insert({row: event.start.row, column: event.start.column + 1}, ' ""'); - editor.moveCursorTo(event.end.row, 1 + event.end.column); - editor.execCommand('startAutocomplete'); - } - else if(prevKey.length === 2 && prevKey[1] === 'steps') { - const stepType = await getLastOfType(editor, 'step', event.end); - const properties = await getStepProperties(stepType); - const property = properties[ prevKey[0] ] ?? null; - const propType = property.type ?? null; - const propRef = property['$ref']; - - if (propType === 'string') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' ""'); - editor.moveCursorTo(event.end.row, 2 + event.end.column); - } - else if(propRef === '#/definitions/FileReference') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, ' {}'); - editor.moveCursorTo(event.end.row, 2 + event.end.column); - } - } - } - else if (inserted === '[') { - editor.getSession().insert({row: event.end.row, column: event.start.column + 1}, "]"); - return; - } - else if (inserted === '{') { - editor.getSession().insert({row: event.end.row, column: event.start.column + 1}, "}"); - return; - } - else if(inserted === ',') { - const nextIndent = (lines[1 + event.start.row].match(/^(\s+)/g) || [''])[0]; - if(nextIndent.length >= indent.length) { - return; - } - if (lines[event.start.row][-1 + event.start.column] !== '"') { - editor.getSession().insert({row: event.end.row, column: event.end.column}, "\n" + indent); - editor.moveCursorTo(1 + event.end.row, 1 + (indent.length)); - return; - } - editor.getSession().insert({row: event.end.row, column: event.end.column}, "\n" + indent + '""'); - editor.moveCursorTo(1 + event.end.row, 1 + (indent.length)); - editor.execCommand('startAutocomplete'); - } - }); - - window.test = { - iframeSrc, - iframe, - textarea, - button, - }; - - button.addEventListener('click', () => { - try { - clearError(); - runBlueprint(editor); - } - catch (error) { - showError(error); - } - }); - - genButton.addEventListener('click', () => { - try { - clearError(); - fetchBluePrintFromAI(editor).then( ({blueprint}) => { - formatJson(editor, blueprint); - runBlueprint(editor); - } ); - } - catch (error) { - showError(error); - } - }); - - let prevWin; - - newTab.addEventListener('click', () => { - runBlueprint(editor); - const query = new URLSearchParams(); - - query.set('mode', 'seamless'); - // query.set('php', blueprint?.preferredVersions?.php); - // query.set('wp', blueprint?.preferredVersions?.wp); - const url = `https://playground.wordpress.net/?${query}#` + JSON.stringify(getCurrentBlueprint(editor)); - if (prevWin) { - prevWin.close(); - } - prevWin = window.open(url, '_blank'); - }); - - if (window.location.hash) { - loadFromHash(editor); - } - else { - formatJson(editor, { - landingPage: "/wp-admin/", - phpExtensionBundles: [ - "kitchen-sink" - ], - preferredVersions: { - php: "7.4", - wp: "5.9", - }, - steps: [ - { - step: "login", - username: "admin", - password: "password", - }, - ], - }); - } - - runBlueprint(editor); - - const editorState = { - current: null - }; - - window.addEventListener('hashchange', () => { - loadFromHash(editor); - runBlueprint(editor); - }); - - editor.commands.addCommand({ - name: 'Run Blueprint', - bindKey: { - win: 'Ctrl-Enter|Ctrl-S', - mac: 'Command-Enter|Command-S', - }, - exec: editor => runBlueprint(editor), - readOnly: false - }); - - const zIn = document.querySelector("button#zoom-in"); - const zOut = document.querySelector("button#zoom-out"); - const zoomLevel = document.querySelector("span#zoom"); - if (zIn && zOut && zoomLevel) { - let zoom = 1; - - zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; - - zIn.addEventListener('click', () => { - if (zoom > 3) { - return; - } - zoom += 0.1; - iframe.style.setProperty('--zoom', zoom); - zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; - }); - - zOut.addEventListener('click', () => { - if (zoom < 0.35) { - return; - } - zoom -= 0.1; - iframe.style.setProperty('--zoom', zoom); - zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; - }); - } - - const save = document.querySelector("button#save"); - const open = document.querySelector("button#open"); - - const saveMethod = () => { - const dataUri = `data:application/json;base64,${btoa(JSON.stringify(JSON.parse(editor.getValue()), null, 2))}` - const link = document.createElement('a'); - link.setAttribute('href', dataUri); - link.setAttribute('download', `blueprint-${(new Date).toISOString()}.json`); - link.click(); - }; - - const openMethod = () => { - const input = document.createElement('input'); - input.setAttribute('type', 'file'); - input.addEventListener('change', event => { - [...input.files].forEach(f => { - const reader = new FileReader(); - reader.addEventListener('load', () => { - editor.setValue(JSON.stringify(JSON.parse(reader.result), null, 2)); - editor.moveCursorTo(0, 0); - runBlueprint(editor); - }); - reader.readAsText(f); - }); - }); - input.click(); - }; - - save.addEventListener('click', saveMethod); - open.addEventListener('click', openMethod); - - editor.commands.addCommand({ - name: 'Save Blueprint', - bindKey: { - win: 'Ctrl-S', - mac: 'Command-S', - }, - exec: editor => saveMethod(), - readOnly: false - }); - - editor.commands.addCommand({ - name: 'Open Blueprint', - bindKey: { - win: 'Ctrl-O', - mac: 'Command-O', - }, - exec: editor => openMethod(), - readOnly: false - }); +document.addEventListener('DOMContentLoaded', () => { + const iframeSrc = 'https://playground.wordpress.net/'; + const iframe = document.querySelector('iframe#wp-playground'); + const textarea = document.querySelector('#jsontext'); + const button = document.querySelector('button#run'); + const aiButton = document.querySelector('button#open-ai'); + const closeAi = document.querySelector('button#close-ai'); + const genButton = document.querySelector('button#generate'); + const newTab = document.querySelector('button#new-tab'); + + let aiOpen = false; + + aiButton.addEventListener('click', (event) => { + document.body.setAttribute('data-show-ai', (aiOpen = !aiOpen)); + }); + + closeAi.addEventListener('click', (event) => { + document.body.setAttribute('data-show-ai', (aiOpen = false)); + }); + + const editor = ace.edit('jsontext'); + editor.setTheme('ace/theme/github_dark'); + editor.session.setMode('ace/mode/json'); + + const langTools = ace.require('ace/ext/language_tools'); + + langTools.setCompleters([]); + + langTools.addCompleter({ triggerCharacters: ['"'], getCompletions }); + + editor.setOptions({ + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, + enableSnippets: true, + useSoftTabs: true, + tabSize: 2, + }); + + editor.getSession().on('change', async (event) => { + if (event.action !== 'insert') { + return; + } + + const content = editor.getValue(); + const lines = content.split('\n'); + const quoteCount = (lines[event.start.row].match(/"/g) || []).length; + + if ( + event.start.row === event.end.row && + 1 < Math.abs(event.start.column - event.end.column) + ) { + if (lines[event.end.row][event.end.column] === '"') { + editor.moveCursorTo(event.end.row, event.end.column + 1); + return; + } + } + + if (lines[event.end.row][event.end.column]) { + return; + } + + const indent = (lines[event.start.row].match(/^(\s+)/g) || [''])[0]; + + const inserted = event.lines.join('\n'); + const prevKey = getPrevKeys(editor, event.end); + + if (inserted.length > 1) { + return; + } + + if (inserted === ':') { + const colon = lines[event.start.row].indexOf(':'); + if (colon > -1 && colon < event.start.column) { + return; + } + + if (prevKey.length === 1 && prevKey[0] === 'landingPage') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' ""' + ); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if (prevKey.length === 1 && prevKey[0] === 'preferredVersions') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' {}' + ); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if (prevKey.length === 2 && prevKey[1] === 'preferredVersions') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' ""' + ); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + setTimeout(() => editor.execCommand('startAutocomplete'), 0); + } + + if ( + prevKey.length === 1 && + (prevKey[0] === 'steps' || prevKey[0] === 'features') + ) { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' []' + ); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + } + + if (prevKey.length === 3 && prevKey[2] === 'steps') { + const stepType = getLastOfType(editor, 'step', event.end, 1); + const resType = getLastOfType(editor, 'resource', event.end); + const subProps = await getStepSubProperties( + stepType, + resType, + prevKey[1] + ); + const subProp = subProps[prevKey[0]]; + + if (subProp?.type === 'string') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' ""' + ); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + editor.execCommand('startAutocomplete'); + } else if (subProp?.type === 'object') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' {}' + ); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } + } + + if ( + prevKey.length === 2 && + prevKey[0] === 'step' && + prevKey[1] === 'steps' + ) { + editor + .getSession() + .insert( + { + row: event.start.row, + column: event.start.column + 1, + }, + ' ""' + ); + editor.moveCursorTo(event.end.row, 1 + event.end.column); + editor.execCommand('startAutocomplete'); + } else if (prevKey.length === 2 && prevKey[1] === 'steps') { + const stepType = await getLastOfType(editor, 'step', event.end); + const properties = await getStepProperties(stepType); + const property = properties[prevKey[0]] ?? null; + const propType = property.type ?? null; + const propRef = property['$ref']; + + if (propType === 'string') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' ""' + ); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } else if (propRef === '#/definitions/FileReference') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + ' {}' + ); + editor.moveCursorTo(event.end.row, 2 + event.end.column); + } + } + } else if (inserted === '[') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.start.column + 1 }, + ']' + ); + return; + } else if (inserted === '{') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.start.column + 1 }, + '}' + ); + return; + } else if (inserted === ',') { + const nextIndent = (lines[1 + event.start.row].match(/^(\s+)/g) || [ + '', + ])[0]; + if (nextIndent.length >= indent.length) { + return; + } + if (lines[event.start.row][-1 + event.start.column] !== '"') { + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + '\n' + indent + ); + editor.moveCursorTo(1 + event.end.row, 1 + indent.length); + return; + } + editor + .getSession() + .insert( + { row: event.end.row, column: event.end.column }, + '\n' + indent + '""' + ); + editor.moveCursorTo(1 + event.end.row, 1 + indent.length); + editor.execCommand('startAutocomplete'); + } + }); + + window.test = { + iframeSrc, + iframe, + textarea, + button, + }; + + button.addEventListener('click', () => { + try { + clearError(); + runBlueprint(editor); + } catch (error) { + showError(error); + } + }); + + genButton.addEventListener('click', () => { + try { + clearError(); + fetchBluePrintFromAI(editor).then(({ blueprint }) => { + formatJson(editor, blueprint); + runBlueprint(editor); + }); + } catch (error) { + showError(error); + } + }); + + let prevWin; + + newTab.addEventListener('click', () => { + runBlueprint(editor); + const query = new URLSearchParams(); + + query.set('mode', 'seamless'); + // query.set('php', blueprint?.preferredVersions?.php); + // query.set('wp', blueprint?.preferredVersions?.wp); + const url = + `https://playground.wordpress.net/?${query}#` + + JSON.stringify(getCurrentBlueprint(editor)); + if (prevWin) { + prevWin.close(); + } + prevWin = window.open(url, '_blank'); + }); + + if (window.location.hash) { + loadFromHash(editor); + } else { + formatJson(editor, { + landingPage: '/wp-admin/', + phpExtensionBundles: ['kitchen-sink'], + preferredVersions: { + php: '7.4', + wp: '5.9', + }, + steps: [ + { + step: 'login', + username: 'admin', + password: 'password', + }, + ], + }); + } + + runBlueprint(editor); + + const editorState = { + current: null, + }; + + window.addEventListener('hashchange', () => { + loadFromHash(editor); + runBlueprint(editor); + }); + + editor.commands.addCommand({ + name: 'Run Blueprint', + bindKey: { + win: 'Ctrl-Enter|Ctrl-S', + mac: 'Command-Enter|Command-S', + }, + exec: (editor) => runBlueprint(editor), + readOnly: false, + }); + + const zIn = document.querySelector('button#zoom-in'); + const zOut = document.querySelector('button#zoom-out'); + const zoomLevel = document.querySelector('span#zoom'); + if (zIn && zOut && zoomLevel) { + let zoom = 1; + + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + + zIn.addEventListener('click', () => { + if (zoom > 3) { + return; + } + zoom += 0.1; + iframe.style.setProperty('--zoom', zoom); + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + }); + + zOut.addEventListener('click', () => { + if (zoom < 0.35) { + return; + } + zoom -= 0.1; + iframe.style.setProperty('--zoom', zoom); + zoomLevel.innerText = (100 * zoom).toFixed(0) + '%'; + }); + } + + const save = document.querySelector('button#save'); + const open = document.querySelector('button#open'); + + const saveMethod = () => { + const dataUri = `data:application/json;base64,${btoa( + JSON.stringify(JSON.parse(editor.getValue()), null, 2) + )}`; + const link = document.createElement('a'); + link.setAttribute('href', dataUri); + link.setAttribute( + 'download', + `blueprint-${new Date().toISOString()}.json` + ); + link.click(); + }; + + const openMethod = () => { + const input = document.createElement('input'); + input.setAttribute('type', 'file'); + input.addEventListener('change', (event) => { + [...input.files].forEach((f) => { + const reader = new FileReader(); + reader.addEventListener('load', () => { + editor.setValue( + JSON.stringify(JSON.parse(reader.result), null, 2) + ); + editor.moveCursorTo(0, 0); + runBlueprint(editor); + }); + reader.readAsText(f); + }); + }); + input.click(); + }; + + save.addEventListener('click', saveMethod); + open.addEventListener('click', openMethod); + + editor.commands.addCommand({ + name: 'Save Blueprint', + bindKey: { + win: 'Ctrl-S', + mac: 'Command-S', + }, + exec: (editor) => saveMethod(), + readOnly: false, + }); + + editor.commands.addCommand({ + name: 'Open Blueprint', + bindKey: { + win: 'Ctrl-O', + mac: 'Command-O', + }, + exec: (editor) => openMethod(), + readOnly: false, + }); }); diff --git a/packages/playground/website/builder/style.css b/packages/playground/website/builder/style.css index f9eba8e852..57a530590c 100644 --- a/packages/playground/website/builder/style.css +++ b/packages/playground/website/builder/style.css @@ -1,397 +1,406 @@ html { - background-color: #aaa; - font-family: sans-serif; + background-color: #aaa; + font-family: sans-serif; } -html, body { - overflow: hidden; +html, +body { + overflow: hidden; } -html, body, .playground-wrapper { - height: 100%; +html, +body, +.playground-wrapper { + height: 100%; } .playground-wrapper { - display: flex; - flex-direction: column; - padding: 10px; - box-sizing: border-box; - background-color: #1e2327; - box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.75); + display: flex; + flex-direction: column; + padding: 10px; + box-sizing: border-box; + background-color: #1e2327; + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.75); } body { - padding: 1em; - padding: 0; - box-sizing: border-box; - margin: 0; + padding: 1em; + padding: 0; + box-sizing: border-box; + margin: 0; } main { - display: flex; - flex-direction: row; - justify-content: stretch; - align-items: center; - flex: 1; + display: flex; + flex-direction: row; + justify-content: stretch; + align-items: center; + flex: 1; } .column { - display: flex; - flex: 1; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100%; - width: 100%; - justify-content: stretch; - align-items: stretch; - flex: 2; - max-width: 50%; - background-color: #24292e; - border: 1px solid #40464d;; - box-sizing: border-box; - position: relative; + display: flex; + flex: 1; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 100%; + justify-content: stretch; + align-items: stretch; + flex: 2; + max-width: 50%; + background-color: #24292e; + border: 1px solid #40464d; + box-sizing: border-box; + position: relative; } .column.editor { - flex: 1; + flex: 1; } .separator { - display: flex; - flex-direction: column; - justify-content: space-between; - align-items: center; - height: 100%; - width: 10px; + display: flex; + flex-direction: column; + justify-content: space-between; + align-items: center; + height: 100%; + width: 10px; } .frame-wrapper { - position: relative; + position: relative; } .frame-center { - width: 100%; - height: 100%; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + width: 100%; + height: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; } svg { - filter: brightness(0.333) contrast(2) brightness(3); - mix-blend-mode: lighten; + filter: brightness(0.333) contrast(2) brightness(3); + mix-blend-mode: lighten; } iframe { - position: absolute; - top: 0; - left: 0; - height: calc(100% / var(--zoom, 1)); - width: calc(100% / var(--zoom, 1)); - transform-origin: top left; - transform: scale(var(--zoom, 1)); - box-sizing: border-box; - border: 0; - background-color: rgba(0,0,0, 0.25); - transition: transform 0.5s linear, width 0.5s linear, height 0.5s linear; + position: absolute; + top: 0; + left: 0; + height: calc(100% / var(--zoom, 1)); + width: calc(100% / var(--zoom, 1)); + transform-origin: top left; + transform: scale(var(--zoom, 1)); + box-sizing: border-box; + border: 0; + background-color: rgba(0, 0, 0, 0.25); + transition: transform 0.5s linear, width 0.5s linear, height 0.5s linear; } .column.viewer { - overflow: hidden; + overflow: hidden; } button { - white-space: pre; - font-weight: bold; - background: none; - border: none; - cursor: pointer; - background-color: #ccc; - padding: 8px; - margin: 7px; - box-shadow: 0 0.25rem 0.5rem rgba(0,0,0,0.25); - user-select: none; - border-radius: 2px; + white-space: pre; + font-weight: bold; + background: none; + border: none; + cursor: pointer; + background-color: #ccc; + padding: 8px; + margin: 7px; + box-shadow: 0 0.25rem 0.5rem rgba(0, 0, 0, 0.25); + user-select: none; + border-radius: 2px; } button:active { - position: relative; - top: 1px; - left: 1px; - box-shadow: 0 0.125rem 0.25rem rgba(0,0,0,0.125); - filter: brightness(0.94); + position: relative; + top: 1px; + left: 1px; + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.125); + filter: brightness(0.94); } button:hover:not(:active) { - filter: brightness(1.06); + filter: brightness(1.06); } button.cta { - background-color: #eaaa00; + background-color: #eaaa00; } button.run { - background-image: url('./play-button.svg'); - background-size: auto 55%; - background-position: 0.325em center; - background-repeat: no-repeat; - padding-left: 1.85em; + background-image: url('./play-button.svg'); + background-size: auto 55%; + background-position: 0.325em center; + background-repeat: no-repeat; + padding-left: 1.85em; } button#open-ai { - background-image: url('./brain.svg'); - background-size: auto 75%; - background-position: 0.325em center; - background-repeat: no-repeat; - padding-left: 2.85em; + background-image: url('./brain.svg'); + background-size: auto 75%; + background-position: 0.325em center; + background-repeat: no-repeat; + padding-left: 2.85em; } button#save { - background-image: url('./save.svg'); - background-size: auto 60%; - background-position: center; - background-repeat: no-repeat; - width: 2.25rem; + background-image: url('./save.svg'); + background-size: auto 60%; + background-position: center; + background-repeat: no-repeat; + width: 2.25rem; } button#open { - background-image: url('./open.svg'); - background-size: auto 50%; - background-position: center; - background-repeat: no-repeat; - width: 2.25rem; + background-image: url('./open.svg'); + background-size: auto 50%; + background-position: center; + background-repeat: no-repeat; + width: 2.25rem; } button#new-tab { - background-image: url('./new-tab-button.svg'); - background-size: auto 55%; - background-position: calc(100% + -0.5em) center; - background-repeat: no-repeat; - padding-right: 2.1em; + background-image: url('./new-tab-button.svg'); + background-size: auto 55%; + background-position: calc(100% + -0.5em) center; + background-repeat: no-repeat; + padding-right: 2.1em; } -.jsontext, .frame-wrapper { - width: 100%; - height: 100%; - transition: height 0.25s ease-out, padding 0.25s ease-out, background-color 0.25s ease-in; +.jsontext, +.frame-wrapper { + width: 100%; + height: 100%; + transition: height 0.25s ease-out, padding 0.25s ease-out, + background-color 0.25s ease-in; } -.frame-wrapper:has( #error-output ) { - background-color: rgba(0, 0, 0, 0.25); - margin: 0; - padding: 1rem; - box-sizing: border-box; +.frame-wrapper:has(#error-output) { + background-color: rgba(0, 0, 0, 0.25); + margin: 0; + padding: 1rem; + box-sizing: border-box; } -.frame-wrapper:has( #error-output[srcdoc=""] ) { - background-color: rgba(255, 255, 255, 0.75); - height: 0%; - padding: 0rem; +.frame-wrapper:has(#error-output[srcdoc='']) { + background-color: rgba(255, 255, 255, 0.75); + height: 0%; + padding: 0rem; } .json-container { - word-break: break-all; - white-space: break-spaces; - padding-left: 30px; - height: 100%; + word-break: break-all; + white-space: break-spaces; + padding-left: 30px; + height: 100%; } .toolbar { - position: relative; - z-index: 1; - background-color: #40464d; - text-align: right; - display: flex; - flex-direction: row; - align-items: center; - justify-content: flex-end; + position: relative; + z-index: 1; + background-color: #40464d; + text-align: right; + display: flex; + flex-direction: row; + align-items: center; + justify-content: flex-end; } .toolbar span { - color: white; - font-size: 0.8rem; - font-style: italic; - white-space: pre; + color: white; + font-size: 0.8rem; + font-style: italic; + white-space: pre; } .toolbar svg, .toolbar span { - margin-left: 0.5rem; + margin-left: 0.5rem; } .toolbar svg { - display: none; + display: none; } -body[data-loading="true"] .toolbar svg { - display: initial; +body[data-loading='true'] .toolbar svg { + display: initial; } .toolbar span.spacer { - flex: 1; - display: inline-block; + flex: 1; + display: inline-block; } pre { - margin-bottom: 0; - margin-top: 10px; + margin-bottom: 0; + margin-top: 10px; } button { - transition: opacity 0.5s ease-in-out; - height: 2rem; + transition: opacity 0.5s ease-in-out; + height: 2rem; } button.zoom { - color: transparent; - background-repeat: no-repeat; - background-position: center; - background-size: 63%; - width: 2rem; + color: transparent; + background-repeat: no-repeat; + background-position: center; + background-size: 63%; + width: 2rem; } #zoom { - font-style: normal; - text-align: center; - width: 2.25rem; - margin: 0; + font-style: normal; + text-align: center; + width: 2.25rem; + margin: 0; } button#zoom-in { - background-image: url(./zoom-in.svg); + background-image: url(./zoom-in.svg); } button#zoom-out { - background-image: url(./zoom-out.svg); + background-image: url(./zoom-out.svg); } -[data-starting=true] button.run { - pointer-events: none; - filter: saturate(0.25); - opacity: 0.5; +[data-starting='true'] button.run { + pointer-events: none; + filter: saturate(0.25); + opacity: 0.5; } -[data-starting=true] button.run { - animation: button-pulse infinite alternate 0.5s; +[data-starting='true'] button.run { + animation: button-pulse infinite alternate 0.5s; } @keyframes button-pulse { - from { opacity: 1; } - to { opacity: 0.5; } + from { + opacity: 1; + } + to { + opacity: 0.5; + } } section#ai { - display: flex; - flex-direction: column; - height: 0rem; + display: flex; + flex-direction: column; + height: 0rem; overflow: hidden; transition: height 0.25s ease-in; } -body[data-show-ai=true] section#ai { - height: 35rem; +body[data-show-ai='true'] section#ai { + height: 35rem; } #prompt { - font-family: sans-serif; - font-size: 1.1rem; - background-color: #CCC; - width: 100%; - resize: none; - box-sizing: border-box; - margin: 0; - flex: 1; - padding: 1rem; - border-radius: 0; - border: 0; + font-family: sans-serif; + font-size: 1.1rem; + background-color: #ccc; + width: 100%; + resize: none; + box-sizing: border-box; + margin: 0; + flex: 1; + padding: 1rem; + border-radius: 0; + border: 0; } button.close { - background-image: url(./x.svg); - background-size: auto 55%; - background-position: center; - background-repeat: no-repeat; - width: 2.2rem; + background-image: url(./x.svg); + background-size: auto 55%; + background-position: center; + background-repeat: no-repeat; + width: 2.2rem; } section.fly-in { - position: absolute; - left: 0; - top:0; - height: 100%; - min-width: 20rem; - background-color: #1e2327; - border-right: 1px solid rgba(255, 255, 255, 0.25); - z-index: 10; - color: white; - padding: 20px; - box-sizing: border-box; - transition: transform 0.125s ease-out; + position: absolute; + left: 0; + top: 0; + height: 100%; + min-width: 20rem; + background-color: #1e2327; + border-right: 1px solid rgba(255, 255, 255, 0.25); + z-index: 10; + color: white; + padding: 20px; + box-sizing: border-box; + transition: transform 0.125s ease-out; } section.fly-in p { - margin: 0; + margin: 0; } section.fly-in ul { - padding: 0; - display: flex; - flex-direction: column; + padding: 0; + display: flex; + flex-direction: column; } section.fly-in ul li { - list-style: none; - display: flex; - flex-direction: row; - align-items: center; - justify-content: space-between; - height: 2rem; - white-space: pre; + list-style: none; + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: 2rem; + white-space: pre; } section.fly-in ul li span { - flex: 1; + flex: 1; } section.fly-in ul li button { - opacity: 0; - pointer-events: none; + opacity: 0; + pointer-events: none; } section.fly-in ul li:hover button { - opacity: 0.5; - visibility: initial; - pointer-events: initial; + opacity: 0.5; + visibility: initial; + pointer-events: initial; } section.fly-in ul li:hover button:hover { - opacity: 1; - filter: invert(1); + opacity: 1; + filter: invert(1); } section.fly-in ul li button { - border: 0; - background: transparent; - box-shadow: none; - filter: invert(1); - - background-size: auto 55%; - background-position: center; - background-repeat: no-repeat; - width: 1.25rem; + border: 0; + background: transparent; + box-shadow: none; + filter: invert(1); + + background-size: auto 55%; + background-position: center; + background-repeat: no-repeat; + width: 1.25rem; } section.fly-in ul li:hover button.rename { - background-image: url(./edit.svg); + background-image: url(./edit.svg); } section.fly-in ul li:hover button.download { - background-image: url(./download.svg); + background-image: url(./download.svg); } section.fly-in ul li:hover button.delete { - background-image: url(./x.svg); + background-image: url(./x.svg); } -body[data-menu=false] section.fly-in { - transform: translateX(-100%); -} \ No newline at end of file +body[data-menu='false'] section.fly-in { + transform: translateX(-100%); +} diff --git a/packages/playground/website/builder/x.svg b/packages/playground/website/builder/x.svg new file mode 100644 index 0000000000..f7fb5dbb77 --- /dev/null +++ b/packages/playground/website/builder/x.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/packages/playground/website/builder/zoom-in.svg b/packages/playground/website/builder/zoom-in.svg new file mode 100644 index 0000000000..9b8ea26541 --- /dev/null +++ b/packages/playground/website/builder/zoom-in.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/playground/website/builder/zoom-out.svg b/packages/playground/website/builder/zoom-out.svg new file mode 100644 index 0000000000..9759fcb7c1 --- /dev/null +++ b/packages/playground/website/builder/zoom-out.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file