owenkaplinsky commited on
Commit
72263a9
·
1 Parent(s): d9f156a

Add styling and resizers

Browse files
project/app.py CHANGED
@@ -126,8 +126,6 @@ def execute_blockly_logic(user_inputs):
126
 
127
  def build_interface():
128
  with gr.Blocks() as demo:
129
- gr.Markdown("# Blockly Code Executor")
130
-
131
  # Create a fixed number of potential input fields (max 10)
132
  input_fields = []
133
  input_labels = []
 
126
 
127
  def build_interface():
128
  with gr.Blocks() as demo:
 
 
129
  # Create a fixed number of potential input fields (max 10)
130
  input_fields = []
131
  input_labels = []
project/src/index.css CHANGED
@@ -1,56 +1,228 @@
 
 
 
 
 
 
1
  html,
2
  body {
3
  height: 100%;
4
- margin: 0;
5
- padding: 0;
6
- max-width: 100vw;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
8
 
 
 
 
 
 
9
  #pageContainer {
10
  display: flex;
11
  width: 100%;
12
- height: 100%;
13
  overflow: hidden;
14
  }
15
 
16
- #blocklyDiv {
17
- flex: 1;
18
- min-width: 600px;
19
- height: 100%;
20
  }
21
 
 
22
  #outputPane {
 
 
 
 
 
 
 
23
  display: flex;
24
  flex-direction: column;
25
- width: 600px;
26
- flex-shrink: 0;
27
- height: 100%;
28
- overflow: hidden;
29
- padding: 1rem;
30
  box-sizing: border-box;
31
  }
32
 
 
33
  #chatContainer {
34
- flex: 0 0 400px;
35
- /* fixed height for iframe area */
36
- width: 100%;
 
37
  }
38
 
 
39
  #chatContainer iframe {
 
 
40
  width: 100%;
41
  height: 100%;
42
  border: none;
43
- display: block;
44
  }
45
 
 
46
  #generatedCode {
 
 
 
 
 
47
  flex: 1;
48
- background: #111;
49
- color: #0f0;
50
- padding: 10px;
51
- font-family: monospace;
52
- white-space: pre-wrap;
53
  overflow-y: auto;
54
- border: 1px solid #333;
55
- margin-top: 10px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  }
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
  html,
8
  body {
9
  height: 100%;
10
+ width: 100%;
11
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
12
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
13
+ overflow: hidden;
14
+ }
15
+
16
+ /* --- Top Bar --- */
17
+ #topBar {
18
+ display: flex;
19
+ align-items: center;
20
+ height: 50px;
21
+ background: #6366f1;
22
+ padding: 0 20px;
23
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
24
+ z-index: 1000;
25
+ }
26
+
27
+ #titleSection {
28
+ display: flex;
29
+ align-items: center;
30
+ }
31
+
32
+ #titleSection h1 {
33
+ font-size: 20px;
34
+ font-weight: 600;
35
+ color: white;
36
+ letter-spacing: 0.5px;
37
+ }
38
+
39
+ #divider {
40
+ width: 2px;
41
+ height: 30px;
42
+ background: rgba(255, 255, 255, 0.3);
43
+ margin: 0 20px;
44
+ }
45
+
46
+ /* --- Menus --- */
47
+ #menuSection {
48
+ display: flex;
49
+ gap: 5px;
50
+ }
51
+
52
+ .menuGroup {
53
+ position: relative;
54
+ }
55
+
56
+ .menuButton {
57
+ background: rgba(255, 255, 255, 0.15);
58
+ color: white;
59
+ border: 1px solid rgba(255, 255, 255, 0.2);
60
+ padding: 8px 16px;
61
+ border-radius: 4px;
62
+ cursor: pointer;
63
+ font-size: 14px;
64
+ font-weight: 500;
65
+ transition: all 0.2s ease;
66
+ backdrop-filter: blur(10px);
67
+ }
68
+
69
+ .menuButton:hover {
70
+ background: rgba(255, 255, 255, 0.25);
71
+ border-color: rgba(255, 255, 255, 0.3);
72
+ transform: translateY(-1px);
73
+ }
74
+
75
+ .menuButton:active {
76
+ transform: translateY(0);
77
+ }
78
+
79
+ .dropdown {
80
+ position: absolute;
81
+ top: 100%;
82
+ left: 0;
83
+ background: white;
84
+ border-radius: 6px;
85
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
86
+ min-width: 150px;
87
+ opacity: 0;
88
+ visibility: hidden;
89
+ transform: translateY(-8px);
90
+ transition: all 0.2s ease;
91
+ margin-top: 4px;
92
+ z-index: 2000;
93
+ }
94
+
95
+ .menuGroup:hover .dropdown,
96
+ .dropdown:hover {
97
+ opacity: 1;
98
+ visibility: visible;
99
+ transform: translateY(0);
100
+ }
101
+
102
+ .dropdownItem {
103
+ display: block;
104
+ padding: 12px 16px;
105
+ color: #333;
106
+ text-decoration: none;
107
+ font-size: 14px;
108
+ transition: all 0.15s ease;
109
+ border-left: 3px solid transparent;
110
+ }
111
+
112
+ .dropdownItem:first-child {
113
+ border-top-left-radius: 6px;
114
+ border-top-right-radius: 6px;
115
+ }
116
+
117
+ .dropdownItem:last-child {
118
+ border-bottom-left-radius: 6px;
119
+ border-bottom-right-radius: 6px;
120
+ }
121
+
122
+ .dropdownItem:hover {
123
+ background: #f0f2f5;
124
+ border-left-color: #667eea;
125
+ padding-left: 20px;
126
  }
127
 
128
+ .dropdownItem:active {
129
+ background: #e8ebf0;
130
+ }
131
+
132
+ /* --- Main Split Layout --- */
133
  #pageContainer {
134
  display: flex;
135
  width: 100%;
136
+ height: calc(100% - 50px);
137
  overflow: hidden;
138
  }
139
 
140
+ #pageContainer.dragging {
141
+ user-select: none;
142
+ cursor: col-resize;
 
143
  }
144
 
145
+ /* --- Add dark grey padding around Gradio + Code --- */
146
  #outputPane {
147
+ flex: 0 0 30%;
148
+ background: #2c2c2c;
149
+ /* dark grey background around both sections */
150
+ padding: 16px;
151
+ /* padding around the inside */
152
+ gap: 16px;
153
+ /* space between iframe and code */
154
  display: flex;
155
  flex-direction: column;
 
 
 
 
 
156
  box-sizing: border-box;
157
  }
158
 
159
+ /* Add matching background to the Gradio frame container */
160
  #chatContainer {
161
+ background: #2c2c2c;
162
+ border: none;
163
+ flex: 0 0 50%;
164
+ box-sizing: border-box;
165
  }
166
 
167
+ /* Slightly inset the Gradio iframe */
168
  #chatContainer iframe {
169
+ border-radius: 6px;
170
+ overflow: hidden;
171
  width: 100%;
172
  height: 100%;
173
  border: none;
 
174
  }
175
 
176
+ /* Style the code area with matching margin and contrast */
177
  #generatedCode {
178
+ background: #1f1f1f;
179
+ color: #e0e7ff;
180
+ border-radius: 6px;
181
+ padding: 16px;
182
+ box-sizing: border-box;
183
  flex: 1;
 
 
 
 
 
184
  overflow-y: auto;
185
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
186
+ white-space: pre-wrap;
187
+ font-size: 13px;
188
+ line-height: 1.5;
189
+ }
190
+
191
+ /* --- Vertical Resizer --- */
192
+ .verticalResizer {
193
+ height: 8px;
194
+ background: #e5e7eb;
195
+ cursor: row-resize;
196
+ transition: background 0.2s ease;
197
+ flex-shrink: 0;
198
+ z-index: 1500;
199
+ }
200
+
201
+ .verticalResizer:hover,
202
+ .verticalResizer.active {
203
+ background: #6366f1;
204
+ }
205
+
206
+ /* --- Resizer --- */
207
+ .resizer {
208
+ width: 8px;
209
+ background: #e5e7eb;
210
+ cursor: col-resize;
211
+ transition: background 0.2s ease;
212
+ flex-shrink: 0;
213
+ z-index: 1500;
214
+ }
215
+
216
+ .resizer:hover,
217
+ .resizer.active {
218
+ background: #6366f1;
219
+ }
220
+
221
+ /* --- Right Pane --- */
222
+ #blocklyDiv {
223
+ flex: 1;
224
+ min-width: 40%;
225
+ height: 100%;
226
+ overflow: hidden;
227
+ background: #fff;
228
  }
project/src/index.html CHANGED
@@ -3,20 +3,152 @@
3
 
4
  <head>
5
  <meta charset="utf-8" />
6
- <title>Blockly Sample App</title>
7
  </head>
8
 
9
  <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  <div id="pageContainer">
11
  <div id="outputPane">
12
  <div id="chatContainer">
13
- <iframe src="http://127.0.0.1:7860" style="width: 100%; height: 600px; border: none;"></iframe>
14
  </div>
15
-
16
  <pre id="generatedCode"><code></code></pre>
17
  </div>
 
18
  <div id="blocklyDiv"></div>
19
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
  </body>
21
 
22
  </html>
 
3
 
4
  <head>
5
  <meta charset="utf-8" />
6
+ <title>Blockly MCP Builder</title>
7
  </head>
8
 
9
  <body>
10
+ <div id="topBar">
11
+ <div id="titleSection">
12
+ <h1>Blockly MCP Builder</h1>
13
+ </div>
14
+ <div id="divider"></div>
15
+ <div id="menuSection">
16
+ <div class="menuGroup">
17
+ <button class="menuButton">File</button>
18
+ <div class="dropdown">
19
+ <a href="#" class="dropdownItem" data-action="new">New</a>
20
+ <a href="#" class="dropdownItem" data-action="open">Open</a>
21
+ <a href="#" class="dropdownItem" data-action="download">Download</a>
22
+ </div>
23
+ </div>
24
+
25
+ <div class="menuGroup">
26
+ <button class="menuButton">Edit</button>
27
+ <div class="dropdown">
28
+ <a href="#" class="dropdownItem" data-action="undo">Undo</a>
29
+ <a href="#" class="dropdownItem" data-action="redo">Redo</a>
30
+ <a href="#" class="dropdownItem" data-action="cleanup">Clean up</a>
31
+ <a href="#" class="dropdownItem" data-action="clear">Clear</a>
32
+ </div>
33
+ </div>
34
+
35
+ <div class="menuGroup">
36
+ <button class="menuButton">Examples</button>
37
+ <div class="dropdown"></div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
  <div id="pageContainer">
43
  <div id="outputPane">
44
  <div id="chatContainer">
45
+ <iframe src="http://127.0.0.1:7860" style="width: 100%; height: 100%; border: none;"></iframe>
46
  </div>
47
+ <div class="verticalResizer"></div>
48
  <pre id="generatedCode"><code></code></pre>
49
  </div>
50
+ <div class="resizer"></div>
51
  <div id="blocklyDiv"></div>
52
  </div>
53
+
54
+ <script>
55
+ // Horizontal resizer (output pane vs blockly)
56
+ const resizer = document.querySelector('.resizer');
57
+ const outputPane = document.getElementById('outputPane');
58
+ const pageContainer = document.getElementById('pageContainer');
59
+
60
+ let startX = 0;
61
+ let startWidth = 0;
62
+
63
+ function onPointerMove(e) {
64
+ const containerRect = pageContainer.getBoundingClientRect();
65
+ const containerWidth = containerRect.width;
66
+
67
+ const dx = e.clientX - startX;
68
+ let newWidthPx = startWidth + dx;
69
+
70
+ const minWidth = containerWidth * 0.2;
71
+ const maxWidth = containerWidth * 0.59;
72
+ newWidthPx = Math.max(minWidth, Math.min(maxWidth, newWidthPx));
73
+
74
+ const newPercent = (newWidthPx / containerWidth) * 100;
75
+ outputPane.style.flex = `0 0 ${newPercent}%`;
76
+ }
77
+
78
+ function onPointerUp() {
79
+ resizer.releasePointerCapture(activePointerId);
80
+ resizer.removeEventListener('pointermove', onPointerMove);
81
+ resizer.removeEventListener('pointerup', onPointerUp);
82
+ resizer.classList.remove('active');
83
+ document.body.style.cursor = '';
84
+ document.body.style.userSelect = '';
85
+ }
86
+
87
+ let activePointerId = null;
88
+
89
+ resizer.addEventListener('pointerdown', (e) => {
90
+ const rect = outputPane.getBoundingClientRect();
91
+ startX = e.clientX;
92
+ startWidth = rect.width;
93
+ activePointerId = e.pointerId;
94
+
95
+ resizer.classList.add('active');
96
+ document.body.style.cursor = 'col-resize';
97
+ document.body.style.userSelect = 'none';
98
+
99
+ resizer.setPointerCapture(activePointerId);
100
+ resizer.addEventListener('pointermove', onPointerMove);
101
+ resizer.addEventListener('pointerup', onPointerUp);
102
+ });
103
+
104
+ // Vertical resizer (gradio vs code)
105
+ const verticalResizer = document.querySelector('.verticalResizer');
106
+ const chatContainer = document.getElementById('chatContainer');
107
+ const generatedCode = document.getElementById('generatedCode');
108
+
109
+ let startY = 0;
110
+ let startHeight = 0;
111
+ let activePointerId2 = null;
112
+
113
+ function onVerticalPointerMove(e) {
114
+ const outputPaneRect = outputPane.getBoundingClientRect();
115
+ const outputPaneHeight = outputPaneRect.height;
116
+
117
+ const dy = e.clientY - startY;
118
+ let newHeightPx = startHeight + dy;
119
+
120
+ const minHeight = outputPaneHeight * 0.4;
121
+ const maxHeight = outputPaneHeight * 0.78;
122
+ newHeightPx = Math.max(minHeight, Math.min(maxHeight, newHeightPx));
123
+
124
+ const newPercent = (newHeightPx / outputPaneHeight) * 100;
125
+ chatContainer.style.flex = `0 0 ${newPercent}%`;
126
+ }
127
+
128
+ function onVerticalPointerUp() {
129
+ verticalResizer.releasePointerCapture(activePointerId2);
130
+ verticalResizer.removeEventListener('pointermove', onVerticalPointerMove);
131
+ verticalResizer.removeEventListener('pointerup', onVerticalPointerUp);
132
+ verticalResizer.classList.remove('active');
133
+ document.body.style.cursor = '';
134
+ document.body.style.userSelect = '';
135
+ }
136
+
137
+ verticalResizer.addEventListener('pointerdown', (e) => {
138
+ const rect = chatContainer.getBoundingClientRect();
139
+ startY = e.clientY;
140
+ startHeight = rect.height;
141
+ activePointerId2 = e.pointerId;
142
+
143
+ verticalResizer.classList.add('active');
144
+ document.body.style.cursor = 'row-resize';
145
+ document.body.style.userSelect = 'none';
146
+
147
+ verticalResizer.setPointerCapture(activePointerId2);
148
+ verticalResizer.addEventListener('pointermove', onVerticalPointerMove);
149
+ verticalResizer.addEventListener('pointerup', onVerticalPointerUp);
150
+ });
151
+ </script>
152
  </body>
153
 
154
  </html>
project/src/index.js CHANGED
@@ -5,6 +5,7 @@ import { pythonGenerator } from 'blockly/python';
5
  import { save, load } from './serialization';
6
  import { toolbox } from './toolbox';
7
  import '@blockly/toolbox-search';
 
8
  import './index.css';
9
 
10
  // Register the blocks and generator with Blockly
@@ -14,32 +15,6 @@ Object.assign(pythonGenerator.forBlock, forBlock);
14
  // Set up UI elements and inject Blockly
15
  const blocklyDiv = document.getElementById('blocklyDiv');
16
 
17
- // Create a custom theme (Scratch-like colors and hats)
18
- const myTheme = Blockly.Theme.defineTheme('myScratchTheme', {
19
- base: Blockly.Themes.Classic,
20
- startHats: true,
21
- blockStyles: {
22
- logic_blocks: {
23
- colourPrimary: '#5C81A6',
24
- colourSecondary: '#4A6D8B',
25
- colourTertiary: '#3B5572',
26
- },
27
- loop_blocks: {
28
- colourPrimary: '#5CA65C',
29
- colourSecondary: '#498949',
30
- colourTertiary: '#3B723B',
31
- },
32
- },
33
- categoryStyles: {
34
- logic_category: { colour: '#5C81A6' },
35
- loop_category: { colour: '#5CA65C' },
36
- },
37
- componentStyles: {
38
- workspaceBackgroundColour: '#f0f0f0',
39
- toolboxBackgroundColour: '#ffffff',
40
- },
41
- });
42
-
43
  // Inject Blockly with theme + renderer
44
  const ws = Blockly.inject(blocklyDiv, {
45
  toolbox,
@@ -61,8 +36,16 @@ const ws = Blockly.inject(blocklyDiv, {
61
  pinch: true
62
  },
63
  renderer: 'zelos',
64
- theme: myTheme,
 
 
 
 
 
 
 
65
  });
 
66
 
67
  const updateCode = () => {
68
  let code = pythonGenerator.workspaceToCode(ws);
 
5
  import { save, load } from './serialization';
6
  import { toolbox } from './toolbox';
7
  import '@blockly/toolbox-search';
8
+ import DarkTheme from '@blockly/theme-dark';
9
  import './index.css';
10
 
11
  // Register the blocks and generator with Blockly
 
15
  // Set up UI elements and inject Blockly
16
  const blocklyDiv = document.getElementById('blocklyDiv');
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  // Inject Blockly with theme + renderer
19
  const ws = Blockly.inject(blocklyDiv, {
20
  toolbox,
 
36
  pinch: true
37
  },
38
  renderer: 'zelos',
39
+ theme: DarkTheme,
40
+ });
41
+
42
+ window.workspace = ws;
43
+
44
+ // Observe any size change to the blockly container
45
+ const observer = new ResizeObserver(() => {
46
+ Blockly.svgResize(ws);
47
  });
48
+ observer.observe(blocklyDiv);
49
 
50
  const updateCode = () => {
51
  let code = pythonGenerator.workspaceToCode(ws);