Skip to content

Commit

Permalink
Nice-ish display of provider status
Browse files Browse the repository at this point in the history
  • Loading branch information
sleepyfran committed Sep 16, 2024
1 parent 713695b commit a6ddcb9
Show file tree
Hide file tree
Showing 16 changed files with 257 additions and 16 deletions.
1 change: 0 additions & 1 deletion packages/components/add-provider/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
},
"dependencies": {
"@echo/core-types": "^1.0.0",
"@echo/components-provider-status": "^1.0.0",
"@echo/components-shared-controllers": "^1.0.0",
"@echo/services-bootstrap-runtime": "^1.0.0",
"effect": "^3.6.5",
Expand Down
7 changes: 5 additions & 2 deletions packages/components/add-provider/src/add-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import type { ProviderLoadedEvent } from "./provider-loader";
import "@echo/components-provider-status";
import "./provider-loader";
import "./select-root";
import { EffectConsumer } from "@echo/components-shared-controllers";
Expand Down Expand Up @@ -61,7 +60,11 @@ export class AddProvider extends LitElement {
),
Match.tag(
"ProviderStarted",
() => html`<provider-status></provider-status>`,
() =>
html`<h1>
Provider started! Check the status on the top bar for more
information
</h1>`,
),
Match.exhaustive,
);
Expand Down
1 change: 1 addition & 0 deletions packages/components/icons/ATTRIBUTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
All icons in this package outside of the providers folder are taken from the Pixel Art Icons pack by Gerrit Halfmann, which is licensed under an MIT license. The original pack can be found at https://pixelarticons.com
3 changes: 3 additions & 0 deletions packages/components/icons/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./src/providers";
export * from "./src/done-icon";
export * from "./src/sync-icon";
17 changes: 17 additions & 0 deletions packages/components/icons/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@echo/components-icons",
"private": true,
"version": "1.0.0",
"scripts": {
"lint": "eslint . --ext ts --report-unused-disable-directives --max-warnings 0",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@echo/components-shared-controllers": "^1.0.0",
"@echo/core-types": "^1.0.0",
"@echo/services-bootstrap-runtime": "^1.0.0",
"effect": "^3.6.5",
"lit": "^3.2.0",
"@lit/task": "^1.0.1"
}
}
32 changes: 32 additions & 0 deletions packages/components/icons/src/done-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";

/**
* Icon that shows a check-mark inside a circle.
*/
@customElement("done-icon")
export class DoneIcon extends LitElement {
@property({ type: Number }) size = 24;
@property({ type: String }) color = "currentColor";

render() {
return html`<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${this.size} ${this.size}"
height="${this.size}"
width="${this.size}"
>
<path
d="M17 3H7v2H5v2H3v10h2v2h2v2h10v-2h2v-2h2V7h-2V5h-2V3zm0 2v2h2v10h-2v2H7v-2H5V7h2V5h10zm-9 6h2v2h2v2h-2v-2H8v-2zm8-2h-2v2h-2v2h2v-2h2V9z"
fill="${this.color}"
/>
</svg>`;
}
}

declare global {
interface HTMLElementTagNameMap {
"done-icon": DoneIcon;
}
}
1 change: 1 addition & 0 deletions packages/components/icons/src/providers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./onedrive-icon";
44 changes: 44 additions & 0 deletions packages/components/icons/src/providers/onedrive-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";

/**
* Icon that shows the OneDrive icon.
*/
@customElement("onedrive-icon")
export class OneDriveIcon extends LitElement {
@property({ type: Number }) size = 24;

render() {
return html`<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${this.size} ${this.size}"
height="${this.size}"
width="${this.size * 2}"
>
<g id="STYLE_COLOR">
<path
d="M12.20245,11.19292l.00031-.0011,6.71765,4.02379,4.00293-1.68451.00018.00068A6.4768,6.4768,0,0,1,25.5,13c.14764,0,.29358.0067.43878.01639a10.00075,10.00075,0,0,0-18.041-3.01381C7.932,10.00215,7.9657,10,8,10A7.96073,7.96073,0,0,1,12.20245,11.19292Z"
fill="#0364b8"
/>
<path
d="M12.20276,11.19182l-.00031.0011A7.96073,7.96073,0,0,0,8,10c-.0343,0-.06805.00215-.10223.00258A7.99676,7.99676,0,0,0,1.43732,22.57277l5.924-2.49292,2.63342-1.10819,5.86353-2.46746,3.06213-1.28859Z"
fill="#0078d4"
/>
<path
d="M25.93878,13.01639C25.79358,13.0067,25.64764,13,25.5,13a6.4768,6.4768,0,0,0-2.57648.53178l-.00018-.00068-4.00293,1.68451,1.16077.69528L23.88611,18.19l1.66009.99438,5.67633,3.40007a6.5002,6.5002,0,0,0-5.28375-9.56805Z"
fill="#1490df"
/>
<path
d="M25.5462,19.18437,23.88611,18.19l-3.80493-2.2791-1.16077-.69528L15.85828,16.5042,9.99475,18.97166,7.36133,20.07985l-5.924,2.49292A7.98889,7.98889,0,0,0,8,26H25.5a6.49837,6.49837,0,0,0,5.72253-3.41556Z"
fill="#28a8ea"
/>
</g>
</svg>`;
}
}

declare global {
interface HTMLElementTagNameMap {
"onedrive-icon": OneDriveIcon;
}
}
31 changes: 31 additions & 0 deletions packages/components/icons/src/sync-icon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { LitElement, html } from "lit";
import { customElement, property } from "lit/decorators.js";

/**
* Icon that represents a syncing state.
*/
@customElement("sync-icon")
export class SyncIcon extends LitElement {
@property({ type: Number }) size = 24;

render() {
return html`<svg
fill="none"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 ${this.size} ${this.size}"
height="${this.size}"
width="${this.size}"
>
<path
d="M4 9V7h12V5h2v2h2v2h-2v2h-2V9H4zm12 2h-2v2h2v-2zm0-6h-2V3h2v2zm4 12v-2H8v-2h2v-2H8v2H6v2H4v2h2v2h2v2h2v-2H8v-2h12z"
fill="currentColor"
/>
</svg>`;
}
}

declare global {
interface HTMLElementTagNameMap {
"sync-icon": SyncIcon;
}
}
1 change: 1 addition & 0 deletions packages/components/icons/src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="vite/client" />
7 changes: 7 additions & 0 deletions packages/components/icons/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"include": [
"src",
"index.ts"
],
"extends": "../../../tsconfig.json"
}
1 change: 1 addition & 0 deletions packages/components/provider-status/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@echo/core-types": "^1.0.0",
"@echo/components-icons": "^1.0.0",
"@echo/components-shared-controllers": "^1.0.0",
"@echo/services-bootstrap-runtime": "^1.0.0",
"effect": "^3.6.5",
Expand Down
107 changes: 97 additions & 10 deletions packages/components/provider-status/src/provider-status.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,125 @@
import { MediaProviderStatus } from "@echo/core-types";
import {
MediaProviderStatus,
type ProviderId,
type ProviderStatus,
} from "@echo/core-types";
import { StreamConsumer } from "@echo/components-shared-controllers";
import { LitElement, html } from "lit";
import { LitElement, css, html, nothing } from "lit";
import { customElement } from "lit/decorators.js";
import { map } from "lit/directives/map.js";
import { Match } from "effect";
import "@echo/components-icons";

/**
* Component that displays the status of all active providers.
*/
@customElement("provider-status")
export class ProviderStatus extends LitElement {
@customElement("all-providers-status-bar")
export class AllProvidersStatusBar extends LitElement {
private _providerStatus = new StreamConsumer(
this,
MediaProviderStatus.observe,
);

static styles = css`
.provider-container {
display: flex;
align-items: center;
margin: 0.5rem 0;
}
.provider-status {
position: relative;
}
.provider-status-icon {
position: absolute;
bottom: -5px;
right: 5px;
}
.syncing-icon {
animation: blinking 1s infinite;
}
@keyframes blinking {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
`;

render() {
return this._providerStatus.render({
initial: () => html`<p>Retrieving syncing status...</p>`,
initial: () => nothing,
item: (status) =>
map(
status,
([providerId, providerStatus]) => html`
<h4>${providerId}</h4>
<pre>${JSON.stringify(providerStatus, null, 2)}</pre>
<div class="provider-container">
<div
class="provider-status"
title=${this._providerStatusTitle(providerId, providerStatus)}
>
${this._renderProviderIcon(providerId)}
${this._renderProviderStatus(providerStatus)}
</div>
</div>
`,
),
complete: () => html`<p>Complete</p>`,
error: (error) => html`<p style="{ color: 'red' }">Error: ${error}</p>`,
complete: () => nothing,
error: () => nothing,
});
}

private _renderProviderIcon(providerId: ProviderId) {
switch (providerId) {
case "onedrive":
return html`<onedrive-icon size="32"></onedrive-icon>`;
default:
return nothing;
}
}

private _renderProviderStatus(providerStatus: ProviderStatus) {
return Match.value(providerStatus).pipe(
Match.tag(
"syncing",
() =>
html`<sync-icon
class="provider-status-icon syncing-icon"
></sync-icon>`,
),
Match.tag(
"synced",
() => html`<done-icon class="provider-status-icon"></done-icon>`,
),
Match.orElse(() => nothing),
);
}

private _providerStatusTitle(
providerId: ProviderId,
providerStatus: ProviderStatus,
) {
return Match.value(providerStatus).pipe(
Match.tag(
"synced",
(status) =>
`${providerId} has finished syncing. Synced ${status.syncedFiles} files`,
),
Match.orElse(() => `Syncing ${providerId}`),
);
}
}

declare global {
interface HTMLElementTagNameMap {
"provider-status": ProviderStatus;
"all-provider-status-bar": AllProvidersStatusBar;
}
}
1 change: 1 addition & 0 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@echo/components-add-provider": "^1.0.0",
"@echo/components-library": "^1.0.0",
"@echo/components-player": "^1.0.0",
"@echo/components-provider-status": "^1.0.0",
"@echo/components-shared-controllers": "^1.0.0",
"@echo/core-types": "^1.0.0",
"@echo/services-bootstrap-runtime": "^1.0.0",
Expand Down
17 changes: 15 additions & 2 deletions packages/web/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { LitElement, html } from "lit";
import { LitElement, css, html } from "lit";
import { customElement } from "lit/decorators.js";
import { AppInit } from "@echo/core-types";
import { EffectConsumer } from "@echo/components-shared-controllers";
import "@echo/components-add-provider";
import "@echo/components-library";
import "@echo/components-player";
import "@echo/components-provider-status";

/**
* Root element of the application.
Expand All @@ -13,12 +14,24 @@ import "@echo/components-player";
export class MyElement extends LitElement {
private _init = new EffectConsumer(this, AppInit.init);

static styles = css`
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 1rem;
}
`;

render() {
return this._init.render({
initial: () => html`<h1>Initializing Echo...</h1>`,
complete: () => html`
<div>
<add-provider></add-provider>
<header>
<add-provider></add-provider>
<all-providers-status-bar></all-providers-status-bar>
</header>
<echo-player></echo-player>
<user-library></user-library>
</div>
Expand Down
2 changes: 1 addition & 1 deletion tools/plop-templates/components/template/vite-env.d.ts.hbs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
/// <reference types="vite/client" />
/// <reference types="vite/client" />

0 comments on commit a6ddcb9

Please sign in to comment.