diff --git a/extern/util/static.js b/extern/util/static.js index 68ecfe6f32..70b6a062e7 100644 --- a/extern/util/static.js +++ b/extern/util/static.js @@ -68,6 +68,7 @@ EntryStatic.getAllBlocks = function () { 'repeat_inf', 'repeat_while_true', 'stop_repeat', + 'continue_repeat', '_if', 'if_else', 'wait_until_true', @@ -486,6 +487,10 @@ EntryStatic.getAllBlocks = function () { 'get_social_disaster_guideline', 'count_safety_accident_guideline', 'get_safety_accident_guideline', + 'disaster_alert_title', + 'count_disaster_alert', + 'get_disaster_alert', + 'check_disaster_alert', ], }, { diff --git a/extern/util/static_mini.js b/extern/util/static_mini.js index d41852a842..66518459ef 100644 --- a/extern/util/static_mini.js +++ b/extern/util/static_mini.js @@ -158,6 +158,7 @@ EntryStatic.getAllBlocks = function () { 'repeat_inf', 'repeat_while_true', 'stop_repeat', + 'continue_repeat', '_if', 'if_else', 'wait_until_true', diff --git a/images/hardware/disasterAlert.png b/images/hardware/disasterAlert.png new file mode 100644 index 0000000000..82ae56706c Binary files /dev/null and b/images/hardware/disasterAlert.png differ diff --git a/src/class/Expansion.js b/src/class/Expansion.js index 389b73b81e..7a6931d328 100644 --- a/src/class/Expansion.js +++ b/src/class/Expansion.js @@ -4,6 +4,7 @@ import '../playground/blocks/block_expansion_festival'; import '../playground/blocks/block_expansion_behaviorconduct_disaster'; import '../playground/blocks/block_expansion_behaviorconduct_lifesafety'; import '../playground/blocks/block_expansion_emergencyActionGuidelines'; +import '../playground/blocks/block_expansion_disasterAlert'; export default class Expansion { constructor(playground) { diff --git a/src/class/container.js b/src/class/container.js index e536d711e7..a8d01b7066 100644 --- a/src/class/container.js +++ b/src/class/container.js @@ -196,6 +196,9 @@ Entry.Container = class Container { border: 'solid 1px #728997', }, onDragActionChange: (isDragging, key) => { + if (!Entry.objectEditable) { + return; + } Entry.ContextMenu.hide(); if (isDragging) { this.selectedObject.setObjectFold(isDragging, true); @@ -207,6 +210,9 @@ Entry.Container = class Container { this.isObjectDragging = isDragging; }, onChangeList: (newIndex, oldIndex) => { + if (!Entry.objectEditable) { + return; + } if (newIndex !== oldIndex) { Entry.do('objectReorder', newIndex, oldIndex); } diff --git a/src/class/hw_lite.ts b/src/class/hw_lite.ts index 67be55ab15..dd5cdfad8e 100644 --- a/src/class/hw_lite.ts +++ b/src/class/hw_lite.ts @@ -265,7 +265,7 @@ export default class HardwareLite { } setFlasher() { - if (this.hwModule.firmwareFlash) { + if (this.hwModule.supportFirmwareFlash) { this.flasher = new WebUsbFlasher(); } } diff --git a/src/class/object.js b/src/class/object.js index ad509735fe..e8b091f540 100644 --- a/src/class/object.js +++ b/src/class/object.js @@ -932,8 +932,12 @@ Entry.EntryObject = class { this.updateCoordinateView(true); this.updateRotationView(true); - Entry.addEventListener('run', this.setDisabled); - Entry.addEventListener('dispatchEventDidToggleStop', this.setEnabled); + if (!Entry.objectEditable) { + this.setDisabled(); + } else { + Entry.addEventListener('run', this.setDisabled); + Entry.addEventListener('dispatchEventDidToggleStop', this.setEnabled); + } return this.view_; } @@ -953,33 +957,40 @@ Entry.EntryObject = class { this.rotateModeAView_ = rotateModeAView; rotationMethodWrapper.appendChild(rotateModeAView); rotationMethodWrapper.appendChild(rotateModeAView); - rotateModeAView.bindOnClick( - this._whenRotateEditable(() => { - Entry.do('objectUpdateRotateMethod', this.id, 'free'); - }, this) - ); + + if (Entry.objectEditable) { + rotateModeAView.bindOnClick( + this._whenRotateEditable(() => { + Entry.do('objectUpdateRotateMethod', this.id, 'free'); + }, this) + ); + } const rotateModeBView = Entry.createElement('span').addClass( 'entryObjectRotateModeWorkspace entryObjectRotateModeBWorkspace' ); this.rotateModeBView_ = rotateModeBView; rotationMethodWrapper.appendChild(rotateModeBView); - rotateModeBView.bindOnClick( - this._whenRotateEditable(() => { - Entry.do('objectUpdateRotateMethod', this.id, 'vertical'); - }, this) - ); + if (Entry.objectEditable) { + rotateModeBView.bindOnClick( + this._whenRotateEditable(() => { + Entry.do('objectUpdateRotateMethod', this.id, 'vertical'); + }, this) + ); + } const rotateModeCView = Entry.createElement('span').addClass( 'entryObjectRotateModeWorkspace entryObjectRotateModeCWorkspace' ); this.rotateModeCView_ = rotateModeCView; rotationMethodWrapper.appendChild(rotateModeCView); - rotateModeCView.bindOnClick( - this._whenRotateEditable(() => { - Entry.do('objectUpdateRotateMethod', this.id, 'none'); - }, this) - ); + if (Entry.objectEditable) { + rotateModeCView.bindOnClick( + this._whenRotateEditable(() => { + Entry.do('objectUpdateRotateMethod', this.id, 'none'); + }, this) + ); + } return rotationMethodWrapper; } @@ -1395,7 +1406,6 @@ Entry.EntryObject = class { ); $(objectView).on('dragstart', (e) => { - // e.originalEvent.dataTransfer.setDragImage(canvas, 25, 25); e.originalEvent.dataTransfer.setData('text', objectId); }); const fragment = document.createDocumentFragment(); diff --git a/src/playground/block_entry.js b/src/playground/block_entry.js index 2fc1f5f59c..dd62a9a4ad 100644 --- a/src/playground/block_entry.js +++ b/src/playground/block_entry.js @@ -2709,11 +2709,14 @@ function getBlocks() { const sounds = sprite.parent.sounds; const isExist = Entry.isExist(soundId, 'id', sounds); if (isExist) { + const duration = Math.floor( + (sound.duration * 1000) / Entry.playbackRateValue + ); const instance = Entry.Utils.playSound(soundId); Entry.Utils.addSoundInstances(instance); setTimeout(() => { script.playState = 0; - }, sound.duration * 1000); + }, duration); } return script; } else if (script.playState === 1) { diff --git a/src/playground/blocks/block_calc.js b/src/playground/blocks/block_calc.js index 57c86b93b3..4323004008 100644 --- a/src/playground/blocks/block_calc.js +++ b/src/playground/blocks/block_calc.js @@ -2242,9 +2242,7 @@ module.exports = { class: 'calc_string', isNotFor: [], func(sprite, script) { - const oldWord = script - .getStringValue('OLD_WORD', script) - .replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const oldWord = script.getStringValue('OLD_WORD', script); const newWord = script.getStringValue('NEW_WORD', script); const originalString = script.getStringValue('STRING', script); return originalString.split(oldWord).join(newWord); diff --git a/src/playground/blocks/block_expansion_disasterAlert.js b/src/playground/blocks/block_expansion_disasterAlert.js new file mode 100644 index 0000000000..e4e6fc738d --- /dev/null +++ b/src/playground/blocks/block_expansion_disasterAlert.js @@ -0,0 +1,244 @@ +'use strict'; + +const PromiseManager = require('../../core/promiseManager'); +const { callApi } = require('../../util/common'); + +Entry.EXPANSION_BLOCK.disasterAlert = { + name: 'disasterAlert', + imageName: 'disasterAlert.png', + title: { + ko: '재난문자', + en: 'Disaster alert', + }, + titleKey: 'template.disaster_alert_title_text', + description: Lang.Msgs.expansion_disasterAlert_description, + descriptionKey: 'Msgs.expansion_disasterAlert_description', + isInitialized: false, + init() { + if (this.isInitialized) { + return; + } + Entry.EXPANSION_BLOCK.disasterAlert.isInitialized = true; + }, + api: '/api/expansionBlock/disasterAlert', + apiType: '01', +}; + +const EMERGENCY_CATEGORY_MAP = { + info: Lang.Blocks.disasterAlertTypeInfo, + exigency: Lang.Blocks.disasterAlertTypeExigency, + urgency: Lang.Blocks.disasterAlertTypeUrgency, +}; + +const getDisasterAlert = (params, defaultValue) => { + const now = new Date(); + const hour = now.getHours(); + const day = now.getDay(); + const key = `disasterAlert-${day}-${hour}`; + const promiseManager = new PromiseManager(); + const job = promiseManager + // eslint-disable-next-line new-cap + .Promise((resolve) => { + callApi(key, { + url: `${Entry.EXPANSION_BLOCK.disasterAlert.api}`, + }) + .then((result) => { + if (result) { + let items = result?.data?.body || []; + const category = EMERGENCY_CATEGORY_MAP?.[params?.category]; + if (category) { + items = items.filter((item) => item.EMRG_STEP_NM === category); + } + switch (params.command) { + case 'count': + return resolve(items?.length || defaultValue || 0); + case 'get': + const result = items?.[params?.index - 1]?.[params.option]; + if (!result) { + return resolve(defaultValue); + } + if (params?.option === 'REG_YMD') { + return resolve(new Date(result).toLocaleString()); + } + return resolve(result); + case 'exist': + return resolve(items?.length > 0); + default: + return resolve(defaultValue); + } + } + return resolve(defaultValue); + }) + .catch(() => resolve(defaultValue)); + }) + .catch(() => defaultValue); + + return job; +}; + +Entry.EXPANSION_BLOCK.disasterAlert.getBlocks = function () { + // 전체, 안전안내, 긴급재난, 위급재난 + const DisasterAlertCategory = { + type: 'Dropdown', + options: [ + [Lang.Blocks.disasterAlertTypeAll, 'all'], + [Lang.Blocks.disasterAlertTypeInfo, 'info'], + [Lang.Blocks.disasterAlertTypeExigency, 'exigency'], + [Lang.Blocks.disasterAlertTypeUrgency, 'urgency'], + ], + value: 'all', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.EXPANSION, + arrowColor: EntryStatic.colorSet.common.WHITE, + }; + // 내용, 수신지역, 긴급단계, 재해구분, 생성일시 + const DisasterAlertOptions = { + type: 'Dropdown', + options: [ + [Lang.Blocks.disasterAlertContents, 'MSG_CN'], + [Lang.Blocks.disasterAlertRegeion, 'RCPTN_RGN_NM'], + [Lang.Blocks.disasterAlertStep, 'EMRG_STEP_NM'], + [Lang.Blocks.disasterAlertDisaster, 'DST_SE_NM'], + [Lang.Blocks.disasterAlertRegisterDate, 'REG_YMD'], + ], + value: 'MSG_CN', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.EXPANSION, + arrowColor: EntryStatic.colorSet.common.WHITE, + }; + + return { + disaster_alert_title: { + template: '%1', + skeleton: 'basic_text', + color: EntryStatic.colorSet.common.TRANSPARENT, + params: [ + { + type: 'Text', + text: Lang.template.disaster_alert_title_text, + color: EntryStatic.colorSet.common.TEXT, + align: 'center', + }, + ], + def: { + type: 'disaster_alert_title', + }, + class: 'disasterAlert', + isNotFor: ['disasterAlert'], + events: {}, + }, + count_disaster_alert: { + color: EntryStatic.colorSet.block.default.EXPANSION, + outerLine: EntryStatic.colorSet.block.darken.EXPANSION, + skeleton: 'basic_string_field', + statements: [], + params: [DisasterAlertCategory], + events: {}, + def: { + params: [DisasterAlertCategory.value], + type: 'count_disaster_alert', + }, + pyHelpDef: { + params: ['A&value'], + type: 'count_disaster_alert', + }, + paramsKeyMap: { + CATEGORY: 0, + }, + class: 'disasterAlert', + isNotFor: ['disasterAlert'], + func(sprite, script) { + const category = script.getField('CATEGORY', script); + return getDisasterAlert( + { + command: 'count', + category, + }, + 0 + ); + }, + syntax: { + js: [], + py: [], + }, + }, + get_disaster_alert: { + color: EntryStatic.colorSet.block.default.EXPANSION, + outerLine: EntryStatic.colorSet.block.darken.EXPANSION, + skeleton: 'basic_string_field', + statements: [], + params: [ + DisasterAlertCategory, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + DisasterAlertOptions, + ], + events: {}, + def: { + params: [DisasterAlertCategory.value, 1, DisasterAlertOptions.value], + type: 'get_disaster_alert', + }, + pyHelpDef: { + params: ['A&value', 'B&value', 'C&value'], + type: 'get_disaster_alert', + }, + paramsKeyMap: { + CATEGORY: 0, + NUMBER: 1, + OPTION: 2, + }, + class: 'disasterAlert', + isNotFor: ['disasterAlert'], + func(sprite, script) { + const number = script.getStringValue('NUMBER', script); + const category = script.getField('CATEGORY', script); + const option = script.getField('OPTION', script); + return getDisasterAlert({ + command: 'get', + category, + index: number, + option, + }); + }, + syntax: { + js: [], + py: [], + }, + }, + check_disaster_alert: { + color: EntryStatic.colorSet.block.default.EXPANSION, + outerLine: EntryStatic.colorSet.block.darken.EXPANSION, + skeleton: 'basic_boolean_field', + statements: [], + params: [DisasterAlertCategory], + events: {}, + def: { + params: [DisasterAlertCategory.value], + type: 'check_disaster_alert', + }, + pyHelpDef: { + params: ['B&value', null], + type: 'check_disaster_alert', + }, + paramsKeyMap: { + CATEGORY: 0, + }, + class: 'disasterAlert', + isNotFor: ['disasterAlert'], + async func(sprite, script) { + const category = script.getField('CATEGORY', script); + return getDisasterAlert({ + command: 'exist', + category, + }); + }, + syntax: { + js: [], + py: [], + }, + }, + }; +}; diff --git a/src/playground/blocks/block_flow.js b/src/playground/blocks/block_flow.js index 3b5385517a..52a0ef9f8e 100644 --- a/src/playground/blocks/block_flow.js +++ b/src/playground/blocks/block_flow.js @@ -326,6 +326,30 @@ module.exports = { }, syntax: { js: [], py: ['break'] }, }, + continue_repeat: { + color: EntryStatic.colorSet.block.default.FLOW, + outerLine: EntryStatic.colorSet.block.darken.FLOW, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Indicator', + img: 'block_icon/flow_icon.svg', + size: 11, + }, + ], + events: {}, + def: { + params: [null], + type: 'continue_repeat', + }, + class: 'repeat', + isNotFor: [], + func(sprite, script) { + return this.executor.continueLoop(); + }, + syntax: { js: [], py: ['continue'] }, + }, _if: { color: EntryStatic.colorSet.block.default.FLOW, outerLine: EntryStatic.colorSet.block.darken.FLOW, diff --git a/src/playground/blocks/block_sound.js b/src/playground/blocks/block_sound.js index 0913fbd9c9..57fd60651b 100644 --- a/src/playground/blocks/block_sound.js +++ b/src/playground/blocks/block_sound.js @@ -293,9 +293,12 @@ module.exports = { script.playState = 1; const instance = Entry.Utils.playSound(sound.id); Entry.Utils.addSoundInstances(instance, sprite); + const duration = Math.floor( + (sound.duration * 1000) / Entry.playbackRateValue + ); setTimeout(() => { script.playState = 0; - }, sound.duration * 1000); + }, duration); } return script; } else if (script.playState == 1) { diff --git a/src/playground/blocks/hardware/block_ITPLE_board.js b/src/playground/blocks/hardware/block_ITPLE_board.js index da2134a836..0ed2b6b0e5 100644 --- a/src/playground/blocks/hardware/block_ITPLE_board.js +++ b/src/playground/blocks/hardware/block_ITPLE_board.js @@ -34,9 +34,11 @@ Entry.ITPLE = { PULSEIN: 6, ULTRASONIC: 7, TIMER: 8, + NEOPIXELINIT: 9, + NEOPIXELCOLOR: 10, }, toneTable: { - '0': 0, + 0: 0, C: 1, CS: 2, D: 3, @@ -51,25 +53,36 @@ Entry.ITPLE = { B: 12, }, toneMap: { - '1': [33, 65, 131, 262, 523, 1046, 2093, 4186], - '2': [35, 69, 139, 277, 554, 1109, 2217, 4435], - '3': [37, 73, 147, 294, 587, 1175, 2349, 4699], - '4': [39, 78, 156, 311, 622, 1245, 2849, 4978], - '5': [41, 82, 165, 330, 659, 1319, 2637, 5274], - '6': [44, 87, 175, 349, 698, 1397, 2794, 5588], - '7': [46, 92, 185, 370, 740, 1480, 2960, 5920], - '8': [49, 98, 196, 392, 784, 1568, 3136, 6272], - '9': [52, 104, 208, 415, 831, 1661, 3322, 6645], - '10': [55, 110, 220, 440, 880, 1760, 3520, 7040], - '11': [58, 117, 233, 466, 932, 1865, 3729, 7459], - '12': [62, 123, 247, 494, 988, 1976, 3951, 7902], + 1: [33, 65, 131, 262, 523, 1046, 2093, 4186], + 2: [35, 69, 139, 277, 554, 1109, 2217, 4435], + 3: [37, 73, 147, 294, 587, 1175, 2349, 4699], + 4: [39, 78, 156, 311, 622, 1245, 2849, 4978], + 5: [41, 82, 165, 330, 659, 1319, 2637, 5274], + 6: [44, 87, 175, 349, 698, 1397, 2794, 5588], + 7: [46, 92, 185, 370, 740, 1480, 2960, 5920], + 8: [49, 98, 196, 392, 784, 1568, 3136, 6272], + 9: [52, 104, 208, 415, 831, 1661, 3322, 6645], + 10: [55, 110, 220, 440, 880, 1760, 3520, 7040], + 11: [58, 117, 233, 466, 932, 1865, 3729, 7459], + 12: [62, 123, 247, 494, 988, 1976, 3951, 7902], + }, + duration: { + TIME_1ms: 1, + TIME_5ms: 5, + TIME_10ms: 10, + TIME_20ms: 20, + TIME_50ms: 50, + TIME_100ms: 100, + TIME_200ms: 200, + TIME_500ms: 500, + TIME_600ms: 600, }, highList: ['high', '1', 'on'], lowList: ['low', '0', 'off'], BlockState: {}, }; -Entry.ITPLE.setLanguage = function() { +Entry.ITPLE.setLanguage = function () { return { ko: { template: { @@ -82,7 +95,12 @@ Entry.ITPLE.setLanguage = function() { ITPLE_set_tone: '디지털 %1 번 핀의 버저를 %2 %3 음으로 %4 초 연주하기 %5', ITPLE_get_digital: '디지털 %1 번 센서값', ITPLE_set_motor_direction: '%1 모터 %2 방향으로 정하기 %3', - ITPLE_set_motor_speed: '%1 모터 %2 속도로 정하기 %3', + ITPLE_set_motor_speed_old: '(V1)%1 모터 %2 속도로 정하기 %3', + ITPLE_set_motor_speed_new: '(V2)%1 모터 %2 속도로 정하기 %3', + ITPLE_set_servo: '디지털 %1 번 핀의 서보모터를 %2 도로 정하기 %3', + ITPLE_set_neopixelinit: '디지털 %1 번 핀에 연결된 %2 개의 네오픽셀 LED 사용하기 %3', + ITPLE_set_neopixel: + '디지털 %1 번 핀에 연결된 %2 번째 네오픽셀 LED를 R: %3 , G: %4 , B: %5 색으로 켜기 %6', }, }, en: { @@ -96,7 +114,12 @@ Entry.ITPLE.setLanguage = function() { ITPLE_set_tone: 'Play tone pin %1 on note %2 octave %3 beat %4 %5', ITPLE_get_digital: 'Digital %1 Sensor value', ITPLE_set_motor_direction: '%1 motor %2 direction %3', - ITPLE_set_motor_speed: '%1 motor %2 speed %3', + ITPLE_set_motor_speed_old: '(old) %1 motor %2 speed %3', + ITPLE_set_motor_speed_new: '(new) %1 motor %2 speed %3', + ITPLE_set_servo: 'Set servo motor of pin %1 to %2 degree %3', + ITPLE_set_neopixelinit: 'Use %2 NeoPixel LEDs connected to digital pin %1 %3', + ITPLE_set_neopixel: + 'Turn on the %2nd NeoPixel LED connected to digital pin %1 with color R: %3, G: %4, B: %5 %6', }, }, }; @@ -112,11 +135,15 @@ Entry.ITPLE.blockMenuBlocks = [ 'ITPLE_digital_pwm', 'ITPLE_set_tone', 'ITPLE_set_motor_direction', - 'ITPLE_set_motor_speed', + 'ITPLE_set_motor_speed_old', + 'ITPLE_set_motor_speed_new', + 'ITPLE_set_servo', + 'ITPLE_set_neopixelinit', + 'ITPLE_set_neopixel', ]; //region ITPLE 보드 -Entry.ITPLE.getBlocks = function() { +Entry.ITPLE.getBlocks = function () { return { ITPLE_analog_list: { color: EntryStatic.colorSet.block.default.HARDWARE, @@ -656,7 +683,7 @@ Entry.ITPLE.getBlocks = function() { params: [ { type: 'arduino_get_port_number', - params: [5], + params: [10], }, { type: 'arduino_get_digital_toggle', @@ -1199,7 +1226,7 @@ Entry.ITPLE.getBlocks = function() { PORT: 0, VALUE: 1, }, - class: 'ITPLE', + class: 'ITPLE_motor', isNotFor: ['ITPLE'], func(sprite, script) { const port = script.getNumberValue('PORT'); @@ -1244,7 +1271,7 @@ Entry.ITPLE.getBlocks = function() { ], }, }, - ITPLE_set_motor_speed: { + ITPLE_set_motor_speed_old: { color: EntryStatic.colorSet.block.default.HARDWARE, outerLine: EntryStatic.colorSet.block.darken.HARDWARE, skeleton: 'basic', @@ -1282,13 +1309,95 @@ Entry.ITPLE.getBlocks = function() { }, null, ], - type: 'ITPLE_set_motor_speed', + type: 'ITPLE_set_motor_speed_old', }, paramsKeyMap: { PORT: 0, VALUE: 1, }, - class: 'ITPLE', + class: 'ITPLE_motor', + isNotFor: ['ITPLE'], + func(sprite, script) { + const port = script.getNumberValue('PORT'); + let value = script.getNumberValue('VALUE'); + value = Math.round(value); + value = Math.max(value, 0); + value = Math.min(value, 255); + if (!Entry.hw.sendQueue.SET) { + Entry.hw.sendQueue.SET = {}; + } + Entry.hw.sendQueue.SET[port] = { + type: Entry.ITPLE.sensorTypes.PWM, + data: value, + time: new Date().getTime(), + }; + return script.callReturn(); + }, + syntax: { + js: [], + py: [ + { + syntax: 'Arduino.analogWrite(%1, %2)', + textParams: [ + { + type: 'Block', + accept: 'string', + }, + { + type: 'Block', + accept: 'string', + }, + ], + }, + ], + }, + }, + + ITPLE_set_motor_speed_new: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Dropdown', + options: [ + ['왼쪽', '5'], + ['오른쪽', '6'], + ], + value: '5', + fontSize: 11, + bgColor: EntryStatic.colorSet.block.darken.HARDWARE, + arrowColor: EntryStatic.colorSet.arrow.default.HARDWARE, + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Indicator', + img: 'block_icon/hardware_icon.svg', + size: 12, + }, + ], + events: {}, + def: { + params: [ + '5', + { + type: 'text', + params: ['255'], + }, + null, + ], + type: 'ITPLE_set_motor_speed_new', + }, + paramsKeyMap: { + PORT: 0, + VALUE: 1, + }, + class: 'ITPLE_motor', isNotFor: ['ITPLE'], func(sprite, script) { const port = script.getNumberValue('PORT'); @@ -1325,6 +1434,83 @@ Entry.ITPLE.getBlocks = function() { ], }, }, + ITPLE_set_servo: { + color: EntryStatic.colorSet.block.default.HARDWARE, + outerLine: EntryStatic.colorSet.block.darken.HARDWARE, + skeleton: 'basic', + statements: [], + params: [ + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Block', + accept: 'string', + defaultType: 'number', + }, + { + type: 'Indicator', + img: 'block_icon/hardware_icon.svg', + size: 12, + }, + ], + events: {}, + def: { + params: [ + '9', + { + type: 'text', + params: ['180'], + }, + null, + ], + type: 'ITPLE_set_servo', + }, + paramsKeyMap: { + PORT: 0, + VALUE: 1, + }, + class: 'ITPLE_motor', + isNotFor: ['ITPLE'], + func(sprite, script) { + const sq = Entry.hw.sendQueue; + const port = script.getNumberValue('PORT', script); + let value = script.getNumberValue('VALUE', script); + value = Math.min(180, value); + value = Math.max(0, value); + + if (!sq.SET) { + sq.SET = {}; + } + sq.SET[port] = { + type: Entry.ITPLE.sensorTypes.SERVO_PIN, + data: value, + time: new Date().getTime(), + }; + + return script.callReturn(); + }, + syntax: { + js: [], + py: [ + { + syntax: 'Arduino.servomotorWrite(%1, %2)', + textParams: [ + { + type: 'Block', + accept: 'string', + }, + { + type: 'Block', + accept: 'string', + }, + ], + }, + ], + }, + }, }; }; //endregion ITPLE 아두이노 확장모드 diff --git a/src/playground/blocks/hardwareLite/block_microbit2_lite.js b/src/playground/blocks/hardwareLite/block_microbit2_lite.js index 7e140e9dae..f902fcf2ca 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2_lite.js @@ -191,6 +191,7 @@ const EVENT_INTERVAL = 150; this.id = '220301'; this.url = 'http://microbit.org/ko/'; this.imageName = 'microbit2lite.png'; + this.supportFirmwareFlash = true; this.title = { en: 'Microbit V2', ko: '마이크로비트 V2', diff --git a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js index ed55e4de10..675734a7d4 100644 --- a/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js +++ b/src/playground/blocks/hardwareLite/block_microbit2ble_lite.js @@ -19,7 +19,7 @@ const convertPresetImageToLedState = (preset) => { Entry.Microbit2BleLite = new (class Microbit2LiteBle { constructor() { this.webapiType = 'ble'; - this.firmwareFlash = true; + this.supportFirmwareFlash = true; this.bluetoothInfo = { filters: [ { diff --git a/src/playground/executors.js b/src/playground/executors.js index 412522a1a3..eb41aa2a97 100644 --- a/src/playground/executors.js +++ b/src/playground/executors.js @@ -224,6 +224,20 @@ class Executor { return Entry.STATIC.PASS; } + async continueLoop() { + if (this._callStack.length) { + this.scope = this._callStack.pop(); + } + while (this._callStack.length) { + const schema = Entry.block[this.scope.block.type]; + if (schema.class === 'repeat') { + continue; + } + this.scope = this._callStack.pop(); + } + return Entry.STATIC.CONTINUE; + } + end() { Entry.dispatchEvent('blockExecuteEnd', this.scope.block && this.scope.block.view); this.scope.block = null; diff --git a/src/playground/workspace.js b/src/playground/workspace.js index 246727cfb9..410dfebe3e 100644 --- a/src/playground/workspace.js +++ b/src/playground/workspace.js @@ -199,11 +199,11 @@ Entry.Workspace = class Workspace { Entry.modal.confirm(alertMessage.message).then((result) => { if (result) { Entry.variableContainer.removeNotPythonSupportedFunction(); - Entry.expansion.banExpansionBlocks(Entry.expansionBlocks); - Entry.aiUtilize.banAIUtilizeBlocks(Entry.aiUtilizeBlocks); - Entry.hwLite.banClassAllHardwareLite(); - Entry.playground.dataTable.removeAllBlocks(); - Entry.aiLearning.removeAllBlocks(); + Entry.expansion?.banExpansionBlocks(Entry.expansionBlocks); + Entry.aiUtilize?.banAIUtilizeBlocks(Entry.aiUtilizeBlocks); + Entry.hwLite?.banClassAllHardwareLite(); + Entry.playground?.dataTable?.removeAllBlocks(); + Entry.aiLearning?.removeAllBlocks(); Util.removeNotSupportedBlock(); Entry.playground.blockMenu.banClass('python_disable'); // 블럭 삭제되고 처리 diff --git a/types/index.d.ts b/types/index.d.ts index eff40e1504..1b8564568c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -213,7 +213,7 @@ export declare interface EntryHWLiteBaseModule extends EntryBlockModule { type?: 'master' | 'slave'; delimeter?: string | number; webapiType?: 'ble' | 'usb' | 'serial'; - firmwareFlash?: boolean; + supportFirmwareFlash?: boolean; // 필수 함수 목록 setZero: () => void;