Skip to content

Commit

Permalink
feat(editor): style script run output
Browse files Browse the repository at this point in the history
  • Loading branch information
sabberworm committed Oct 14, 2024
1 parent 23ac319 commit 372d455
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 25 deletions.
7 changes: 6 additions & 1 deletion mock/mock-response.jsonl

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/main/frontend/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ const RootElement = styled('div')`
grid-column: span 2;
}
}
fieldset {
border-radius: 6px;
}
`;

export const App: FC<{ runEndpoint: string }> = props => {
Expand Down
5 changes: 4 additions & 1 deletion src/main/frontend/coral/custom-elements.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,10 @@ declare module 'react' {
'coral-checkbox-label': DetailedHTMLProps<HTMLAttributes<HTMLElement> & { class?: string }, HTMLElement>;
'coral-switch': DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement> & { class?: string }, HTMLInputElement>;
'coral-wait': DetailedHTMLProps<HTMLAttributes<HTMLElement> & { class?: string }, HTMLElement>;
'coral-icon': DetailedHTMLProps<HTMLAttributes<HTMLElement> & { icon: CoralIcon; class?: string }, HTMLElement>;
'coral-icon': DetailedHTMLProps<
HTMLAttributes<HTMLElement> & { icon: CoralIcon; size?: CoralIconSize; class?: string },
HTMLElement
>;
'coral-selectlist': DetailedHTMLProps<SelectHTMLAttributes<HTMLSelectElement> & { class?: string }, HTMLSelectElement>;
'coral-selectlist-item': DetailedHTMLProps<OptionHTMLAttributes<HTMLOptionElement> & { class?: string }, HTMLSelectElement>;
'coral-popover': DetailedHTMLProps<
Expand Down
14 changes: 8 additions & 6 deletions src/main/frontend/model/Run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@ export interface LogMessage {
exception?: string;
}

export type MimeType = `${'text' | 'application' | 'image' | 'video' | 'audio'}/${string}` | `text/${string};charset=utf-8`;

export interface FileMessage {
type: 'file';
name: string;
mime: string;
mime: MimeType;
data?: string;
blob?: Blob;
}

export type PrintMessage = string;

function decodeBase64(base64: string) {
const raw = window.atob(base64);
const rawLength = raw.length;
Expand Down Expand Up @@ -54,8 +58,6 @@ async function* streamingFetch<T>(response: Response) {
}
}

type PrintMessage = string;

export type Message = LogMessage | FileMessage | PrintMessage;

export class Run {
Expand All @@ -65,12 +67,12 @@ export class Run {

public messageHandler?: (message: Message | null) => void;

constructor(response: Response) {
constructor(response: Promise<Response>) {
this.loadMessages(response).catch(e => console.error('Error loading messages', e));
}

private async loadMessages(response: Response) {
for await (const message of streamingFetch<Message>(response)) {
private async loadMessages(response: Promise<Response>) {
for await (const message of streamingFetch<Message>(await response)) {
if (typeof message !== 'string') {
if (message.type === 'file' && message.data) {
message.blob = new Blob([decodeBase64(message.data)], { type: message.mime });
Expand Down
2 changes: 1 addition & 1 deletion src/main/frontend/sections/Runner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const Runner: FC = () => {
const [runs, setRuns] = useState<Run[]>([]);

async function runWith(data: FormData) {
const response = await fetch(endpoint, {
const response = fetch(endpoint, {
method: 'POST',
body: data,
});
Expand Down
87 changes: 87 additions & 0 deletions src/main/frontend/sections/output/FileMessageOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React, { FC } from 'react';

import { styled } from 'goober';

import { FileMessage, MimeType } from '../../model/Run';
import { CoralIcon } from '../../coral/custom-elements';

const Elm = styled('div')`
background: #666;
color: white;
padding: 1em;
white-space: pre-wrap;
word-break: break-all;
border: 2px solid #0002;
border-radius: 12px;
display: grid;
gap: 6px;
grid-template-areas: 'icon name . download' 'icon type . download';
grid-template-columns: max-content max-content 1fr max-content;
> coral-icon {
grid-area: icon;
position: relative;
top: 0.3em;
}
> .name {
grid-area: name;
}
> .type {
grid-area: type;
}
> a {
grid-area: download;
}
`;

function iconFor(mimeType: MimeType): CoralIcon {
const [kind, ...rest] = mimeType.split('/');
let [type] = rest;
[type] = type!.split(';');
let icon: CoralIcon = 'file';
if (kind === 'application') {
icon = 'fileCode';
if (type === 'pdf') {
icon = 'filePDF';
}
if (type === 'json') {
icon = 'fileJson';
}
if (type === 'xhtml+xml') {
icon = 'fileHTML';
}
if (['zip', 'vnd.rar', 'x-compressed', 'x-gzip-compressed'].includes(type!)) {
icon = 'fileZip';
}
}
if (kind === 'text') {
icon = 'fileTxt';
if (type === 'csv') {
icon = 'fileCSV';
}
if (type === 'html') {
icon = 'fileHTML';
}
}
if (kind === 'image') {
icon = 'image';
}

return icon;
}

export const FileMessageOutput: FC<{ message: FileMessage }> = ({ message }) => {
const icon = iconFor(message.mime);

return (
<Elm>
<coral-icon icon={icon} size="L" />
<span className="name">{message.name}</span>
<small className="type">{message.mime}</small>
<a href={URL.createObjectURL(message.blob!)} download={message.name}>
<button type="button" is="coral-button" icon="download">
Download
</button>
</a>
</Elm>
);
};
76 changes: 76 additions & 0 deletions src/main/frontend/sections/output/LogMessageOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import React, { FC } from 'react';

import { styled } from 'goober';
import { useToggle } from '@uidotdev/usehooks';

import { LogMessage } from '../../model/Run';

const Elm = styled('div')`
display: grid;
grid-template-columns: min-content 1fr min-content;
align-items: baseline;
gap: 6px;
> .level {
text-transform: uppercase;
display: inline-block;
border: 1px solid #00000022;
padding: 0 0.3em;
border-radius: 8px;
background: #ddebf7;
&.debug {
background: #5b9bd5;
}
&.info {
background: #70ad47;
}
&.warn {
background: #ffc000;
}
&.error {
background: #ff3300;
color: white;
}
}
> .message {
word-break: break-all;
}
> .ex-toggle {
font-size: 1.4em;
font-weight: bold;
background: #ff3300;
color: white;
display: inline-block;
border: 0;
aspect-ratio: 1;
border-radius: 50%;
padding: 0 6px;
text-align: center;
}
&:has(.exception) > .ex-toggle {
color: #ff3300;
background: white;
}
> .exception {
grid-column: span 3;
white-space: pre-wrap;
word-break: break-all;
font-family: monospace;
}
`;

export const LogMessageOutput: FC<{ message: LogMessage }> = ({ message }) => {
const [expanded, toggleExpanded] = useToggle(false);

return (
<Elm>
<span className={`level ${message.level}`}>{message.level}</span>
<span className="message">{message.message}</span>
{message.exception ? (
<button className="ex-toggle" onClick={() => toggleExpanded()}>
!!
</button>
) : undefined}
{expanded ? <div className="exception">{message.exception}</div> : undefined}
</Elm>
);
};
11 changes: 10 additions & 1 deletion src/main/frontend/sections/output/MessageOutput.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import React, { FC } from 'react';

import { Message } from '../../model/Run';
import { PrintMessageOutput } from './PrintMessageOutput';
import { FileMessageOutput } from './FileMessageOutput';
import { LogMessageOutput } from './LogMessageOutput';

export const MessageOutput: FC<{ message: Message }> = ({ message }) => {
return <pre>{JSON.stringify(message)}</pre>;
if (typeof message === 'string') {
return <PrintMessageOutput message={message} />;
}
if (message.type === 'file') {
return <FileMessageOutput message={message} />;
}
return <LogMessageOutput message={message} />;
};
18 changes: 18 additions & 0 deletions src/main/frontend/sections/output/PrintMessageOutput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React, { FC } from 'react';

import { styled } from 'goober';

import { PrintMessage } from '../../model/Run';

const Elm = styled('div')`
background: #ccc;
padding: 0.4em;
white-space: pre-wrap;
word-break: break-all;
border: 2px solid #0002;
border-radius: 12px;
`;

export const PrintMessageOutput: FC<{ message: PrintMessage }> = ({ message }) => {
return <Elm>{message}</Elm>;
};
58 changes: 44 additions & 14 deletions src/main/frontend/sections/output/RunOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,32 @@ import { styled } from 'goober';
import { Run } from '../../model/Run';
import { MessageOutput } from './MessageOutput';

const Elm = styled('div')``;
const Elm = styled('details')`
border: 1px solid #c0c0c0;
border-radius: 6px;
padding: 1em;
margin-bottom: 6px;
> summary {
display: grid;
align-items: baseline;
grid-auto-flow: column;
grid-template-columns: 22px 1fr;
&::before {
content: '▶';
}
h3 {
margin: 0;
}
margin: 0.5em 0;
}
&[open] > summary::before {
content: '▼';
}
> .content {
display: grid;
gap: 6px;
}
`;

export const RunOutput: FC<{ run: Run }> = ({ run }) => {
const [messages, setMessages] = useState(run.messages);
Expand All @@ -21,19 +46,24 @@ export const RunOutput: FC<{ run: Run }> = ({ run }) => {
}, [run]);

return (
<Elm>
<h3>
{finished ? undefined : (
<>
<coral-wait></coral-wait>{' '}
</>
)}
{run.started.toLocaleString('de-CH')}
</h3>

{messages.map((msg, i) => (
<MessageOutput key={i} message={msg} />
))}
<Elm open>
<summary>
<h3>
{finished ? (
<span className="finished"></span>
) : (
<>
<coral-wait></coral-wait>{' '}
</>
)}
{run.started.toLocaleString()}
</h3>
</summary>
<div className="content">
{messages.map((msg, i) => (
<MessageOutput key={i} message={msg} />
))}
</div>
</Elm>
);
};
2 changes: 1 addition & 1 deletion src/main/frontend/widgets/StepEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const Elm = styled('div')`
--contrast-color: black;
}
details {
> details {
border-radius: 4px;
background-color: var(--accent-color);
Expand Down

0 comments on commit 372d455

Please sign in to comment.