From 7dc7c318b88be6acf17cdde9860523cac95d1b20 Mon Sep 17 00:00:00 2001 From: inhachoi <80045891+inhachoi@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:42:16 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[#36]=20class=20=EB=B8=94=EB=A1=9D=20?= =?UTF-8?q?=EB=8F=99=EC=A0=81=20=EC=83=9D=EC=84=B1=20(#109)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨ feat: 커스텀 툴박스 돔 조작으로 생성 * ✨ feat: alert 창을 이용한 css 블록 동적 생성 * 🔨 refactor: 빈 값 입력시 거절 alert 추가 * 🙀 chore: 함수 위치 변경 * 🔨 refactor: 파일 분리 * 🙀 chore: fsd 구조에 맞게 캡슐화 * 🙀 chore: 시맨틱 태그 사용 + ifram에 title추가 * 🔨 refactor: 프리뷰 부분 컴포넌트화로 분리 * 🙀 chore: import 설정 변경 * 🐛 fix: ci/cd 에러 수정 --- apps/client/src/pages/NotFound.tsx | 2 +- apps/client/src/widgets/index.ts | 11 + .../widgets/workspace/CssPropsSelectBox.tsx | 1 + .../src/widgets/workspace/PreviewBox.tsx | 37 ++ .../widgets/workspace/WorkspaceContent.tsx | 315 ++---------------- .../workspace/blockly/categoryColours.ts | 23 ++ .../workspace/blockly/classMakerPrompt.ts | 44 +++ .../workspace/{ => blockly}/customCategory.ts | 1 + .../workspace/blockly/customizeFlyoutSVG.ts | 46 +++ .../widgets/workspace/blockly/defineBlocks.ts | 73 ++++ .../workspace/blockly/htmBlockContents.ts | 26 ++ .../{ => blockly}/htmlCodeGenerator.ts | 0 .../widgets/workspace/blockly/initTheme.ts | 22 ++ .../workspace/blockly/toolboxConfig.ts | 56 ++++ .../workspace/blockly/toolboxConfig2.ts | 4 + 15 files changed, 365 insertions(+), 296 deletions(-) create mode 100644 apps/client/src/widgets/workspace/PreviewBox.tsx create mode 100644 apps/client/src/widgets/workspace/blockly/categoryColours.ts create mode 100644 apps/client/src/widgets/workspace/blockly/classMakerPrompt.ts rename apps/client/src/widgets/workspace/{ => blockly}/customCategory.ts (99%) create mode 100644 apps/client/src/widgets/workspace/blockly/customizeFlyoutSVG.ts create mode 100644 apps/client/src/widgets/workspace/blockly/defineBlocks.ts create mode 100644 apps/client/src/widgets/workspace/blockly/htmBlockContents.ts rename apps/client/src/widgets/workspace/{ => blockly}/htmlCodeGenerator.ts (100%) create mode 100644 apps/client/src/widgets/workspace/blockly/initTheme.ts create mode 100644 apps/client/src/widgets/workspace/blockly/toolboxConfig.ts create mode 100644 apps/client/src/widgets/workspace/blockly/toolboxConfig2.ts diff --git a/apps/client/src/pages/NotFound.tsx b/apps/client/src/pages/NotFound.tsx index 6ab2eed5..efb0bf01 100644 --- a/apps/client/src/pages/NotFound.tsx +++ b/apps/client/src/pages/NotFound.tsx @@ -7,7 +7,7 @@ export const NotFound = () => {
- + not_found

유효한 페이지가 아닙니다!
다른 페이지에서 만나요! diff --git a/apps/client/src/widgets/index.ts b/apps/client/src/widgets/index.ts index 6f84b31a..920520f9 100644 --- a/apps/client/src/widgets/index.ts +++ b/apps/client/src/widgets/index.ts @@ -7,5 +7,16 @@ export { WorkspaceGrid } from './home/WorkspaceGrid'; export { WorkspaceContainer } from './home/WorkspaceContainer'; export { WorkspaceModal } from './home/WorkspaceModal'; +export { CssPropsSelectBox } from './workspace/CssPropsSelectBox'; +export { PreviewBox } from './workspace/PreviewBox'; export { WorkspaceContent } from './workspace/WorkspaceContent'; export { WorkspacePageHeader } from './workspace/WorkspacePageHeader'; + +export { categoryColours } from './workspace/blockly/categoryColours'; +export { classMakerPrompt } from './workspace/blockly/classMakerPrompt'; +export { customizeFlyoutSVG } from './workspace/blockly/customizeFlyoutSVG'; +export { defineBlocks } from './workspace/blockly/defineBlocks'; +export { htmBlockContents } from './workspace/blockly/htmBlockContents'; +export { initTheme } from './workspace/blockly/initTheme'; +export { toolboxConfig } from './workspace/blockly/toolboxConfig'; +export { toolboxConfig2 } from './workspace/blockly/toolboxConfig2'; diff --git a/apps/client/src/widgets/workspace/CssPropsSelectBox.tsx b/apps/client/src/widgets/workspace/CssPropsSelectBox.tsx index d233ee99..31342117 100644 --- a/apps/client/src/widgets/workspace/CssPropsSelectBox.tsx +++ b/apps/client/src/widgets/workspace/CssPropsSelectBox.tsx @@ -1,4 +1,5 @@ import { useState } from 'react'; + import Question from '@/shared/assets/question.svg?react'; export const CssPropsSelectBox = () => { diff --git a/apps/client/src/widgets/workspace/PreviewBox.tsx b/apps/client/src/widgets/workspace/PreviewBox.tsx new file mode 100644 index 00000000..5e3a3947 --- /dev/null +++ b/apps/client/src/widgets/workspace/PreviewBox.tsx @@ -0,0 +1,37 @@ +type PreviewBoxProps = { + activeTab: 'preview' | 'html' | 'css'; + setActiveTab: (tab: 'preview' | 'html' | 'css') => void; + htmlCode: string; +}; + +export const PreviewBox = ({ activeTab, setActiveTab, htmlCode }: PreviewBoxProps) => { + return ( +

+ +
+ {activeTab === 'preview' && } + {activeTab === 'html' &&
{htmlCode}
} + {activeTab === 'css' &&

css 파싱 기능은 구현 중 입니다.

} +
+
+ ); +}; diff --git a/apps/client/src/widgets/workspace/WorkspaceContent.tsx b/apps/client/src/widgets/workspace/WorkspaceContent.tsx index 6508a4af..04b35cf3 100644 --- a/apps/client/src/widgets/workspace/WorkspaceContent.tsx +++ b/apps/client/src/widgets/workspace/WorkspaceContent.tsx @@ -1,9 +1,18 @@ import 'blockly/blocks'; import * as Blockly from 'blockly/core'; import { useEffect, useState } from 'react'; -import htmlCodeGenerator from '@/widgets/workspace/htmlCodeGenerator'; -import CustomCategory from './customCategory'; -import { CssPropsSelectBox } from './CssPropsSelectBox'; + +import htmlCodeGenerator from '@/widgets/workspace/blockly/htmlCodeGenerator'; +import CustomCategory from '@/widgets/workspace/blockly/customCategory'; +import { + CssPropsSelectBox, + defineBlocks, + toolboxConfig, + initTheme, + customizeFlyoutSVG, + classMakerPrompt, + PreviewBox, +} from '@/widgets'; Blockly.registry.register( Blockly.registry.Type.TOOLBOX_ITEM, @@ -12,208 +21,20 @@ Blockly.registry.register( true ); -const customTheme = Blockly.Theme.defineTheme('custom', { - name: 'custom', - base: Blockly.Themes.Classic, - componentStyles: { - workspaceBackgroundColour: '#fafafa', // 워크스페이스 배경색 - toolboxBackgroundColour: 'blackBackground', // 툴박스 배경색 - flyoutBackgroundColour: 'white', // 툴박스 플라이아웃 배경색 - flyoutOpacity: 1, - scrollbarColour: '#000000', - insertionMarkerColour: '#fff', - insertionMarkerOpacity: 0.3, - scrollbarOpacity: 0.001, - cursorColour: '#d0d0d0', - }, - categoryStyles: { - containerCategory: { - colour: 'FF3A61', - }, - textCategory: { - colour: 'FFD900', - }, - formCategory: { - colour: 'FF9821', - }, - tableCategory: { - colour: 'B223F5', - }, - listCategory: { - colour: '3ED5FF', - }, - linkCategory: { - colour: '3E84FF', - }, - etcCategory: { - colour: '00AF6F', - }, - }, -}); - -Blockly.Blocks['html'] = { - init: function () { - this.appendDummyInput().appendField('html'); - this.appendValueInput('css class').setCheck('String').appendField('css class'); - this.appendStatementInput('children').appendField('children'); - this.setColour(230); - }, -}; - -Blockly.Blocks['head'] = { - init: function () { - this.setPreviousStatement(true); - this.setNextStatement(true); - this.appendEndRowInput().appendField('head'); - this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); - this.appendStatementInput('children').appendField(); - this.setColour(120); - }, -}; - -Blockly.Blocks['body'] = { - init: function () { - this.setPreviousStatement(true); - this.setNextStatement(true); - this.appendEndRowInput().appendField('body'); - this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); - this.appendStatementInput('children').appendField(); - this.setColour(300); - }, -}; - -Blockly.Blocks['p'] = { - init: function () { - this.setPreviousStatement(true); - this.setNextStatement(true); - this.appendEndRowInput().appendField('p'); - this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); - this.appendStatementInput('children').appendField(); - this.setColour(180); - }, -}; - -Blockly.Blocks['button'] = { - init: function () { - this.setPreviousStatement(true); - this.setNextStatement(true); - this.appendEndRowInput().appendField('button'); - this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); - this.appendStatementInput('children').appendField(); - this.setColour(280); - }, -}; - -Blockly.Blocks['text'] = { - init: function () { - this.setPreviousStatement(true); // 다른 블록 위에 연결 가능 - this.setNextStatement(true); // 다른 블록 아래에 연결 가능 - this.appendDummyInput().appendField('text').appendField(new Blockly.FieldTextInput(), 'TEXT'); - this.setColour(40); - }, -}; - -// css 블록 -Blockly.Blocks['css_style'] = { - init: function () { - this.appendDummyInput().appendField(new Blockly.FieldTextInput('클래스명'), 'CLASS'); // "클래스명"은 초기값 - this.setOutput(true); // 이 블록을 다른 블록에 연결할 수 있도록 설정 - }, -}; - -const contents = [ - { - kind: 'block', - type: 'html', - }, - { - kind: 'block', - type: 'head', - }, - { - kind: 'block', - type: 'body', - }, - { - kind: 'block', - type: 'p', - }, - { - kind: 'block', - type: 'button', - }, - { - kind: 'block', - type: 'text', - }, -]; - -const toolboxConfig = { - kind: 'categoryToolbox', - contents: [ - { - kind: 'category', - name: '컨테이너', - categorystyle: 'containerCategory', - contents: contents, - }, - { - kind: 'category', - name: '텍스트', - categorystyle: 'textCategory', - contents: contents, - }, - { - kind: 'category', - name: '폼', - categorystyle: 'formCategory', - contents: [{ kind: 'block', type: 'css_style' }], - }, - { - kind: 'category', - name: '표', - categorystyle: 'tableCategory', - contents: contents, - }, - { - kind: 'category', - name: '리스트', - categorystyle: 'listCategory', - contents: contents, - }, - { - kind: 'category', - name: '링크', - categorystyle: 'linkCategory', - contents: contents, - }, - { - kind: 'category', - name: '기타', - categorystyle: 'etcCategory', - contents: contents, - }, - ], -}; - -const toolboxConfig2 = { - kind: 'categoryToolbox', - contents: [], -}; - export const WorkspaceContent = () => { const [workspace, setWorkspace] = useState(null); const [htmlCode, setHtmlCode] = useState(''); const [activeTab, setActiveTab] = useState<'preview' | 'html' | 'css'>('preview'); + defineBlocks(); + useEffect(() => { const newWorkspace = Blockly.inject('blocklyDiv', { renderer: 'zelos', toolboxPosition: 'end', toolbox: toolboxConfig, - theme: customTheme, // 커스텀 테마 적용 + theme: initTheme, zoom: { - // 확대 및 축소 버튼 설정 controls: true, wheel: true, startScale: 1.0, @@ -222,83 +43,12 @@ export const WorkspaceContent = () => { scaleSpeed: 1.2, }, }); - setWorkspace(newWorkspace); - - interface IExtendedIToolbox extends Blockly.IToolbox { - HtmlDiv: HTMLElement; - } - - const customizeFlyoutSVG = () => { - const toolbox: IExtendedIToolbox = newWorkspace.getToolbox()! as IExtendedIToolbox; - - const tabs = document.createElement('div'); - tabs.className = 'flex w-96'; - - const tab1 = document.createElement('button'); - tab1.classList.add('tab'); - tab1.textContent = 'HTML'; - - const tab2 = document.createElement('button'); - tab2.classList.add('tab'); - tab2.textContent = 'CSS'; - - tab1.addEventListener('click', () => { - newWorkspace.updateToolbox(toolboxConfig); - const toolboxContents = document.querySelector('.blocklyToolboxContents'); - toolboxContents!.classList.remove('hidden'); - tab1.classList.add('tabSelected'); - tab2.classList.remove('tabSelected'); - }); - - tab2.addEventListener('click', () => { - newWorkspace.updateToolbox(toolboxConfig2); - const toolboxContents = document.querySelector('.blocklyToolboxContents'); - toolboxContents!.classList.add('hidden'); - tab2.classList.add('tabSelected'); - tab1.classList.remove('tabSelected'); - }); - tabs.appendChild(tab1); - tabs.appendChild(tab2); + newWorkspace.registerButtonCallback('classMakerPrompt', () => classMakerPrompt(newWorkspace)); - toolbox!.HtmlDiv.prepend(tabs); - const flyout = newWorkspace!.getToolbox()!.getFlyout(); - flyout!.hide = () => {}; - }; - - customizeFlyoutSVG(); - - // CSS 카테고리가 열릴 때 input 필드를 동적으로 추가하는 함수 - const addInputFieldToFlyout = () => { - const toolboxElement = document.querySelector('.blocklyFlyout'); - - if (toolboxElement) { - // 기존에 추가된 input 필드가 있는지 확인하고, 있으면 제거 - let existingInputDiv = toolboxElement.querySelector('.custom-input'); - if (existingInputDiv) { - existingInputDiv.remove(); - } - - // 새로운 input 필드 생성 - const inputDiv = document.createElement('div'); - inputDiv.className = 'custom-input'; - inputDiv.style.padding = '5px'; - inputDiv.innerHTML = ``; - - // Flyout toolbox에 input 필드를 추가 - toolboxElement.insertBefore(inputDiv, toolboxElement.firstChild); - } - }; + setWorkspace(newWorkspace); + customizeFlyoutSVG(newWorkspace); - // CSS 카테고리 열기를 감지하고 input 필드를 추가 - newWorkspace.addChangeListener((event) => { - if ( - event.type === Blockly.Events.TOOLBOX_ITEM_SELECT && - (event as any).newItemId === 'css_category' - ) { - addInputFieldToFlyout(); - } - }); return () => { newWorkspace.dispose(); }; @@ -315,37 +65,12 @@ export const WorkspaceContent = () => { return (
-
- -
- {activeTab === 'preview' && } - {activeTab === 'html' &&
{htmlCode}
} - {activeTab === 'css' &&

css 파싱 기능은 구현 중 입니다.

} -
-
+
+ diff --git a/apps/client/src/widgets/workspace/blockly/categoryColours.ts b/apps/client/src/widgets/workspace/blockly/categoryColours.ts new file mode 100644 index 00000000..c4b73ee1 --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/categoryColours.ts @@ -0,0 +1,23 @@ +export const categoryColours = { + containerCategory: { + colour: 'FF3A61', + }, + textCategory: { + colour: 'FFD900', + }, + formCategory: { + colour: 'FF9821', + }, + tableCategory: { + colour: 'B223F5', + }, + listCategory: { + colour: '3ED5FF', + }, + linkCategory: { + colour: '3E84FF', + }, + etcCategory: { + colour: '00AF6F', + }, +}; diff --git a/apps/client/src/widgets/workspace/blockly/classMakerPrompt.ts b/apps/client/src/widgets/workspace/blockly/classMakerPrompt.ts new file mode 100644 index 00000000..f049ef56 --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/classMakerPrompt.ts @@ -0,0 +1,44 @@ +import 'blockly/blocks'; +import * as Blockly from 'blockly/core'; + +import { toolboxConfig } from '@/widgets'; + +// prompt를 이용한 class 동적 생성 +export const classMakerPrompt = (workspace: Blockly.WorkspaceSvg) => { + const blockName = prompt('새로운 class 블록 이름을 입력하세요.'); + + if (blockName?.trim() === '') { + return alert('블록 이름을 입력해주세요.'); + } + + if (!Blockly.Blocks[blockName!]) { + Blockly.Blocks[blockName!] = { + init: function () { + this.appendDummyInput().appendField(new Blockly.FieldTextInput(blockName!), 'CLASS'); // 입력된 이름 반영 + this.setOutput(true); + this.setColour(230); + }, + }; + } + + // "폼" 카테고리를 찾아 기존 블록 유지 및 새 블록 추가 + const formCategory = toolboxConfig.contents.find((category) => category.name === '폼'); + + // 기존 블록 유지 및 새 블록 추가 + const existingBlocks = formCategory!.contents || []; + const isBlockAlreadyAdded = existingBlocks.some((block) => block.type === blockName); + + if (isBlockAlreadyAdded) { + alert(`"${blockName}" 블록은 이미 "폼" 카테고리에 존재합니다.`); + return; + } + + if (blockName) { + formCategory!.contents = [...existingBlocks, { kind: 'block', type: blockName }]; + } + + // 기존 툴박스 갱신 + workspace.updateToolbox(toolboxConfig); + + alert(`새 블록 "${blockName}"이(가) "폼" 카테고리에 성공적으로 추가되었습니다.`); +}; diff --git a/apps/client/src/widgets/workspace/customCategory.ts b/apps/client/src/widgets/workspace/blockly/customCategory.ts similarity index 99% rename from apps/client/src/widgets/workspace/customCategory.ts rename to apps/client/src/widgets/workspace/blockly/customCategory.ts index 2242a722..478691ce 100644 --- a/apps/client/src/widgets/workspace/customCategory.ts +++ b/apps/client/src/widgets/workspace/blockly/customCategory.ts @@ -1,6 +1,7 @@ import * as Blockly from 'blockly/core'; import { CategoryInfo } from 'blockly/core/utils/toolbox'; import { IToolbox } from 'blockly'; + import { CATEGORY_ICONS } from '@/shared/utils'; export default class CustomCategory extends Blockly.ToolboxCategory { diff --git a/apps/client/src/widgets/workspace/blockly/customizeFlyoutSVG.ts b/apps/client/src/widgets/workspace/blockly/customizeFlyoutSVG.ts new file mode 100644 index 00000000..de7f386a --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/customizeFlyoutSVG.ts @@ -0,0 +1,46 @@ +import 'blockly/blocks'; +import * as Blockly from 'blockly/core'; + +import { toolboxConfig, toolboxConfig2 } from '@/widgets'; + +interface IExtendedIToolbox extends Blockly.IToolbox { + HtmlDiv: HTMLElement; +} + +export const customizeFlyoutSVG = (newWorkspace: any) => { + const toolbox: IExtendedIToolbox = newWorkspace.getToolbox()! as IExtendedIToolbox; + + const tabs = document.createElement('div'); + tabs.className = 'flex w-96'; + + const tab1 = document.createElement('button'); + tab1.classList.add('tab'); + tab1.textContent = 'HTML'; + + const tab2 = document.createElement('button'); + tab2.classList.add('tab'); + tab2.textContent = 'CSS'; + + tab1.addEventListener('click', () => { + newWorkspace.updateToolbox(toolboxConfig); + const toolboxContents = document.querySelector('.blocklyToolboxContents'); + toolboxContents!.classList.remove('hidden'); + tab1.classList.add('tabSelected'); + tab2.classList.remove('tabSelected'); + }); + + tab2.addEventListener('click', () => { + newWorkspace.updateToolbox(toolboxConfig2); + const toolboxContents = document.querySelector('.blocklyToolboxContents'); + toolboxContents!.classList.add('hidden'); + tab2.classList.add('tabSelected'); + tab1.classList.remove('tabSelected'); + }); + + tabs.appendChild(tab1); + tabs.appendChild(tab2); + + toolbox!.HtmlDiv.prepend(tabs); + const flyout = newWorkspace!.getToolbox()!.getFlyout(); + flyout!.hide = () => {}; +}; diff --git a/apps/client/src/widgets/workspace/blockly/defineBlocks.ts b/apps/client/src/widgets/workspace/blockly/defineBlocks.ts new file mode 100644 index 00000000..e4adfd9d --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/defineBlocks.ts @@ -0,0 +1,73 @@ +import * as Blockly from 'blockly/core'; + +export const defineBlocks = () => { + Blockly.Blocks['html'] = { + init: function () { + this.appendDummyInput().appendField('html'); + this.appendValueInput('css class').setCheck('String').appendField('css class'); + this.appendStatementInput('children').appendField('children'); + this.setColour(230); + }, + }; + + Blockly.Blocks['head'] = { + init: function () { + this.setPreviousStatement(true); + this.setNextStatement(true); + this.appendEndRowInput().appendField('head'); + this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); + this.appendStatementInput('children').appendField(); + this.setColour(120); + }, + }; + + Blockly.Blocks['body'] = { + init: function () { + this.setPreviousStatement(true); + this.setNextStatement(true); + this.appendEndRowInput().appendField('body'); + this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); + this.appendStatementInput('children').appendField(); + this.setColour(300); + }, + }; + + Blockly.Blocks['p'] = { + init: function () { + this.setPreviousStatement(true); + this.setNextStatement(true); + this.appendEndRowInput().appendField('p'); + this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); + this.appendStatementInput('children').appendField(); + this.setColour(180); + }, + }; + + Blockly.Blocks['button'] = { + init: function () { + this.setPreviousStatement(true); + this.setNextStatement(true); + this.appendEndRowInput().appendField('button'); + this.appendValueInput('css class').setCheck('CSS-CLASS').appendField('css class'); + this.appendStatementInput('children').appendField(); + this.setColour(280); + }, + }; + + Blockly.Blocks['text'] = { + init: function () { + this.setPreviousStatement(true); // 다른 블록 위에 연결 가능 + this.setNextStatement(true); // 다른 블록 아래에 연결 가능 + this.appendDummyInput().appendField('text').appendField(new Blockly.FieldTextInput(), 'TEXT'); + this.setColour(40); + }, + }; + + // css 블록 + Blockly.Blocks['css_style'] = { + init: function () { + this.appendDummyInput().appendField(new Blockly.FieldTextInput('클래스명'), 'CLASS'); // "클래스명"은 초기값 + this.setOutput(true); // 이 블록을 다른 블록에 연결할 수 있도록 설정 + }, + }; +}; diff --git a/apps/client/src/widgets/workspace/blockly/htmBlockContents.ts b/apps/client/src/widgets/workspace/blockly/htmBlockContents.ts new file mode 100644 index 00000000..40f33e3e --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/htmBlockContents.ts @@ -0,0 +1,26 @@ +export const htmBlockContents = [ + { + kind: 'block', + type: 'html', + }, + { + kind: 'block', + type: 'head', + }, + { + kind: 'block', + type: 'body', + }, + { + kind: 'block', + type: 'p', + }, + { + kind: 'block', + type: 'button', + }, + { + kind: 'block', + type: 'text', + }, +]; diff --git a/apps/client/src/widgets/workspace/htmlCodeGenerator.ts b/apps/client/src/widgets/workspace/blockly/htmlCodeGenerator.ts similarity index 100% rename from apps/client/src/widgets/workspace/htmlCodeGenerator.ts rename to apps/client/src/widgets/workspace/blockly/htmlCodeGenerator.ts diff --git a/apps/client/src/widgets/workspace/blockly/initTheme.ts b/apps/client/src/widgets/workspace/blockly/initTheme.ts new file mode 100644 index 00000000..11142f66 --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/initTheme.ts @@ -0,0 +1,22 @@ +import 'blockly/blocks'; +import * as Blockly from 'blockly/core'; + +import { categoryColours } from '@/widgets'; + +export const initTheme = Blockly.Theme.defineTheme('custom', { + name: 'custom', + base: Blockly.Themes.Classic, + componentStyles: { + workspaceBackgroundColour: '#fafafa', // 워크스페이스 배경색 + toolboxBackgroundColour: 'blackBackground', // 툴박스 배경색 + flyoutBackgroundColour: 'white', // 툴박스 플라이아웃 배경색 + flyoutOpacity: 1, + scrollbarColour: '#000000', + insertionMarkerColour: '#fff', + insertionMarkerOpacity: 0.3, + scrollbarOpacity: 0.001, + cursorColour: '#d0d0d0', + }, + + categoryStyles: categoryColours, +}); diff --git a/apps/client/src/widgets/workspace/blockly/toolboxConfig.ts b/apps/client/src/widgets/workspace/blockly/toolboxConfig.ts new file mode 100644 index 00000000..c5801b4f --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/toolboxConfig.ts @@ -0,0 +1,56 @@ +import { htmBlockContents } from '@/widgets'; + +export const toolboxConfig = { + kind: 'categoryToolbox', + contents: [ + { + kind: 'category', + name: '컨테이너', + categorystyle: 'containerCategory', + contents: htmBlockContents, + }, + { + kind: 'category', + name: '텍스트', + categorystyle: 'textCategory', + contents: htmBlockContents, + }, + { + kind: 'category', + name: '폼', + categorystyle: 'formCategory', + contents: [ + { + kind: 'button', + text: '추가하기', + callbackKey: 'classMakerPrompt', + }, + { kind: 'block', type: 'css_style' }, + ], + }, + { + kind: 'category', + name: '표', + categorystyle: 'tableCategory', + contents: htmBlockContents, + }, + { + kind: 'category', + name: '리스트', + categorystyle: 'listCategory', + contents: htmBlockContents, + }, + { + kind: 'category', + name: '링크', + categorystyle: 'linkCategory', + contents: htmBlockContents, + }, + { + kind: 'category', + name: '기타', + categorystyle: 'etcCategory', + contents: htmBlockContents, + }, + ], +}; diff --git a/apps/client/src/widgets/workspace/blockly/toolboxConfig2.ts b/apps/client/src/widgets/workspace/blockly/toolboxConfig2.ts new file mode 100644 index 00000000..6e065afa --- /dev/null +++ b/apps/client/src/widgets/workspace/blockly/toolboxConfig2.ts @@ -0,0 +1,4 @@ +export const toolboxConfig2 = { + kind: 'categoryToolbox', + contents: [], +}; From 561cc668e8cfebf8200cb101cd5866b42cd2002b Mon Sep 17 00:00:00 2001 From: inhachoi <80045891+inhachoi@users.noreply.github.com> Date: Thu, 21 Nov 2024 10:54:12 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[#21]=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EB=8A=94=20=E2=80=9C=EB=AF=B8=EB=A6=AC=EB=B3=B4=EA=B8=B0?= =?UTF-8?q?=E2=80=9D=20=ED=83=AD=EC=9D=84=20=EB=88=84=EB=A5=B4=EB=A9=B4=20?= =?UTF-8?q?=EB=B8=94=EB=A1=9D=EC=9C=BC=EB=A1=9C=20=EB=A7=8C=EB=93=A0=20?= =?UTF-8?q?=EC=9B=B9=20=EC=82=AC=EC=9D=B4=ED=8A=B8=EB=A5=BC=20=EB=AF=B8?= =?UTF-8?q?=EB=A6=AC=20=EB=B3=BC=20=EC=88=98=20=EC=9E=88=EB=8B=A4.=20(#111?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🙀 chore: useState 로직 분리 * 🔨 refactor: css 탭 클릭하면 flyout만 뜨게 함 * 🙀 chore: tab 버튼 적절한 변수명으로 수정 * ✨ feat: 커스텀 flyout 시도 * ✨ feat: class 블록들 전역 관리 * 🙀 chore: 테스트 및 디버깅용 코드 제거 * ✨ feat: 성공, 실패시 alert창에서 toast 메세지로 변경 * ✨ feat: 스타일 탭 다시 클릭 안해도, 스타일 블록 실시간 갱신 * 🔨 refactor: 스타일 블록 이름 수정불가로 변경 * ✨ feat: 자동 변환 기능 추가 --- apps/client/src/shared/store/index.ts | 1 + .../src/shared/store/useClassBlockStore.ts | 15 +++ apps/client/src/widgets/index.ts | 2 +- .../src/widgets/workspace/PreviewBox.tsx | 8 +- .../widgets/workspace/WorkspaceContent.tsx | 38 ++++---- .../workspace/blockly/classMakerPrompt.ts | 30 +++--- .../widgets/workspace/blockly/customFlyout.ts | 96 +++++++++++++++++++ .../workspace/blockly/customToolbox.ts | 64 +++++++++++++ .../workspace/blockly/customizeFlyoutSVG.ts | 46 --------- .../widgets/workspace/blockly/defineBlocks.ts | 3 +- .../workspace/blockly/toolboxConfig.ts | 9 +- .../workspace/blockly/toolboxConfig2.ts | 9 +- 12 files changed, 230 insertions(+), 91 deletions(-) create mode 100644 apps/client/src/shared/store/useClassBlockStore.ts create mode 100644 apps/client/src/widgets/workspace/blockly/customFlyout.ts create mode 100644 apps/client/src/widgets/workspace/blockly/customToolbox.ts delete mode 100644 apps/client/src/widgets/workspace/blockly/customizeFlyoutSVG.ts diff --git a/apps/client/src/shared/store/index.ts b/apps/client/src/shared/store/index.ts index ee116c2d..4194a1d5 100644 --- a/apps/client/src/shared/store/index.ts +++ b/apps/client/src/shared/store/index.ts @@ -1,3 +1,4 @@ export { useLoadingStore } from './useLoadingStore'; export { useModalStore } from './useModalStore'; export { useWorkspaceStore } from './useWorkspaceStore'; +export { useClassBlockStore } from './useClassBlockStore'; diff --git a/apps/client/src/shared/store/useClassBlockStore.ts b/apps/client/src/shared/store/useClassBlockStore.ts new file mode 100644 index 00000000..7dfb5c3a --- /dev/null +++ b/apps/client/src/shared/store/useClassBlockStore.ts @@ -0,0 +1,15 @@ +import { create } from 'zustand'; + +type TclassBlock = { + classBlockList: string[]; + addClassBlock: (newClassBlockName: string) => void +}; + +export const useClassBlockStore = create((set) => ({ + classBlockList: [], + addClassBlock: (newClassBlockName: string) => { + set((state) => ({ + classBlockList: [...state.classBlockList, newClassBlockName], + })); + }, +})); diff --git a/apps/client/src/widgets/index.ts b/apps/client/src/widgets/index.ts index 920520f9..ce5d4b6b 100644 --- a/apps/client/src/widgets/index.ts +++ b/apps/client/src/widgets/index.ts @@ -14,7 +14,7 @@ export { WorkspacePageHeader } from './workspace/WorkspacePageHeader'; export { categoryColours } from './workspace/blockly/categoryColours'; export { classMakerPrompt } from './workspace/blockly/classMakerPrompt'; -export { customizeFlyoutSVG } from './workspace/blockly/customizeFlyoutSVG'; +export { customToolbox } from './workspace/blockly/customToolbox'; export { defineBlocks } from './workspace/blockly/defineBlocks'; export { htmBlockContents } from './workspace/blockly/htmBlockContents'; export { initTheme } from './workspace/blockly/initTheme'; diff --git a/apps/client/src/widgets/workspace/PreviewBox.tsx b/apps/client/src/widgets/workspace/PreviewBox.tsx index 5e3a3947..dd0f7409 100644 --- a/apps/client/src/widgets/workspace/PreviewBox.tsx +++ b/apps/client/src/widgets/workspace/PreviewBox.tsx @@ -1,10 +1,12 @@ +import { useState } from 'react'; + type PreviewBoxProps = { - activeTab: 'preview' | 'html' | 'css'; - setActiveTab: (tab: 'preview' | 'html' | 'css') => void; htmlCode: string; }; -export const PreviewBox = ({ activeTab, setActiveTab, htmlCode }: PreviewBoxProps) => { +export const PreviewBox = ({ htmlCode }: PreviewBoxProps) => { + const [activeTab, setActiveTab] = useState<'preview' | 'html' | 'css'>('preview'); + return (