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 = () => {
-
+
유효한 페이지가 아닙니다!
다른 페이지에서 만나요!
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 (