initial pass on frontend: using tailwindcss + daisyui + litelement with webpack build + dev server
This commit is contained in:
		
							parent
							
								
									c38e0b7bf7
								
							
						
					
					
						commit
						666becdb65
					
				
							
								
								
									
										258
									
								
								frontend/dist/main.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										258
									
								
								frontend/dist/main.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										11
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html data-theme="light">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8" />
 | 
			
		||||
    <title>Demo</title>
 | 
			
		||||
    <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>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										29
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								frontend/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "frontend",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "main": "index.js",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "axios": "^0.22.0",
 | 
			
		||||
    "daisyui": "^1.14.2",
 | 
			
		||||
    "lit": "^2.0.0",
 | 
			
		||||
    "lit-element-router": "^2.0.3",
 | 
			
		||||
    "path-parser": "^6.1.0",
 | 
			
		||||
    "tailwindcss": "^2.2.16"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "webpack --mode production",
 | 
			
		||||
    "build-dev": "webpack --mode development",
 | 
			
		||||
    "start-dev": "webpack serve --mode=development"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "autoprefixer": "^10.3.6",
 | 
			
		||||
    "css-loader": "^6.3.0",
 | 
			
		||||
    "postcss": "^8.3.8",
 | 
			
		||||
    "postcss-loader": "^6.1.1",
 | 
			
		||||
    "style-loader": "^3.3.0",
 | 
			
		||||
    "webpack": "^5.56.0",
 | 
			
		||||
    "webpack-cli": "^4.8.0",
 | 
			
		||||
    "webpack-dev-server": "^4.3.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								frontend/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								frontend/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  plugins: [
 | 
			
		||||
      require('tailwindcss'),
 | 
			
		||||
      require('autoprefixer'),
 | 
			
		||||
  ]
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										345
									
								
								frontend/src/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										345
									
								
								frontend/src/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,345 @@
 | 
			
		||||
import { LiteElement, APIRouter, html } from "./utils";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
class App extends LiteElement
 | 
			
		||||
{
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.authState = null;
 | 
			
		||||
 | 
			
		||||
    const authState = window.localStorage.getItem("authState");
 | 
			
		||||
    if (authState) {
 | 
			
		||||
      this.authState = JSON.parse(authState);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.router = new APIRouter({
 | 
			
		||||
      "home": "/",
 | 
			
		||||
      "login": "/log-in",
 | 
			
		||||
      "my-account": "/my-account",
 | 
			
		||||
      "archive-info": "/archive/:aid",
 | 
			
		||||
      "archive-info-tab": "/archive/:aid/:tab"
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.viewState = this.router.match(window.location.pathname);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get properties() {
 | 
			
		||||
    return {
 | 
			
		||||
      viewState: { type: Object },
 | 
			
		||||
      authState: { type: Object }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  firstUpdated() {
 | 
			
		||||
    window.addEventListener("popstate", (event) => {
 | 
			
		||||
      // if (event.state.view) {
 | 
			
		||||
      //   this.view = event.state.view;
 | 
			
		||||
      // }
 | 
			
		||||
      this.viewState = this.router.match(window.location.pathname);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.viewState = this.router.match(window.location.pathname);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navigate(newView) {
 | 
			
		||||
    if (newView.startsWith("http")) {
 | 
			
		||||
      newView = new URL(newView).pathname;
 | 
			
		||||
    }
 | 
			
		||||
    this.viewState = this.router.match(newView);
 | 
			
		||||
    if (this.viewState._route === "login") {
 | 
			
		||||
      this.clearAuthState();
 | 
			
		||||
    }
 | 
			
		||||
    //console.log(this.view._route, window.location.href); 
 | 
			
		||||
    window.history.pushState(this.viewState, "", this.viewState._path);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navLink(event) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    this.navigate(event.currentTarget.href);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`
 | 
			
		||||
    ${this.renderNavBar()}
 | 
			
		||||
    <div class="w-full h-full px-12 py-12">
 | 
			
		||||
    ${this.renderPage()}
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderNavBar() {
 | 
			
		||||
    return html`
 | 
			
		||||
    <div class="navbar shadow-lg bg-neutral text-neutral-content">
 | 
			
		||||
      <div class="flex-1 px-2 mx-2">
 | 
			
		||||
        <a href="/" class="link link-hover text-lg font-bold" @click="${this.navLink}">Browsertrix Cloud</a>
 | 
			
		||||
      </div> 
 | 
			
		||||
      <div class="flex-none">
 | 
			
		||||
        ${this.authState ? html`
 | 
			
		||||
        <a class="link link-hover font-bold px-4" href="/my-account" @click="${this.navLink}">My Account</a> 
 | 
			
		||||
        <button class="btn btn-error" @click="${this.onLogOut}">Log Out</button>` 
 | 
			
		||||
        : html`
 | 
			
		||||
        <button class="btn ${this.viewState._route !== "login" ? "btn-primary" : "btn-ghost"}" @click="${this.onNeedLogin}">Log In</button>
 | 
			
		||||
        `}
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  renderPage() {
 | 
			
		||||
    switch (this.viewState._route) {
 | 
			
		||||
      case "login":
 | 
			
		||||
        return html`<log-in @logged-in="${this.onLoggedIn}">`;
 | 
			
		||||
 | 
			
		||||
      case "home":
 | 
			
		||||
        return html`<div>Home</div>`;
 | 
			
		||||
 | 
			
		||||
      case "my-account":
 | 
			
		||||
        return html`<my-account @navigate="${this.onNavigateTo}" @need-login="${this.onNeedLogin}"  .authState="${this.authState}"></my-account>`;
 | 
			
		||||
 | 
			
		||||
      case "archive-info":
 | 
			
		||||
      case "archive-info-tab":
 | 
			
		||||
        return html`<btrix-archive @navigate="${this.onNavigateTo}" .authState="${this.authState}" .viewState="${this.viewState}" aid="${this.viewState.aid}" tab="${this.viewState.tab || "running"}"></btrix-archive>`;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        return html`<div>Not Found!</div>`;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onLogOut() {
 | 
			
		||||
    this.clearAuthState();
 | 
			
		||||
    this.navigate("/");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onLoggedIn(event) {
 | 
			
		||||
    this.authState = {
 | 
			
		||||
      username: event.detail.username,
 | 
			
		||||
      headers: {"Authorization": event.detail.auth}
 | 
			
		||||
    };
 | 
			
		||||
    window.localStorage.setItem("authState", JSON.stringify(this.authState));
 | 
			
		||||
    this.navigate("/my-account");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onNeedLogin() {
 | 
			
		||||
    this.clearAuthState();
 | 
			
		||||
    this.navigate("/log-in");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onNavigateTo(event) {
 | 
			
		||||
    this.navigate(event.detail);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  clearAuthState() {
 | 
			
		||||
    this.authState = null;
 | 
			
		||||
    window.localStorage.setItem("authState", "");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
class LogIn extends LiteElement
 | 
			
		||||
{
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.loginError = "";
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get properties() {
 | 
			
		||||
    return {
 | 
			
		||||
      loginError: { type: String }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`
 | 
			
		||||
    <div class="hero min-h-screen bg-blue-400">
 | 
			
		||||
      <div class="text-center hero-content bg-base-200 shadow-2xl rounded-xl px-16 py-8">
 | 
			
		||||
 | 
			
		||||
        <div class="max-w-md">
 | 
			
		||||
          <form action="" @submit="${this.onSubmit}">
 | 
			
		||||
            <div class="form-control">
 | 
			
		||||
              <label class="label">
 | 
			
		||||
                <span class="label-text">User</span>
 | 
			
		||||
              </label> 
 | 
			
		||||
              <input id="username" name="username" type="text" placeholder="Username" class="input input-bordered">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-control">
 | 
			
		||||
              <label class="label">
 | 
			
		||||
                <span class="label-text">Password</span>
 | 
			
		||||
              </label> 
 | 
			
		||||
              <input id="password" name="password" type="password" placeholder="Password" class="input input-bordered">
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-control py-4">
 | 
			
		||||
              <button class="btn btn-primary" type="submit">Log In</button> 
 | 
			
		||||
            </div>
 | 
			
		||||
          </form>
 | 
			
		||||
          <div id="login-error" class="text-red-600">
 | 
			
		||||
            ${this.loginError}
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async onSubmit(event) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
 | 
			
		||||
    const username = this.querySelector("#username").value;
 | 
			
		||||
 | 
			
		||||
    const params = new URLSearchParams();
 | 
			
		||||
    params.set("grant_type", "password");
 | 
			
		||||
    params.set("username", username);
 | 
			
		||||
    params.set("password", this.querySelector("#password").value);
 | 
			
		||||
 | 
			
		||||
    const headers = {"Content-Type": "application/x-www-form-urlencoded"}
 | 
			
		||||
 | 
			
		||||
    const resp = await fetch("/api/auth/jwt/login", {headers, method: "POST", body: params.toString()});
 | 
			
		||||
    if (resp.status !== 200) {
 | 
			
		||||
      this.loginError = "Sorry, invalid credentials";
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const data = await resp.json();
 | 
			
		||||
      if (data.token_type === "bearer" && data.access_token) {
 | 
			
		||||
        const auth = "Bearer " + data.access_token;
 | 
			
		||||
        const detail = {auth, username};
 | 
			
		||||
        this.dispatchEvent(new CustomEvent("logged-in", {detail}));
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
    } catch(e) {
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.auth) {
 | 
			
		||||
      this.loginError = "Unknown login response";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
class MyAccount extends LiteElement
 | 
			
		||||
{
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.archiveList = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static get properties() {
 | 
			
		||||
    return {
 | 
			
		||||
      authState: { type: Object },
 | 
			
		||||
      archiveList: { type: Array },
 | 
			
		||||
      id: { type: String }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async firstUpdated() {
 | 
			
		||||
    if (!this.authState) {
 | 
			
		||||
      this.dispatchEvent(new CustomEvent("need-login"));
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const data = await this.apiFetch("/archives", this.authState);
 | 
			
		||||
    this.archiveList = data.archives;
 | 
			
		||||
 | 
			
		||||
    const data2 = await this.apiFetch("/users/me", this.authState);
 | 
			
		||||
    this.id = data2.id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`
 | 
			
		||||
    <div class="container bg-base-200 m-auto border rounded-lg px-8 py-8">
 | 
			
		||||
      <h2 class="text-2xl font-bold">Your Archives</h2>
 | 
			
		||||
      ${this.archiveList.map(archive => html`
 | 
			
		||||
      <div class="card mt-6 ml-6 border rounded-none border-gray-600 hover:bg-gray-300">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <div class="card-title">
 | 
			
		||||
          <span class="mr-4">${archive.name}</span>${this.getAccessValue(archive)}
 | 
			
		||||
          </div>
 | 
			
		||||
          <div class="card-actions">
 | 
			
		||||
            <a class="btn btn-primary" href="/archive/${archive.id}" @click="${this.navLink}">View Archive</a> 
 | 
			
		||||
          </div>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div> 
 | 
			
		||||
 | 
			
		||||
      `)}
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getAccessValue(archive) {
 | 
			
		||||
    const value = archive.users && archive.users[this.id];
 | 
			
		||||
    switch (value) {
 | 
			
		||||
      case 40:
 | 
			
		||||
        return html`<div class="badge badge-info">Owner</div>`;
 | 
			
		||||
 | 
			
		||||
      default:
 | 
			
		||||
        return "";
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
class Archive extends LiteElement
 | 
			
		||||
{
 | 
			
		||||
  static get properties() {
 | 
			
		||||
    return {
 | 
			
		||||
      authState: { type: Object },
 | 
			
		||||
      aid: {type: String},
 | 
			
		||||
      tab: {type: String},
 | 
			
		||||
      viewState: { type: Object }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    const aid = this.aid;
 | 
			
		||||
    const tab = this.tab || "running";
 | 
			
		||||
    return html`
 | 
			
		||||
    <div class="container bg-base-200 m-auto border shadow-xl rounded-lg px-8 py-8">
 | 
			
		||||
      <div class="tabs tabs-boxed">
 | 
			
		||||
        <a href="/archive/${aid}/running" class="tab ${tab === "running" ? 'tab-active' : ''}" @click="${this.navLink}">Crawls Running</a>
 | 
			
		||||
        <a href="/archive/${aid}/finished" class="tab ${tab === "finished" ? 'tab-active' : ''}" @click="${this.navLink}">Finished</a>
 | 
			
		||||
        <a href="/archive/${aid}/configs" class="tab ${tab === "configs" ? 'tab-active' : ''}" @click="${this.navLink}">Crawl Configs</a> 
 | 
			
		||||
      </div>
 | 
			
		||||
      ${tab === "configs" ?
 | 
			
		||||
      html`<btrix-archive-configs .archive=${this}></btrix-archive-configs>` : ""}
 | 
			
		||||
    </div>
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
class ArchiveConfigs extends LiteElement
 | 
			
		||||
{
 | 
			
		||||
  static get properties() {
 | 
			
		||||
    return {
 | 
			
		||||
      archive: { type: Object },
 | 
			
		||||
      configs: { type: Array }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async firstUpdated() {
 | 
			
		||||
    const res = await this.apiFetch(`/archives/${this.archive.aid}/crawlconfigs`, this.archive.authState);
 | 
			
		||||
    this.configs = res.crawl_configs;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  render() {
 | 
			
		||||
    return html`<div>Archive Configs!</div>
 | 
			
		||||
    ${this.configs && this.configs.map((config) => html`
 | 
			
		||||
      <div>${config.crawlCount} ${config.config.seeds}</div>
 | 
			
		||||
    `)}
 | 
			
		||||
    `;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
customElements.define("browsertrix-app", App);
 | 
			
		||||
customElements.define("log-in", LogIn);
 | 
			
		||||
customElements.define("my-account", MyAccount);
 | 
			
		||||
customElements.define("btrix-archive", Archive);
 | 
			
		||||
customElements.define("btrix-archive-configs", ArchiveConfigs);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										62
									
								
								frontend/src/utils.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								frontend/src/utils.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
import "tailwindcss/tailwind.css";
 | 
			
		||||
 | 
			
		||||
import { LitElement, html } from "lit";
 | 
			
		||||
import { Path } from "path-parser";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
export class LiteElement extends LitElement
 | 
			
		||||
{
 | 
			
		||||
  createRenderRoot() {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navTo(url) {
 | 
			
		||||
    this.dispatchEvent(new CustomEvent("navigate", {detail: url}));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  navLink(event) {
 | 
			
		||||
    event.preventDefault();
 | 
			
		||||
    this.dispatchEvent(new CustomEvent("navigate", {detail: event.currentTarget.href, bubbles: true, composed: true}));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async apiFetch(path, auth) {
 | 
			
		||||
    const resp = await fetch("/api" + path, {headers: auth.headers});
 | 
			
		||||
    if (resp.status !== 200) {
 | 
			
		||||
      this.navTo("/log-in");
 | 
			
		||||
      throw new Error("logged out");
 | 
			
		||||
    }
 | 
			
		||||
    return await resp.json();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// ===========================================================================
 | 
			
		||||
export class APIRouter {
 | 
			
		||||
  constructor(paths) {
 | 
			
		||||
    this.routes = {};
 | 
			
		||||
 | 
			
		||||
    for (const [name, route] of Object.entries(paths)) {
 | 
			
		||||
      this.routes[name] = new Path(route);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  match(path) {
 | 
			
		||||
    for (const [name, route] of Object.entries(this.routes)) {
 | 
			
		||||
      const parts = path.split("?", 2);
 | 
			
		||||
      const matchUrl = parts[0];
 | 
			
		||||
 | 
			
		||||
      const res = route.test(matchUrl);
 | 
			
		||||
      if (res) {
 | 
			
		||||
        res._route = name;
 | 
			
		||||
        res._path = path;
 | 
			
		||||
        //res._query = new URLSearchParams(parts.length === 2 ? parts[1] : "");
 | 
			
		||||
        return res;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  
 | 
			
		||||
    return {_route: null, _path: path};
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { html };
 | 
			
		||||
							
								
								
									
										18
									
								
								frontend/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								frontend/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
  mode: 'jit',
 | 
			
		||||
  
 | 
			
		||||
  purge: {
 | 
			
		||||
    content: ['./*.html', './src/*.js'],
 | 
			
		||||
    options: {
 | 
			
		||||
      safelist: [
 | 
			
		||||
        /data-theme$/,
 | 
			
		||||
      ]
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  plugins: [
 | 
			
		||||
    require('daisyui')
 | 
			
		||||
  ],
 | 
			
		||||
  extract: {
 | 
			
		||||
    include: ['./src/*.js'],
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										49
									
								
								frontend/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								frontend/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
			
		||||
// webpack.config.js
 | 
			
		||||
const path = require("path")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const backendUrl = new URL("http://btrix.cloud/");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  entry: "./src/index.js",
 | 
			
		||||
  output: {
 | 
			
		||||
    path: path.resolve(__dirname, "dist"),
 | 
			
		||||
    filename: "main.js",
 | 
			
		||||
    publicPath: "/"
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  module: {
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        test: /\.css$/,
 | 
			
		||||
        use: [
 | 
			
		||||
          "style-loader",
 | 
			
		||||
          { loader: "css-loader", options: { importLoaders: 1 } },
 | 
			
		||||
          "postcss-loader",
 | 
			
		||||
        ],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  devServer: {
 | 
			
		||||
    watchFiles: ["src/*.js"],
 | 
			
		||||
    open: true,
 | 
			
		||||
    compress: true,
 | 
			
		||||
    hot: true,
 | 
			
		||||
    static: {
 | 
			
		||||
      directory: path.join(__dirname),
 | 
			
		||||
      //publicPath: "/",
 | 
			
		||||
      watch: true
 | 
			
		||||
    },
 | 
			
		||||
    historyApiFallback: true,
 | 
			
		||||
    proxy: {
 | 
			
		||||
      '/api': {
 | 
			
		||||
        target: backendUrl.href,
 | 
			
		||||
        headers: {
 | 
			
		||||
          'Host': backendUrl.host
 | 
			
		||||
        },    
 | 
			
		||||
        pathRewrite: { '^/api': '' },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    port: 9870
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2926
									
								
								frontend/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2926
									
								
								frontend/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user