Skip to content

Commit

Permalink
chore(widget): extract detail view and font into separate files (#184)
Browse files Browse the repository at this point in the history
* extract detail view and font
* update readme dev instructions
* various other refactors
  • Loading branch information
andrewedstrom authored Nov 21, 2023
1 parent 069434d commit 543ea22
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 140 deletions.
32 changes: 29 additions & 3 deletions packages/widget/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,40 @@ The first time you run the tests, you will need to install browsers for Playwrig
pnpm run install:browsers
```

After that, you can run the tests with
#### All tests

```shell
pnpm run test
```

To see the tests run visually, step-by-step, you can open the Playwright UI like this:
#### Unit tests

```shell
pnpm run test:ui
pnpm run test:unit
```

#### Integration tests

To see the tests run in step-by-step, you can open the Playwright UI like this. The Playwright UI is an amazing tool
because it lets you see screenshots of each step of the test, and it automatically reruns the tests when you make
changes.

```shell
pnpm run test:integration:ui
```

Alternatively, you can run the tests headlessly and see results in the terminal:

```shell
pnpm run test:integration
```

By default, the tests only run in chromium locally. To run in chromium, firefox, and webkit, you can run:

```shell
# Headless
pnpm run test:integration:all-browsers

# With UI
pnpm run test:integration:ui:all-browsers
```
3 changes: 2 additions & 1 deletion packages/widget/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"test:unit": "ava",
"test:integration": "playwright test",
"test:integration:all-browsers": "ALL_BROWSERS=true playwright test",
"test:integration:ui": "playwright test --ui"
"test:integration:ui": "playwright test --ui",
"test:integration:ui:all-browsers": "ALL_BROWSERS=true playwright test --ui"
},
"exports": {
".": "./dist/index.js"
Expand Down
8 changes: 4 additions & 4 deletions packages/widget/src/assets/close-details-button.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { svg } from 'lit';

export const closeDetailsButton = (color: string) => svg`
<svg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'>
<circle stroke='${color}' cx='15' cy='15' r='14.5'/>
<path fill='${color}' fill-rule='evenodd' clip-rule='evenodd' d='M16.41 15L20.7 10.71C20.89 10.53 21 10.28 21 10C21 9.45 20.55 9 20 9C19.72 9 19.47 9.11 19.29 9.29L15 13.59L10.71 9.29C10.53 9.11 10.28 9 10 9C9.45 9 9 9.45 9 10C9 10.28 9.11 10.53 9.29 10.71L13.59 15L9.3 19.29C9.11 19.47 9 19.72 9 20C9 20.55 9.45 21 10 21C10.28 21 10.53 20.89 10.71 20.71L15 16.41L19.29 20.7C19.47 20.89 19.72 21 20 21C20.55 21 21 20.55 21 20C21 19.72 20.89 19.47 20.71 19.29L16.41 15Z'/>
</svg>
<svg width='30' height='30' viewBox='0 0 30 30' fill='none' xmlns='http://www.w3.org/2000/svg'>
<circle stroke='${color}' cx='15' cy='15' r='14.5'/>
<path fill='${color}' fill-rule='evenodd' clip-rule='evenodd' d='M16.41 15L20.7 10.71C20.89 10.53 21 10.28 21 10C21 9.45 20.55 9 20 9C19.72 9 19.47 9.11 19.29 9.29L15 13.59L10.71 9.29C10.53 9.11 10.28 9 10 9C9.45 9 9 9.45 9 10C9 10.28 9.11 10.53 9.29 10.71L13.59 15L9.3 19.29C9.11 19.47 9 19.72 9 20C9 20.55 9.45 21 10 21C10.28 21 10.53 20.89 10.71 20.71L15 16.41L19.29 20.7C19.47 20.89 19.72 21 20 21C20.55 21 21 20.55 21 20C21 19.72 20.89 19.47 20.71 19.29L16.41 15Z'/>
</svg>
`;
45 changes: 26 additions & 19 deletions packages/widget/src/detail-navigation-header.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { nothing, svg, SVGTemplateResult } from 'lit';
import { DisplayObject, TYPE_DISPLAY_OPTIONS } from './util';

const TIMELINE_WIDTH: number = 368;
const FIRST_NODE_X: number = 6.5;
const LAST_NODE_X: number = TIMELINE_WIDTH - 9.5;

const backButton = (
allNodes: DisplayObject[],
selectedNode: DisplayObject,
Expand Down Expand Up @@ -35,49 +39,52 @@ const forwardButton = (
</svg>`;
};

function getNodeX(i: number, numberOfNodes: number): number {
const interval = (LAST_NODE_X - FIRST_NODE_X) / (numberOfNodes - 1);
return FIRST_NODE_X + interval * i;
}

const timeline = (
allNodes: DisplayObject[],
selectedNode: DisplayObject,
updateSelectedNode: (node: DisplayObject) => void,
) => {
const width = 368;
const firstXPosition: number = 6.5;
const lastXPosition: number = 358.5;

const timelineNodes = allNodes.map((node, i) => {
let x = firstXPosition;
if (i > 0) {
const spaceBetweenNodes: number = (lastXPosition - firstXPosition) / (allNodes.length - 1);
x = firstXPosition + spaceBetweenNodes * i;
}
const timelineNodes: SVGTemplateResult[] = allNodes.map((node, i) => {
const x = getNodeX(i, allNodes.length);
const displayOpts = TYPE_DISPLAY_OPTIONS[node.type];
const color = displayOpts.detailBackgroundColor ?? displayOpts.backgroundColor;

const selectedNodeIndicator =
node.nodeId === selectedNode.nodeId
? svg`
<circle class='selected-node-outline' cx='${x}' cy='6.5' r='5.5' stroke='${color}'/>
const thisNodeIsSelected: boolean = node.nodeId === selectedNode.nodeId;
const selectedNodeIndicator: SVGTemplateResult | typeof nothing = thisNodeIsSelected
? svg`<circle class='selected-node-outline' cx='${x}' cy='6.5' r='5.5' stroke='${color}'/>
<path class='selected-node-line' d='M${x} 7L${x} 35' stroke='${color}' stroke-linecap='round'/>`
: nothing;
: nothing;

return svg`
<circle class='timeline-node clickable' cx='${x}' cy='6.5' r='3.5' fill='${color}'
@click='${() => updateSelectedNode(node)}'/>
${selectedNodeIndicator}
`;
});

const timeline = svg`<line x1='3' y1='6.75' x2='${
TIMELINE_WIDTH - 6
}' y2='6.75' stroke='#777777' stroke-width='0.5'/`;

return svg`
<svg width='${width}' height='35' viewBox='0 0 ${width} 35' fill='none' xmlns='http://www.w3.org/2000/svg' style='align-self: end;'>
<line x1='3' y1='6.75' x2='${width - 6}' y2='6.75' stroke='#777777' stroke-width='0.5'/>
<svg width='${TIMELINE_WIDTH}' height='35' viewBox='0 0 ${TIMELINE_WIDTH} 35' fill='none' xmlns='http://www.w3.org/2000/svg' style='align-self: end;'>
${timeline}>
${timelineNodes}
</svg>`;
};

export const renderDetailNavigationHeader: (
type RenderDetailNavigationHeader = (
allNodes: DisplayObject[],
selectedNode: DisplayObject,
updateSelectedNode: (node: DisplayObject) => void,
) => SVGTemplateResult = (
) => SVGTemplateResult;

export const renderDetailNavigationHeader: RenderDetailNavigationHeader = (
allNodes: DisplayObject[],
selectedNode: DisplayObject,
updateSelectedNode,
Expand Down
68 changes: 68 additions & 0 deletions packages/widget/src/detail-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { html, HTMLTemplateResult } from 'lit';
import { DisplayObject, filterMetadataEntries, TYPE_DISPLAY_OPTIONS } from './util';
import { renderDetailNavigationHeader } from './detail-navigation-header';
import { closeDetailsButton } from './assets';

export function renderDetailsView(
selectedNode: DisplayObject,
allNodes: DisplayObject[],
updateSelectedNode: (node: DisplayObject) => void,
closeDetailsView: () => void,
) {
const opts = TYPE_DISPLAY_OPTIONS[selectedNode.type];
const metadataEntries: [string, any][] = filterMetadataEntries(selectedNode);

const metadataBody: HTMLTemplateResult =
metadataEntries.length > 0 ? createMetadataGrid(metadataEntries) : emptyMetadataMessage();

const backgroundColor = opts.detailBackgroundColor || opts.backgroundColor;
const textColor = opts.detailTextColor || opts.textColor;

return html`
<div class="detail-timeline">
${renderDetailNavigationHeader(allNodes, selectedNode, updateSelectedNode)}
</div>
<div class="detail-header" style="background: ${backgroundColor};">
<span style="color: ${textColor};"> ${opts.longLabel} </span>
<div class="close-button clickable" @click="${closeDetailsView}">
${closeDetailsButton(textColor)}
</div>
</div>
<div class="detail-body">${metadataBody}</div>
`;
}

const createMetadataGrid = (metadataEntries: [string, any][]): HTMLTemplateResult => {
const gridItems: HTMLTemplateResult[] = metadataEntries.map((entry, index) =>
createGridItem(entry, index),
);
return html` <div class="metadata-grid">${gridItems}</div>`;
};

const createGridItem = ([key, value]: [string, any], index: number): HTMLTemplateResult => {
if (Array.isArray(value)) {
const values: any[] = value; // rename since it's actually plural
return html`
<div
class="metadata-grid-item key"
style="grid-row-start: ${index + 1}; grid-row-end: ${index + values.length + 1};"
>
${key}
</div>
${values.map((val) => html` <div class="metadata-grid-item value content">${val}</div>`)}
`;
}

return html`
<div class="metadata-grid-item key">${key}</div>
<div class="metadata-grid-item value">${value}</div>
`;
};

const emptyMetadataMessage = (): HTMLTemplateResult => {
return html` <div class="metadata-item">
<div class="metadata-key">no metadata found</div>
</div>`;
};
Loading

0 comments on commit 543ea22

Please sign in to comment.