Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify blocks edit page #33103

Merged
merged 4 commits into from
Feb 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 0 additions & 57 deletions apps/src/code-studio/initializeCodeMirror.js
Expand Up @@ -17,7 +17,6 @@ import 'codemirror/addon/lint/javascript-lint';
import 'codemirror/mode/xml/xml';
import 'codemirror/mode/javascript/javascript';
import './vendor/codemirror.inline-attach';
import jsonic from 'jsonic';
import {JSHINT} from 'jshint';

import React from 'react';
Expand All @@ -30,9 +29,6 @@ CodeMirrorSpellChecker({
codeMirrorInstance: CodeMirror
});

const VALID_COLOR = 'black';
const INVALID_COLOR = '#d00';

/**
* initializeCodeMirror replaces a textarea on the page with a full-featured
* CodeMirror editor.
Expand Down Expand Up @@ -135,56 +131,3 @@ function initializeCodeMirror(target, mode, options = {}) {
return editor;
}
module.exports = initializeCodeMirror;

module.exports.initializeCodeMirrorForJson = function(
textAreaId,
{validationDivId, onBlur, onChange}
) {
// Leniently validate and fix up custom block JSON using jsonic
const textAreaEl = document.getElementById(textAreaId);
if (textAreaEl) {
const jsonValidationDiv = validationDivId
? $(`#${validationDivId}`)
: $(
textAreaEl.parentNode.insertBefore(
document.createElement('div'),
textAreaEl.nextSibling
)
);
const showErrors = fn => arg => {
try {
if (fn) {
fn(arg);
}
jsonValidationDiv.text('JSON appears valid.');
jsonValidationDiv.css('color', VALID_COLOR);
} catch (err) {
jsonValidationDiv.text(err.toString());
jsonValidationDiv.css('color', INVALID_COLOR);
}
};
const fixupJson = showErrors(() => {
if (jsonEditor.getValue().trim()) {
let blocks = jsonic(jsonEditor.getValue().trim());
if (onBlur) {
blocks = onBlur(blocks);
}
if (onChange) {
onChange(jsonEditor);
}
jsonEditor.setValue(JSON.stringify(blocks, null, 2));
} else {
jsonEditor.setValue('');
}
});

const jsonEditor = initializeCodeMirror(
textAreaId,
'application/json',
showErrors(onChange)
);
jsonEditor.on('blur', fixupJson);
fixupJson();
return fixupJson;
}
};
95 changes: 72 additions & 23 deletions apps/src/sites/studio/pages/blocks/edit.js
@@ -1,9 +1,7 @@
import * as codegen from '@cdo/apps/lib/tools/jsinterpreter/codegen';
import $ from 'jquery';
import assetUrl from '@cdo/apps/code-studio/assetUrl';
import initializeCodeMirror, {
initializeCodeMirrorForJson
} from '@cdo/apps/code-studio/initializeCodeMirror';
import initializeCodeMirror from '@cdo/apps/code-studio/initializeCodeMirror';
import jsonic from 'jsonic';
import {parseElement} from '@cdo/apps/xml';
import {installCustomBlocks} from '@cdo/apps/block_utils';
Expand All @@ -16,7 +14,10 @@ import animationListModule, {
import defaultSprites from '@cdo/apps/p5lab/spritelab/defaultSprites.json';
import {getStore, registerReducers} from '@cdo/apps/redux';

let poolField, nameField, helperEditor;
const VALID_COLOR = 'black';
const INVALID_COLOR = '#d00';

let poolField, nameField, helperEditor, configEditor, validationDiv;

$(document).ready(() => {
registerReducers({animationList: animationListModule});
Expand All @@ -30,38 +31,88 @@ $(document).ready(() => {
typeHints: true
});

let submitButton = document.querySelector('#block_submit');
const fixupJson = initializeCodeMirrorForJson('block_config', {onChange});
helperEditor = initializeCodeMirror('block_helper_code', 'javascript', {
callback: fixupJson,
onUpdateLinting: (_, errors) => {
if (errors.length) {
submitButton.setAttribute('disabled', 'disabled');
} else {
submitButton.removeAttribute('disabled');
}
}
const blockConfigElement = document.getElementById('block_config');

// Pretty print the config
let blocks = blockConfigElement.value;
if (blocks) {
blockConfigElement.value = JSON.stringify(JSON.parse(blocks), null, 2);
}

validationDiv = $(
blockConfigElement.parentNode.insertBefore(
document.createElement('div'),
blockConfigElement.nextSibling
)
);

const helperCodeElement = document.getElementById('block_helper_code');
configEditor = initializeCodeMirror(blockConfigElement, 'application/json', {
callback: validateBlockConfig,
onUpdateLinting: onUpdateLinting
});
poolField.addEventListener('change', fixupJson);

helperEditor = initializeCodeMirror(helperCodeElement, 'javascript', {
callback: _ => validateBlockConfig(),
onUpdateLinting: onUpdateLinting
});
poolField.addEventListener('change', updateBlockPreview);

if (blocks) {
updateBlockPreview();
}

$('.alert.alert-success')
.delay(5000)
.fadeOut(1000);
});

let config;
function onChange(editor) {
config = editor.getValue();
function onUpdateLinting(_, errors) {
const submitButton = document.querySelector('#block_submit');
if (errors.length) {
submitButton.setAttribute('disabled', 'disabled');
} else {
submitButton.removeAttribute('disabled');
}
}

const parsedConfig = jsonic(config);
function validateBlockConfig(editor) {
try {
if (editor) {
JSON.parse(editor.getValue());
}
updateBlockPreview();
validationDiv.text('Config and helper code appear valid.');
validationDiv.css('color', VALID_COLOR);
} catch (err) {
validationDiv.text(err.toString());
validationDiv.css('color', INVALID_COLOR);
}
}

function getBlockName(name, pool) {
if (!pool || pool === 'GamelabJr') {
pool = 'gamelab';
}
return `${pool}_${name}`;
}

function updateBlockPreview() {
const parsedConfig = jsonic(configEditor.getValue());

// Only Dancelab and Spritelab use customInputTypes.
const customInputTypes =
poolField.value === 'Dancelab'
? dancelabCustomInputTypes
: spritelabCustomInputTypes;

const blocksInstalled = installCustomBlocks({
const blockName = getBlockName(
parsedConfig.func || parsedConfig.name,
poolField.value
);
nameField.value = blockName;
// Calling this function just so that we can catch and show errors (if any)
installCustomBlocks({
blockly: Blockly,
blockDefinitions: [
{
Expand All @@ -74,8 +125,6 @@ function onChange(editor) {
],
customInputTypes
});
const blockName = Object.values(blocksInstalled)[0][0];
nameField.value = blockName;
const blocksDom = parseElement(`<block type="${blockName}" />`);
Blockly.mainBlockSpace.clear();
Blockly.Xml.domToBlockSpace(Blockly.mainBlockSpace, blocksDom);
Expand Down
16 changes: 1 addition & 15 deletions apps/src/sites/studio/pages/levels/editors/_gamelab.js
@@ -1,8 +1,6 @@
/** @file JavaScript run only on the gamelab level edit page. */
import $ from 'jquery';
import initializeCodeMirror, {
initializeCodeMirrorForJson
} from '@cdo/apps/code-studio/initializeCodeMirror';
import initializeCodeMirror from '@cdo/apps/code-studio/initializeCodeMirror';
import {throwIfSerializedAnimationListIsInvalid} from '@cdo/apps/p5lab/shapes';

const VALID_COLOR = 'black';
Expand Down Expand Up @@ -37,18 +35,6 @@ $(document).ready(function() {
}
});

// Leniently validate and fix up custom block JSON using jsonic
if (document.getElementById('level_custom_blocks')) {
initializeCodeMirrorForJson('level_custom_blocks', {
validationDiv: 'custom-blocks-validation',
onBlur(blocks) {
if (!Array.isArray(blocks)) {
return [blocks];
}
return blocks;
}
});
}
if (document.getElementById('level_custom_helper_library')) {
initializeCodeMirror('level_custom_helper_library', 'javascript');
}
Expand Down