Skip to content

Commit 257e3b5

Browse files
authored
Open multiple sagas next to each other, revamped "home" recipe (#80)
* minor fixes in recipe-list use * added a lit window manager * opening sagas works * add close button
1 parent 78a1da6 commit 257e3b5

File tree

13 files changed

+240
-110
lines changed

13 files changed

+240
-110
lines changed
Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
<!DOCTYPE html>
1+
<!doctype html>
22
<html lang="en">
3-
<head>
4-
<meta charset="utf-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, shrink-to-fit=no"
8+
/>
69
<title></title>
7-
<link rel="stylesheet" href="src/main.css">
10+
<link rel="stylesheet" href="src/main.css" />
811
<script type="module" src="src/main.ts"></script>
9-
</head>
10-
<body class="theme">
11-
<com-app></com-app>
12-
</body>
12+
</head>
13+
<body class="theme">
14+
<common-window-manager id="window-manager"></common-window-manager>
15+
</body>
1316
</html>

typescript/packages/lookslike-high-level/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
"wireit": {
2727
"build": {
2828
"dependencies": [
29-
"../common-ui:build"
29+
"../common-ui:build",
30+
"../common-frp:build",
31+
"../lookslike-sagas:build"
3032
],
3133
"command": "vite build"
3234
},
@@ -36,6 +38,7 @@
3638
},
3739
"dependencies": {
3840
"@commontools/common-ui": "^0.0.1",
39-
"@commontools/common-frp": "^0.0.1"
41+
"@commontools/common-frp": "^0.0.1",
42+
"@commontools/lookslike-sagas": "^0.0.1"
4043
}
4144
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * as components from "./components/index.js";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * as CommonWindowManager from "./window-manager.js";
2+
export * as CommonSagaLink from "./saga-link.js";
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { LitElement, html, css } from "lit";
2+
import { customElement, property } from "lit/decorators.js";
3+
import { render } from "@commontools/common-ui";
4+
import { Gem, ID, NAME } from "../recipe.js";
5+
6+
export const sagaLink = render.view("common-saga-link", {
7+
saga: { type: "object" },
8+
name: { tyoe: "string" },
9+
});
10+
11+
@customElement("common-saga-link")
12+
export class CommonSagaLink extends LitElement {
13+
static override styles = css`
14+
a {
15+
color: #3366cc;
16+
text-decoration: none;
17+
}
18+
a:hover {
19+
text-decoration: underline;
20+
}
21+
`;
22+
23+
@property({ type: String })
24+
saga: Gem | undefined = undefined;
25+
26+
@property({ type: String })
27+
name: string | undefined = undefined;
28+
29+
handleClick(e: Event) {
30+
e.preventDefault();
31+
this.dispatchEvent(
32+
new CustomEvent("open-saga", {
33+
detail: { saga: this.saga },
34+
bubbles: true,
35+
composed: true,
36+
})
37+
);
38+
}
39+
40+
override render() {
41+
console.log("rendering saga link", this.saga, this.name);
42+
if (!this.saga) return html``;
43+
return html`
44+
<a href="#${this.saga[ID]}" @click="${this.handleClick}">
45+
${this.name ?? this.saga[NAME]}
46+
</a>
47+
`;
48+
}
49+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { LitElement, html, css } from "lit";
2+
import { customElement, property } from "lit/decorators.js";
3+
import { view, tags, render } from "@commontools/common-ui";
4+
import { isGem, Gem, ID } from "../recipe.js";
5+
const { binding } = view;
6+
const { include } = tags;
7+
8+
@customElement("common-window-manager")
9+
export class CommonWindowManager extends LitElement {
10+
static override styles = css`
11+
:host {
12+
display: flex;
13+
overflow-x: auto;
14+
width: 100%;
15+
padding: 20px 0; /* Add vertical padding */
16+
}
17+
.window {
18+
flex: 0 0 auto;
19+
width: 300px;
20+
margin-left: 20px;
21+
padding: 10px;
22+
border: 1px solid #e0e0e0;
23+
border-radius: 8px;
24+
background-color: rgba(255, 255, 255, 0.8);
25+
backdrop-filter: blur(10px);
26+
box-shadow:
27+
0 10px 20px rgba(0, 0, 0, 0.1),
28+
0 6px 6px rgba(0, 0, 0, 0.1),
29+
0 0 0 1px rgba(0, 0, 0, 0.05);
30+
transition: all 0.3s ease;
31+
}
32+
.close-button {
33+
position: absolute;
34+
top: 8px;
35+
right: 8px;
36+
width: 16px;
37+
height: 16px;
38+
border-radius: 50%;
39+
background-color: rgba(0, 0, 0, 0.1);
40+
border: none;
41+
cursor: pointer;
42+
display: flex;
43+
align-items: center;
44+
justify-content: center;
45+
font-size: 12px;
46+
color: rgba(0, 0, 0, 0.4);
47+
font-weight: bold;
48+
transition: all 0.2s ease;
49+
}
50+
.close-button:hover {
51+
background-color: rgba(0, 0, 0, 0.15);
52+
color: rgba(0, 0, 0, 0.6);
53+
}
54+
`;
55+
56+
@property({ type: Array })
57+
sagas: Gem[] = [];
58+
59+
private renderedSagas: { [key: string]: HTMLElement } = {};
60+
61+
override render() {
62+
return html`
63+
${this.sagas.map((saga) => {
64+
if (!this.renderedSagas[saga[ID]])
65+
this.renderedSagas[saga[ID]] = render.render(
66+
include({ content: binding("UI") }),
67+
{
68+
UI: saga.UI,
69+
}
70+
) as HTMLElement;
71+
72+
return html`
73+
<div class="window" id="${saga[ID]}">
74+
<button class="close-button" @click="${this.onClose}">×</button>
75+
<common-screen-element>
76+
${this.renderedSagas[saga[ID]]}
77+
</common-screen-element>
78+
</div>
79+
`;
80+
})}
81+
`;
82+
}
83+
84+
openSaga(saga: Gem) {
85+
this.sagas = [...this.sagas, saga];
86+
this.updateComplete.then(() => {
87+
const newWindow = this.renderRoot.querySelector(".window:last-child");
88+
if (newWindow) {
89+
newWindow.scrollIntoView({
90+
behavior: "smooth",
91+
block: "nearest",
92+
inline: "start",
93+
});
94+
}
95+
});
96+
}
97+
98+
onClose(e: Event) {
99+
const id = (e.currentTarget as HTMLElement).parentElement?.id;
100+
if (id) {
101+
this.sagas = this.sagas.filter((saga) => saga[ID] + "" !== id);
102+
}
103+
}
104+
105+
override connectedCallback() {
106+
super.connectedCallback();
107+
this.addEventListener("open-saga", this.handleAddWindow);
108+
}
109+
110+
override disconnectedCallback() {
111+
super.disconnectedCallback();
112+
this.removeEventListener("open-saga", this.handleAddWindow);
113+
}
114+
115+
private handleAddWindow(e: Event) {
116+
const saga = (e as CustomEvent).detail.saga;
117+
if (isGem(saga)) {
118+
this.openSaga(saga);
119+
}
120+
}
121+
}

typescript/packages/lookslike-high-level/src/data.ts

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@ const { state } = signal;
55

66
import { todoList, todoTask } from "./recipes/todo-list.js";
77
import "./recipes/todo-list-as-task.js"; // Necessary, so that suggestions are indexed.
8-
import { recipeItem, recipeList } from "./recipes/recipe-list.js";
9-
import { Recipe } from "./recipe.js";
8+
import { sagaList } from "./recipes/saga-list.js";
109

1110
export const keywords: { [key: string]: string[] } = {
1211
groceries: ["grocery list"],
@@ -19,31 +18,28 @@ export function addGems(gems: { [key: string]: Gem }) {
1918
dataGems.send({ ...dataGems.get(), ...gems });
2019
}
2120

22-
const recipes: { [name: string]: Recipe } = {
21+
export function getGemByName(name: string): Gem | undefined {
22+
return dataGems.get()[name];
23+
}
24+
25+
const recipes: { [name: string]: Gem } = {
2326
"todo list": todoList({
2427
items: ["Buy groceries", "Walk the dog", "Wash the car"].map((item) =>
2528
todoTask({
2629
title: item,
2730
done: false,
28-
}),
31+
})
2932
),
3033
}),
3134
"grocery list": todoList({
3235
items: ["milk", "eggs", "bread"].map((item) =>
3336
todoTask({
3437
title: item,
3538
done: false,
36-
}),
39+
})
3740
),
3841
}),
42+
home: sagaList({ sagas: dataGems }),
3943
};
4044

41-
recipes["recipe list"] = recipeList({
42-
items: Object.keys(recipes).map((name) =>
43-
recipeItem({
44-
title: name,
45-
}),
46-
),
47-
});
48-
4945
addGems(recipes);

typescript/packages/lookslike-high-level/src/index.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
import { view, tags, render } from "@commontools/common-ui";
2-
import { signal } from "@commontools/common-frp";
3-
import { dataGems } from "./data.js";
4-
const { binding } = view;
5-
const { include } = tags;
6-
const { computed, isSignal } = signal;
7-
8-
// Hard coded todo list as UI to show
9-
const UI = computed([dataGems], (dataGems) => {
10-
if (dataGems.length === 0) return null;
11-
12-
let UI = dataGems["recipe list"]?.UI;
13-
if (isSignal(UI)) UI = UI.get();
14-
return UI;
1+
export { components } from "@commontools/common-ui";
2+
import { CommonWindowManager } from "./components/window-manager.js";
3+
export { components as myComponents } from "./components.js";
4+
import { getGemByName } from "./data.js";
5+
6+
document.addEventListener("DOMContentLoaded", () => {
7+
const windowManager = document.getElementById(
8+
"window-manager"
9+
)! as CommonWindowManager;
10+
console.log(getGemByName("home"));
11+
windowManager.openSaga(getGemByName("home")!);
1512
});
16-
17-
// Render the UI by including the recipe's UI
18-
const element = render.render(include({ content: binding("UI") }), {
19-
UI,
20-
});
21-
22-
document.body.appendChild(element);

typescript/packages/lookslike-high-level/src/recipe.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ export type Gem = {
2323
[NAME]?: string;
2424
} & Bindings;
2525

26+
export function isGem(value: any): value is Gem {
27+
return typeof value === "object" && ID in value && TYPE in value;
28+
}
29+
2630
// Readwrite signals are inputs that are passed through to the output
2731
export type Recipe = (inputs: RecipeInputs) => Gem;
2832

0 commit comments

Comments
 (0)