Skip to content

Commit a79e0f0

Browse files
staredclaude
andcommitted
Implement chart-focused design and auto-execution
Major UI improvements: - Chart displays prominently at top-left aligned, single latest chart only - Console output minimized to small collapsible section at bottom - Auto-execute code when WebR loads and when examples are selected - Remove hardcoded initial code, move to "Getting started" example - Example dropdown always shows active example name (no placeholder text) - All examples properly organized in examples.ts file Technical changes: - Fix missing examples import in App.vue - Update OutputDisplay to show only latest plot with clean styling - Add auto-execution logic to useWebR composable - Set default selected example to 'getting-started' - Remove unnecessary chart containers and frames 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 4da5502 commit a79e0f0

File tree

5 files changed

+105
-122
lines changed

5 files changed

+105
-122
lines changed

src/App.vue

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,11 @@ import FileUpload from './components/FileUpload.vue'
55
import ExampleSelector from './components/ExampleSelector.vue'
66
import OutputDisplay from './components/OutputDisplay.vue'
77
import { useWebR } from './composables/useWebR'
8+
import { examples } from './data/examples'
89
import type { RExample, CsvData } from './types'
910
10-
const code = ref(`# WebR ggplot2 & dplyr Demo
11-
# Select an example or write your own code
12-
13-
library(ggplot2)
14-
library(dplyr)
15-
16-
# Create your first visualization
17-
ggplot(mtcars, aes(x = wt, y = mpg)) +
18-
geom_point() +
19-
theme_minimal()`)
11+
// Start with the first example (getting-started)
12+
const code = ref(examples[0].code)
2013
2114
const {
2215
isReady,
@@ -43,12 +36,16 @@ const handleFileRemoved = () => {
4336
executeCode('if (exists("data")) rm(data)')
4437
}
4538
46-
const handleExampleSelect = (example: RExample) => {
39+
const handleExampleSelect = async (example: RExample) => {
4740
code.value = example.code
41+
// Auto-execute the selected example
42+
if (isReady.value && example.code.trim()) {
43+
await executeCode(example.code)
44+
}
4845
}
4946
5047
onMounted(() => {
51-
initializeWebR()
48+
initializeWebR(code.value)
5249
})
5350
</script>
5451

src/components/ExampleSelector.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const emit = defineEmits<{
77
exampleSelected: [example: RExample]
88
}>()
99
10-
const selectedExample = ref<string>('')
10+
const selectedExample = ref<string>('getting-started')
1111
1212
const currentExample = computed(() => {
1313
return examples.find((ex) => ex.id === selectedExample.value) || null
@@ -28,7 +28,6 @@ const handleExampleChange = () => {
2828
@change="handleExampleChange"
2929
class="select"
3030
>
31-
<option value="">Examples ▼</option>
3231
<option v-for="example in examples" :key="example.id" :value="example.id">
3332
{{ example.title }}
3433
</option>

src/components/OutputDisplay.vue

Lines changed: 74 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,9 @@ const handlePlotError = (event: Event) => {
3636
console.error('Plot load error:', event)
3737
}
3838
39-
const plotMessages = computed(() => {
40-
return props.messages.filter(message => message.type === 'plot')
39+
const latestPlot = computed(() => {
40+
const plots = props.messages.filter(message => message.type === 'plot')
41+
return plots.length > 0 ? plots[plots.length - 1] : null
4142
})
4243
4344
const textMessages = computed(() => {
@@ -65,27 +66,28 @@ watch(
6566
<p>Run some R code to see the output here</p>
6667
</div>
6768

68-
<!-- Display all plots first -->
69-
<div v-for="(message, index) in plotMessages" :key="'plot-' + index" class="message">
70-
<div class="plot-container">
71-
<img
72-
:src="message.content"
73-
alt="R plot"
74-
class="plot-image"
75-
@load="handlePlotLoad"
76-
@error="handlePlotError"
77-
/>
78-
</div>
69+
<!-- Display latest chart prominently -->
70+
<div v-if="latestPlot" class="chart-display">
71+
<img
72+
:src="latestPlot.content"
73+
alt="R plot"
74+
class="chart-image"
75+
@load="handlePlotLoad"
76+
@error="handlePlotError"
77+
/>
7978
</div>
8079

81-
<!-- Single foldable bar for all text output -->
82-
<details v-if="textMessages.length > 0" class="text-output" open>
83-
<summary class="output-summary">Console Output ({{ textMessages.length }})</summary>
84-
<div class="output-text">
85-
<div v-for="(message, index) in textMessages" :key="'text-' + index" class="text-message" :class="message.type">
86-
<div class="message-type">{{ message.type.toUpperCase() }}:</div>
87-
<pre v-if="message.type === 'stdout' || message.type === 'stderr'" class="message-content">{{ message.content }}</pre>
88-
<div v-else class="message-content">{{ message.content }}</div>
80+
<!-- Minimized console output at bottom -->
81+
<details v-if="textMessages.length > 0" class="console-output">
82+
<summary class="console-summary">
83+
<span class="console-icon">📜</span>
84+
Console ({{ textMessages.length }})
85+
</summary>
86+
<div class="console-content">
87+
<div v-for="(message, index) in textMessages" :key="'text-' + index" class="console-message" :class="message.type">
88+
<span class="message-label">{{ message.type.toUpperCase() }}:</span>
89+
<pre v-if="message.type === 'stdout' || message.type === 'stderr'" class="message-text">{{ message.content }}</pre>
90+
<span v-else class="message-text">{{ message.content }}</span>
8991
</div>
9092
</div>
9193
</details>
@@ -103,8 +105,10 @@ watch(
103105
104106
.output-content {
105107
flex: 1;
106-
padding: 1rem;
108+
padding: 0;
107109
overflow-y: auto;
110+
display: flex;
111+
flex-direction: column;
108112
}
109113
110114
.loading {
@@ -142,133 +146,96 @@ watch(
142146
overflow: hidden;
143147
}
144148
145-
.text-output {
146-
border: 1px solid #e5e7eb;
147-
border-radius: 6px;
149+
.chart-display {
150+
flex: 1;
151+
display: flex;
152+
align-items: flex-start;
153+
justify-content: center;
154+
padding: 1rem;
155+
background: #fff;
156+
}
157+
158+
.chart-image {
159+
max-width: 100%;
160+
max-height: 100%;
161+
height: auto;
162+
border-radius: 4px;
163+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
164+
}
165+
166+
.console-output {
167+
border-top: 1px solid #e5e7eb;
148168
background-color: #f9fafb;
169+
margin-top: auto;
149170
}
150171
151-
.output-summary {
152-
padding: 0.75rem;
172+
.console-summary {
173+
padding: 0.5rem 1rem;
153174
cursor: pointer;
154-
font-weight: 600;
155-
font-size: 0.875rem;
175+
font-size: 0.75rem;
156176
color: #6b7280;
157177
background-color: #f3f4f6;
158-
border-bottom: 1px solid #e5e7eb;
159178
user-select: none;
179+
display: flex;
180+
align-items: center;
181+
gap: 0.5rem;
160182
}
161183
162-
.output-summary:hover {
184+
.console-summary:hover {
163185
background-color: #e5e7eb;
164186
}
165187
166-
.output-text {
167-
margin: 0;
168-
padding: 1rem;
169-
font-family: 'Courier New', monospace;
188+
.console-icon {
170189
font-size: 0.875rem;
171-
line-height: 1.5;
172-
white-space: pre-wrap;
173-
color: #374151;
174-
background-color: #fff;
175-
}
176-
177-
.message-other {
178-
border: 1px solid #e5e7eb;
179-
border-radius: 6px;
180-
background-color: #f9fafb;
181-
}
182-
183-
.message-other.success {
184-
border-left: 4px solid #22c55e;
185-
background-color: #f0fdf4;
186190
}
187191
188-
.message-other.error {
189-
border-left: 4px solid #ef4444;
190-
background-color: #fef2f2;
191-
}
192-
193-
.message-other.warning {
194-
border-left: 4px solid #f59e0b;
195-
background-color: #fffbeb;
196-
}
197-
198-
.message-type {
199-
font-size: 0.75rem;
200-
font-weight: 600;
201-
text-transform: uppercase;
202-
padding: 0.5rem 0.75rem;
203-
background-color: #f3f4f6;
204-
color: #6b7280;
205-
border-bottom: 1px solid #e5e7eb;
206-
}
207-
208-
.plot-container {
209-
text-align: center;
210-
padding: 1rem;
211-
background-color: white;
212-
border-radius: 4px;
213-
margin: 0.5rem 0;
214-
}
215-
216-
.plot-image {
217-
max-width: 100%;
218-
height: auto;
219-
border-radius: 4px;
220-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
221-
background-color: white;
222-
display: block;
223-
margin: 0 auto;
192+
.console-content {
193+
max-height: 200px;
194+
overflow-y: auto;
195+
padding: 0.75rem;
196+
background-color: #fff;
197+
border-top: 1px solid #e5e7eb;
224198
}
225199
226-
.text-message {
200+
.console-message {
227201
display: flex;
228202
gap: 0.5rem;
229-
margin-bottom: 0.75rem;
230-
padding-bottom: 0.75rem;
231-
border-bottom: 1px solid #f3f4f6;
203+
margin-bottom: 0.5rem;
204+
font-size: 0.75rem;
205+
line-height: 1.4;
232206
}
233207
234-
.text-message:last-child {
235-
border-bottom: none;
208+
.console-message:last-child {
236209
margin-bottom: 0;
237-
padding-bottom: 0;
238210
}
239211
240-
.text-message .message-type {
241-
font-size: 0.75rem;
212+
.message-label {
242213
font-weight: 600;
243214
text-transform: uppercase;
244215
color: #6b7280;
245-
min-width: 4rem;
216+
min-width: 3rem;
246217
flex-shrink: 0;
247218
}
248219
249-
.text-message.success .message-type {
220+
.console-message.success .message-label {
250221
color: #059669;
251222
}
252223
253-
.text-message.error .message-type {
224+
.console-message.error .message-label {
254225
color: #dc2626;
255226
}
256227
257-
.text-message.warning .message-type {
228+
.console-message.stderr .message-label {
258229
color: #d97706;
259230
}
260231
261-
.text-message.stderr .message-type {
262-
color: #d97706;
263-
}
264-
265-
.text-message .message-content {
232+
.message-text {
266233
flex: 1;
267234
margin: 0;
268235
font-family: 'Courier New', monospace;
269-
font-size: 0.875rem;
270-
line-height: 1.5;
271-
white-space: pre-wrap;
272236
color: #374151;
237+
white-space: pre-wrap;
273238
}
239+
240+
274241
</style>

src/composables/useWebR.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ export const useWebR = () => {
99
let webR: any = null
1010
let shelter: any = null
1111

12-
const initializeWebR = async () => {
12+
const initializeWebR = async (initialCode?: string) => {
1313
try {
1414
isLoading.value = true
1515

@@ -38,6 +38,11 @@ export const useWebR = () => {
3838

3939
isReady.value = true
4040
addMessage('success', 'WebR initialized successfully with ggplot2, dplyr, and ggrepel')
41+
42+
// Auto-execute initial code if provided
43+
if (initialCode && initialCode.trim()) {
44+
await executeCode(initialCode)
45+
}
4146
} catch (error) {
4247
console.error('WebR initialization failed:', error)
4348
addMessage('error', `Failed to initialize WebR: ${error}`)

src/data/examples.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
import type { RExample } from '@/types'
22

33
export const examples: RExample[] = [
4+
{
5+
id: 'getting-started',
6+
title: 'Getting started',
7+
description: 'Simple scatter plot to get you started with ggplot2',
8+
code: `# WebR ggplot2 & dplyr Demo
9+
# Select an example or write your own code
10+
11+
library(ggplot2)
12+
library(dplyr)
13+
14+
# Create your first visualization
15+
ggplot(mtcars, aes(x = wt, y = mpg)) +
16+
geom_point() +
17+
theme_minimal()`,
18+
},
419
{
520
id: 'basic-plot',
621
title: 'Basic example',

0 commit comments

Comments
 (0)