Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions frontend/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
import type { Options } from "@swc/core";
import type { StorybookConfig } from "@storybook/web-components-webpack5";
import * as webpack from "webpack";

const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
Expand Down Expand Up @@ -104,5 +105,15 @@ const config: StorybookConfig = {

return newConfig;
},
webpackFinal: async (config) => {
config.plugins = config.plugins || [];
config.plugins.push(
new webpack.DefinePlugin({
APP_MODE: JSON.stringify("public"),
}),
);
return config;
},
};
export default config;

3 changes: 3 additions & 0 deletions frontend/declaration.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,6 @@ declare const GIT_VERSION: string;
declare const GIT_COMMIT_HASH: string;
declare const GIT_BRANCH: string;
declare const GIT_LAST_COMMIT_DATETIME: string;

/** App deployment mode: 'public' or 'internal' */
declare const APP_MODE: "public" | "internal";
23 changes: 15 additions & 8 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@
},
"private": true,
"scripts": {
"build": "webpack --mode development",
"build:prod": "webpack --mode production",
"serve": "webpack serve --mode development",
"serve:prod": "webpack serve --mode production",
"start": "npm run build && npm run serve",
"start:prod": "npm run build:prod && npm run serve:prod",
"deploy:prod": "npm run build:prod && gcloud app deploy",
"build:public": "APP_MODE=public webpack --mode development",
"build:public:prod": "APP_MODE=public webpack --mode production",
"build:internal": "APP_MODE=internal webpack --mode development",
"build:internal:prod": "APP_MODE=internal webpack --mode production",
"serve:public": "APP_MODE=public webpack serve --mode development",
"serve:public:prod": "APP_MODE=public webpack serve --mode production",
"serve:internal": "APP_MODE=internal webpack serve --mode development",
"serve:internal:prod": "APP_MODE=internal webpack serve --mode production",
"start:public": "npm run build:public && npm run serve:public",
"start:public:prod": "npm run build:public:prod && npm run serve:public:prod",
"start:internal": "npm run build:internal && npm run serve:internal",
"start:internal:prod": "npm run build:internal:prod && npm run serve:internal:prod",
"deploy:public": "npm run build:public:prod && gcloud app deploy",
"deploy:internal": "npm run build:internal:prod && gcloud app deploy",
"lint": "eslint src",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
Expand Down Expand Up @@ -97,4 +104,4 @@
"plugin:storybook/recommended"
]
}
}
}
27 changes: 26 additions & 1 deletion frontend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import "./components/floating_panel_host/floating_panel_host";
import "./components/smart_highlight_menu/smart_highlight_menu";
import "./components/dialogs/dialogs";
import "./components/banner/banner";
import "./components/auth/login_page";
import "lit-toast/lit-toast.js";

import { MobxLitElement } from "@adobe/lit-mobx";
Expand All @@ -33,6 +34,8 @@ import { createRef, ref, Ref } from "lit/directives/ref.js";
import { styleMap } from "lit/directives/style-map.js";

import { core } from "./core/core";
import { AppConfigService } from "./services/app_config.service";
import { AuthService } from "./services/auth.service";
import { Pages, RouterService } from "./services/router.service";
import { SettingsService } from "./services/settings.service";
import { SnackbarService } from "./services/snackbar.service";
Expand All @@ -43,16 +46,20 @@ import { styles } from "./app.scss";
import { LightMobxLitElement } from "./components/light_mobx_lit_element/light_mobx_lit_element";

import { GalleryView } from "./shared/types";
import { FloatingPanelService } from "./services/floating_panel_service";

/** App main component. */
@customElement("lumi-app")
export class App extends LightMobxLitElement {
static override styles: CSSResultGroup = [styles];

private readonly appConfigService = core.getService(AppConfigService);
private readonly authService = core.getService(AuthService);
private readonly bannerService = core.getService(BannerService);
private readonly routerService = core.getService(RouterService);
private readonly settingsService = core.getService(SettingsService);
private readonly snackbarService = core.getService(SnackbarService);
private readonly floatingPanelService = core.getService(FloatingPanelService);

private readonly toastRef: Ref<any> = createRef<any>();

Expand Down Expand Up @@ -121,6 +128,21 @@ export class App extends LightMobxLitElement {
}

override render() {
// In internal mode, require authentication before showing app content
if (
this.appConfigService.features.authentication &&
!this.authService.isAuthenticated
) {
return html`
<style>
${styles}
</style>
<div class="app-wrapper mode--${this.settingsService.colorMode}">
<login-page></login-page>
</div>
`;
}

const mainStyles = styleMap({
height: this.bannerService.isBannerOpen
? `calc(100% - ${BANNER_HEIGHT}px)`
Expand All @@ -131,7 +153,10 @@ export class App extends LightMobxLitElement {
<style>
${styles}
</style>
<div class="app-wrapper mode--${this.settingsService.colorMode}">
<div
class="app-wrapper mode--${this.settingsService.colorMode}"
@mousedown=${() => this.floatingPanelService.hide()}
>
${this.renderBanner()}
<main style=${mainStyles}>
<div class="content-wrapper">${this.renderPageContent()}</div>
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/components/auth/login_page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@use "../../sass/common";
@use "../../sass/typescale";

.login-page {
@include common.flex-row-align-center;
justify-content: center;
height: 100vh;
width: 100vw;
background: linear-gradient(
135deg,
var(--md-sys-color-primary-container) 0%,
var(--md-sys-color-surface) 100%
);
}

.login-card {
@include common.flex-column;
align-items: center;
gap: common.$spacing-xxl;
padding: 36px;

background: var(--md-sys-color-surface);
border: 3px solid var(--md-sys-color-primary-container-high);
border-radius: common.$border-radius-large;

min-width: 320px;
}

.header {
@include common.flex-column;
align-items: center;
gap: common.$spacing-medium;
}

h1 {
@include typescale.headline-large;
margin: 0;
color: var(--md-sys-color-on-surface);
}

.subtitle {
@include typescale.body-small;
margin: 0;
color: var(--md-sys-color-on-surface-variant);
font-style: italic;
}

.loading {
@include typescale.body-small;
color: var(--md-sys-color-on-surface-variant);
font-style: italic;
}

.error {
@include typescale.body-small;
color: var(--md-sys-color-error);
margin: 0;
}
76 changes: 76 additions & 0 deletions frontend/src/components/auth/login_page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import "../../pair-components/button";
import "../../pair-components/icon";

import { MobxLitElement } from "@adobe/lit-mobx";
import { CSSResultGroup, html } from "lit";
import { customElement } from "lit/decorators.js";

import { core } from "../../core/core";
import { AuthService } from "../../services/auth.service";
import { APP_NAME } from "../../shared/constants";

import { styles } from "./login_page.scss";

/**
* Full-screen login page shown when user is not authenticated in internal mode.
*/
@customElement("login-page")
export class LoginPage extends MobxLitElement {
static override styles: CSSResultGroup = [styles];

private readonly authService = core.getService(AuthService);

private handleLogin() {
this.authService.signInWithGoogle();
}

override render() {
return html`
<div class="login-page">
<div class="login-card">
<div class="header">
<h1>${APP_NAME}</h1>
<p class="subtitle">Internal Lumi for Research</p>
</div>
${this.authService.isLoading
? html`<p class="loading">Loading...</p>`
: html`
<pr-button
variant="filled"
size="small"
@click=${this.handleLogin}
>
Sign in with Google
</pr-button>
`}
${this.authService.error
? html`<p class="error">${this.authService.error}</p>`
: ""}
</div>
</div>
`;
}
}

declare global {
interface HTMLElementTagNameMap {
"login-page": LoginPage;
}
}
17 changes: 17 additions & 0 deletions frontend/src/components/auth/profile_button.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @license
* Copyright 2025 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@use "../../sass/common";
Loading