Frontend responsive UI tweaks (#20)

This commit is contained in:
sua yoo 2021-11-22 10:25:34 -08:00 committed by GitHub
parent 14f2d13a73
commit 5722909157
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 215 additions and 87 deletions

View File

@ -2,12 +2,15 @@
<html data-theme="light">
<head>
<meta charset="utf-8" />
<title>Demo</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, viewport-fit=cover"
/>
<title>Browsertrix Cloud</title>
<base href="/" />
<script src="/main.js"></script>
</head>
<body class="min-w-screen min-h-screen">
<browsertrix-app
class="flex flex-col min-h-screen bg-blue-400"
></browsertrix-app>
<body>
<browsertrix-app></browsertrix-app>
</body>
</html>

View File

@ -10,6 +10,7 @@
"@lit/localize": "^0.11.1",
"@shoelace-style/shoelace": "^2.0.0-beta.61",
"axios": "^0.22.0",
"color": "^4.0.1",
"lit": "^2.0.0",
"lit-element-router": "^2.0.3",
"path-parser": "^6.1.0",
@ -30,6 +31,7 @@
"devDependencies": {
"@esm-bundle/chai": "^4.3.4-fix.0",
"@lit/localize-tools": "^0.5.0",
"@types/color": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"@web/dev-server-esbuild": "^0.2.16",

View File

@ -1,3 +1,4 @@
import type { TemplateResult } from "lit";
import { msg, updateWhenLocaleChanges } from "@lit/localize";
import "./shoelace";
@ -85,73 +86,109 @@ export class App extends LiteElement {
render() {
return html`
${this.renderNavBar()}
<div class="w-full h-full px-12 py-12">${this.renderPage()}</div>
<footer class="flex justify-center p-4">
<locale-picker></locale-picker>
</footer>
<style>
${theme}
</style>
<div class="min-w-screen min-h-screen flex flex-col">
${this.renderNavBar()}
<main class="relative flex-auto flex">${this.renderPage()}</main>
<footer class="flex justify-center p-4 border-t">
<locale-picker></locale-picker>
</footer>
</div>
`;
}
renderNavBar() {
return html`
<style>
${theme}
</style>
<div
class="flex p-2 items-center shadow-lg bg-white text-neutral-content"
<nav
class="flex items-center justify-between p-2 bg-gray-900 text-gray-50"
>
<div class="flex-1 px-2 mx-2">
<a href="/" class="text-lg font-bold" @click="${this.navLink}"
>${msg("Browsertrix Cloud")}</a
<div>
<a href="/" @click="${this.navLink}"
><h1 class="text-base px-2">${msg("Browsertrix Cloud")}</h1></a
>
</div>
<div class="flex-none">
<div>
${this.authState
? html` <a
class="font-bold px-4"
href="/my-account"
@click="${this.navLink}"
>${msg("My Account")}</a
>
<button class="btn btn-error" @click="${this.onLogOut}">
${msg("Log Out")}
</button>`
: html`
<sl-button type="primary" @click="${this.onNeedLogin}">
${msg("Log In")}
</sl-button>
`}
? html` <sl-dropdown>
<div class="p-2" role="button" slot="trigger">
${this.authState.username}
<span class="text-xs"
><sl-icon name="chevron-down"></sl-icon
></span>
</div>
<sl-menu>
<sl-menu-item>Your account</sl-menu-item>
<sl-menu-item @click="${this.onLogOut}"
>${msg("Log Out")}</sl-menu-item
>
</sl-menu>
</sl-dropdown>`
: html` <a href="/log-in"> ${msg("Log In")} </a> `}
</div>
</div>
</nav>
`;
}
renderPage() {
const navLink = ({ href, label }: { href: string; label: string }) => html`
<li>
<a
class="block p-2 ${href === this.viewState._path
? "text-primary"
: ""}"
href="${href}"
@click="${this.navLink}"
>${label}</a
>
</li>
`;
const appLayout = (template: TemplateResult) => html`
<div class="w-full flex flex-col md:flex-row">
<nav class="md:w-80 md:p-4 md:border-r">
<ul class="flex md:flex-col">
${navLink({ href: "/my-account", label: "Archives" })}
${navLink({ href: "/users", label: "Users" })}
</ul>
</nav>
${template}
</div>
`;
switch (this.viewState._route) {
case "login":
return html`<log-in @logged-in="${this.onLoggedIn}"></log-in>`;
return html`<log-in
class="w-full md:bg-gray-100 flex items-center justify-center"
@logged-in="${this.onLoggedIn}"
></log-in>`;
case "home":
return html`<div>Home</div>`;
return html`<div class="w-full flex items-center justify-center">
<sl-button type="primary" size="large" @click="${this.onNeedLogin}">
${msg("Log In")}
</sl-button>
</div>`;
case "my-account":
return html`<my-account
return appLayout(html`<my-account
class="w-full"
@navigate="${this.onNavigateTo}"
@need-login="${this.onNeedLogin}"
.authState="${this.authState}"
></my-account>`;
></my-account>`);
case "archive-info":
case "archive-info-tab":
return html`<btrix-archive
return appLayout(html`<btrix-archive
class="w-full"
@navigate="${this.onNavigateTo}"
.authState="${this.authState}"
.viewState="${this.viewState}"
aid="${this.viewState.aid!}"
tab="${this.viewState.tab || "running"}"
></btrix-archive>`;
></btrix-archive>`);
default:
return html`<div>Not Found!</div>`;

View File

@ -1,52 +1,61 @@
import { state, property } from "lit/decorators.js";
import LiteElement, { html } from "../utils/LiteElement";
import type { Auth } from "../types/auth";
export class LogInPage extends LiteElement {
@property({ type: Object })
auth?: Auth;
loginError: string = "";
static get properties() {
return {
loginError: { type: String },
};
}
@state()
isLoggingIn: boolean = false;
@state()
loginError?: string;
render() {
return html`
<div class="flex items-center justify-center min-h-screen bg-blue-400">
<div class="bg-white shadow-2xl rounded-xl px-12 py-12">
<div class="max-w-md">
<sl-form @sl-submit="${this.onSubmit}">
<div class="mb-5">
<sl-input
name="username"
label="Username"
placeholder="Username"
required
>
</sl-input>
</div>
<div class="mb-5">
<sl-input
name="password"
type="password"
label="Password"
placeholder="Password"
required
>
</sl-input>
</div>
<sl-button class="w-full" type="primary" submit>Log in</sl-button>
</sl-form>
<div class="md:bg-white md:shadow-2xl md:rounded-lg md:px-12 md:py-12">
<div class="max-w-md">
<sl-form @sl-submit="${this.onSubmit}">
<div class="mb-5">
<sl-input
id="username"
name="username"
label="Username"
placeholder="Username"
required
>
</sl-input>
</div>
<div class="mb-5">
<sl-input
id="password"
name="password"
type="password"
label="Password"
placeholder="Password"
required
>
</sl-input>
</div>
<sl-button
class="w-full"
type="primary"
?loading=${this.isLoggingIn}
submit
>Log in</sl-button
>
</sl-form>
<div id="login-error" class="text-red-600">${this.loginError}</div>
</div>
<div id="login-error" class="text-red-600">${this.loginError}</div>
</div>
</div>
`;
}
async onSubmit(event: { detail: { formData: FormData } }) {
this.isLoggingIn = true;
const { formData } = event.detail;
const username = formData.get("username") as string;
@ -65,6 +74,7 @@ export class LogInPage extends LiteElement {
body: params.toString(),
});
if (resp.status !== 200) {
this.isLoggingIn = false;
this.loginError = "Sorry, invalid credentials";
return;
}
@ -83,5 +93,7 @@ export class LogInPage extends LiteElement {
if (!this.auth) {
this.loginError = "Unknown login response";
}
this.isLoggingIn = false;
}
}

View File

@ -2,10 +2,14 @@
* Cherry-picked Shoelace components
* https://shoelace.style
*/
import { setBasePath } from "@shoelace-style/shoelace/dist/utilities/base-path.js";
import "@shoelace-style/shoelace/dist/themes/light.css";
import "@shoelace-style/shoelace/dist/components/button/button";
import "@shoelace-style/shoelace/dist/components/form/form";
import "@shoelace-style/shoelace/dist/components/icon/icon";
import "@shoelace-style/shoelace/dist/components/input/input";
import "@shoelace-style/shoelace/dist/components/menu/menu";
import "@shoelace-style/shoelace/dist/components/menu-item/menu-item";
import "@shoelace-style/shoelace/dist/components/select/select";
setBasePath("/shoelace");

View File

@ -1,11 +1,50 @@
/**
* Shoelace CSS theming variables
* https://github.com/shoelace-style/shoelace/blob/current/src/themes/light.styles.ts
*
* To make new variables available to Tailwind, update
* `theme` in tailwind.cofnig.js
*/
import { css } from "lit";
import { css, unsafeCSS } from "lit";
import Color from "color";
// TODO generate at build time
const PRIMARY_COLOR = "#4876ff";
const primaryColor = Color(PRIMARY_COLOR);
const theme = css`
:root {
/* TODO add custom variables here */
--primary: ${unsafeCSS(PRIMARY_COLOR)};
/*
* Theme Tokens
*/
/* Primary */
--sl-color-primary-50: ${unsafeCSS(primaryColor.lighten(0.6))};
--sl-color-primary-100: ${unsafeCSS(primaryColor.lighten(0.5))};
--sl-color-primary-200: ${unsafeCSS(primaryColor.lighten(0.4))};
--sl-color-primary-300: ${unsafeCSS(primaryColor.lighten(0.3))};
--sl-color-primary-400: ${unsafeCSS(primaryColor.lighten(0.2))};
--sl-color-primary-500: ${unsafeCSS(primaryColor.lighten(0.1))};
--sl-color-primary-600: var(--primary);
--sl-color-primary-700: ${unsafeCSS(primaryColor.darken(0.1))};
--sl-color-primary-800: ${unsafeCSS(primaryColor.darken(0.2))};
--sl-color-primary-900: ${unsafeCSS(primaryColor.darken(0.3))};
--sl-color-primary-950: ${unsafeCSS(primaryColor.darken(0.4))};
/*
* Forms
*/
/* Buttons */
--sl-button-font-size-small: var(--sl-font-size-small);
--sl-button-font-size-medium: var(--sl-font-size-medium);
--sl-button-font-size-large: var(--sl-font-size-large);
/* Labels */
--sl-input-label-font-size-small: var(--sl-font-size-x-small);
--sl-input-label-font-size-medium: var(--sl-font-size-small);
--sl-input-label-font-size-large: var(--sl-font-size-medium);
}
`;

View File

@ -21,7 +21,10 @@ function makeTheme() {
}));
return {
colors: colors.map(makeColorPalette),
colors: {
...colors.map(makeColorPalette),
primary: `var(--primary)`,
},
fontFamily: {
sans: `var(--sl-font-sans)`,
serif: `var(--sl-font-serif)`,

View File

@ -10,6 +10,11 @@ require("dotenv").config({
});
const backendUrl = new URL(process.env.API_BASE_URL || "http://btrix.cloud/");
const shoelaceAssetsSrcPath = path.resolve(
__dirname,
"node_modules/@shoelace-style/shoelace/dist/assets"
);
const shoelaceAssetsPublicPath = "/shoelace/assets";
module.exports = {
entry: "./src/index.ts",
@ -49,11 +54,18 @@ module.exports = {
open: true,
compress: true,
hot: true,
static: {
directory: path.join(__dirname),
//publicPath: "/",
watch: true,
},
static: [
{
directory: shoelaceAssetsSrcPath,
publicPath: shoelaceAssetsPublicPath,
},
{
directory: path.join(__dirname),
//publicPath: "/",
watch: true,
},
],
historyApiFallback: true,
proxy: {
"/api": {
@ -82,11 +94,8 @@ module.exports = {
patterns: [
// Copy Shoelace assets to dist/shoelace
{
from: path.resolve(
__dirname,
"node_modules/@shoelace-style/shoelace/dist/assets"
),
to: path.resolve(__dirname, "dist/shoelace/assets"),
from: shoelaceAssetsSrcPath,
to: path.resolve(__dirname, "dist", shoelaceAssetsPublicPath),
},
],
}),

View File

@ -252,6 +252,25 @@
"@types/node" "*"
"@types/qs" "*"
"@types/color-convert@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-2.0.0.tgz#8f5ee6b9e863dcbee5703f5a517ffb13d3ea4e22"
integrity sha512-m7GG7IKKGuJUXvkZ1qqG3ChccdIM/qBBo913z+Xft0nKCX4hAU/IxKwZBU4cpRZ7GS5kV4vOblUkILtSShCPXQ==
dependencies:
"@types/color-name" "*"
"@types/color-name@*":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
"@types/color@^3.0.2":
version "3.0.2"
resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.2.tgz#3779043e782f562aa9157b5fc6bd07e14fd8e7f3"
integrity sha512-INiJl6sfNn8iyC5paxVzqiVUEj2boIlFki02uRTAkKwAj++7aAF+ZfEv/XrIeBa0XI/fTZuDHW8rEEcEVnON+Q==
dependencies:
"@types/color-convert" "*"
"@types/command-line-args@^5.0.0":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@types/command-line-args/-/command-line-args-5.2.0.tgz#adbb77980a1cc376bb208e3f4142e907410430f6"