Convert from Polymer to Lit

Change-Id: Iede750ec006911b12df7bb85ab1d453c3ebf8821
diff --git a/BUILD b/BUILD
index cacb69a..274216d 100644
--- a/BUILD
+++ b/BUILD
@@ -6,7 +6,6 @@
     "PLUGIN_TEST_DEPS",
     "gerrit_plugin",
 )
-load("//tools/bzl:js.bzl", "gerrit_js_bundle")
 
 LFS_DEPS = [
     "@jgit//org.eclipse.jgit.lfs.server:jgit-lfs-server",
@@ -39,17 +38,11 @@
         "Gerrit-SshModule: com.googlesource.gerrit.plugins.lfs.SshModule",
         "Gerrit-InitStep: com.googlesource.gerrit.plugins.lfs.InitLfs",
     ],
-    resource_jars = [":gr-lfs"],
+    resource_jars = ["//plugins/lfs/web:lfs"],
     resources = glob(["src/main/resources/**/*"]),
     deps = LFS_DEPS,
 )
 
-gerrit_js_bundle(
-    name = "gr-lfs",
-    srcs = glob(["gr-lfs/*.js"]),
-    entry_point = "gr-lfs/plugin.js",
-)
-
 junit_tests(
     name = "lfs_tests",
     srcs = glob(["src/test/java/**/*.java"]),
diff --git a/gr-lfs/gr-lfs-project-info.js b/gr-lfs/gr-lfs-project-info.js
deleted file mode 100644
index 980ce20..0000000
--- a/gr-lfs/gr-lfs-project-info.js
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2021 The Android Open Source Project
-//
-// 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 {htmlTemplate} from './gr-lfs-project-info_html.js';
-
-class GrLfsProjectInfo extends Polymer.Element {
-  static get is() {
-    return 'gr-lfs-project-info';
-  }
-
-  static get template() {
-    return htmlTemplate;
-  }
-
-  static get properties() {
-    return {
-      repoName: String,
-      _appliedConfig: Object,
-    };
-  }
-
-  connectedCallback() {
-    super.connectedCallback();
-
-    this._getPreferences();
-  }
-
-  _getPreferences() {
-    let encodedRepoName = encodeURIComponent(this.repoName);
-    return this.plugin.restApi('/projects/')
-      .get(`${encodedRepoName}/${this.plugin.getPluginName()}~lfs:config-project`)
-      .then(config => {
-        if (!config || Object.entries(config).length === 0) {
-          this.$.lfsProjectInfo.hidden = true;
-          return;
-        }
-
-        this._appliedConfig = config
-      })
-  }
-}
-
-customElements.define(GrLfsProjectInfo.is, GrLfsProjectInfo);
diff --git a/gr-lfs/gr-lfs-project-info_html.js b/gr-lfs/gr-lfs-project-info_html.js
deleted file mode 100644
index 953d594..0000000
--- a/gr-lfs/gr-lfs-project-info_html.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
- * @license
- * Copyright (C) 2021 The Android Open Source Project
- *
- * 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.
- */
-
-export const htmlTemplate = Polymer.html`
-<style include="shared-styles"></style>
-<style include="gr-form-styles"></style>
-<style>
-  .sectionTitle {
-    padding-top: 2em;
-  }
-</style>
-<fieldset id="lfsProjectInfo" class="gr-form-styles">
-  <h2 class="sectionTitle">LFS Info</h2>
-  <section>
-    <span class="title">Enabled</span>
-    <span class="value">[[_appliedConfig.enabled]]</span>
-  </section>
-  <section>
-    <span class="title">Max Object Size</span>
-    <span class="value">[[_appliedConfig.max_object_size]]</span>
-  </section>
-  <section>
-    <span class="title">Read Only</span>
-    <span class="value">[[_appliedConfig.read_only]]</span>
-  </section>
-  <section hidden="[[!_appliedConfig.backend]]">
-    <span class="title">Backend</span>
-    <span class="value">[[_appliedConfig.backend]]</span>
-  </section>
-</fieldset>`;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java b/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
index 640704b..146ba44 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/lfs/HttpModule.java
@@ -62,7 +62,7 @@
       populateRepository(backend);
     }
 
-    DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("gr-lfs.js"));
+    DynamicSet.bind(binder(), WebUiPlugin.class).toInstance(new JavaScriptPlugin("lfs.js"));
   }
 
   private void populateRepository(LfsBackend backend) {
diff --git a/web/BUILD b/web/BUILD
new file mode 100644
index 0000000..8e9b27a
--- /dev/null
+++ b/web/BUILD
@@ -0,0 +1,42 @@
+load("//tools/js:eslint.bzl", "plugin_eslint")
+load("//tools/bzl:js.bzl", "gerrit_js_bundle")
+load("@npm//@bazel/typescript:index.bzl", "ts_config", "ts_project")
+
+package_group(
+    name = "visibility",
+    packages = ["//plugins/lfs/..."],
+)
+
+package(default_visibility = [":visibility"])
+
+ts_config(
+    name = "tsconfig",
+    src = "tsconfig.json",
+    deps = [
+        "//plugins:tsconfig-plugins-base.json",
+    ],
+)
+
+ts_project(
+    name = "lfs-ts",
+    srcs = glob(
+        ["**/*.ts"],
+        exclude = ["**/*test*"],
+    ),
+    incremental = True,
+    out_dir = "_bazel_ts_out",
+    tsc = "//tools/node_tools:tsc-bin",
+    tsconfig = ":tsconfig",
+    deps = [
+        "@plugins_npm//@gerritcodereview/typescript-api",
+        "@plugins_npm//lit",
+    ],
+)
+
+gerrit_js_bundle(
+    name = "lfs",
+    srcs = [":lfs-ts"],
+    entry_point = "_bazel_ts_out/plugin.js",
+)
+
+plugin_eslint()
diff --git a/web/eslint.config.js b/web/eslint.config.js
new file mode 100644
index 0000000..eb9cfcc
--- /dev/null
+++ b/web/eslint.config.js
@@ -0,0 +1,18 @@
+/**
+ * @license
+ * Copyright 2025 The Android Open Source Project
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+const {defineConfig} = require('eslint/config');
+
+// eslint-disable-next-line no-undef
+__plugindir = 'lfs/web';
+
+const gerritEslint = require('../../eslint.config.js');
+
+module.exports = defineConfig([
+  {
+    extends: [gerritEslint],
+  },
+]);
diff --git a/web/gr-lfs.ts b/web/gr-lfs.ts
new file mode 100644
index 0000000..e112c73
--- /dev/null
+++ b/web/gr-lfs.ts
@@ -0,0 +1,126 @@
+/**
+ * @license
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * 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 {PluginApi} from '@gerritcodereview/typescript-api/plugin';
+import {css, CSSResult, html, LitElement, nothing} from 'lit';
+import {customElement, property, state} from 'lit/decorators.js';
+import '@gerritcodereview/typescript-api/gerrit';
+
+declare global {
+  interface HTMLElementTagNameMap {
+    'gr-lfs': GrLfs;
+  }
+}
+
+@customElement('gr-lfs')
+export class GrLfs extends LitElement {
+  @property({type: Object})
+  plugin!: PluginApi;
+
+  @property({type: String}) repoName = '';
+
+  @state() private _appliedConfig?: {
+    enabled?: boolean;
+    max_object_size?: string;
+    read_only?: boolean;
+    backend?: string;
+  };
+
+  override connectedCallback() {
+    super.connectedCallback();
+    this._getPreferences();
+  }
+
+  static override get styles() {
+    return [
+      window.Gerrit?.styles.form as CSSResult,
+      css`
+        :host {
+          display: block;
+        }
+
+        .sectionTitle {
+          padding-top: 2em;
+        }
+
+        /* Placeholder: Replace these with actual shared styles or imports */
+        .gr-form-styles {
+          border: none;
+          padding: 0;
+        }
+
+        section {
+          display: flex;
+          margin-bottom: 8px;
+        }
+
+        .title {
+          font-weight: bold;
+          width: 150px;
+        }
+
+        .value {
+          flex: 1;
+        }
+      `,
+    ];
+  }
+
+  override render() {
+    const cfg = this._appliedConfig;
+    if (!cfg) return nothing;
+
+    return html`
+      <fieldset class="gr-form-styles">
+        <h2 class="sectionTitle">LFS Info</h2>
+        <section>
+          <span class="title">Enabled</span>
+          <span class="value">${cfg.enabled ?? ''}</span>
+        </section>
+        <section>
+          <span class="title">Max Object Size</span>
+          <span class="value">${cfg.max_object_size ?? ''}</span>
+        </section>
+        <section>
+          <span class="title">Read Only</span>
+          <span class="value">${cfg.read_only ?? ''}</span>
+        </section>
+        ${cfg.backend
+          ? html`
+              <section>
+                <span class="title">Backend</span>
+                <span class="value">${cfg.backend}</span>
+              </section>
+            `
+          : nothing}
+      </fieldset>
+    `;
+  }
+
+  private _getPreferences() {
+    let encodedRepoName = encodeURIComponent(this.repoName);
+    return this.plugin.restApi()
+      .get(`/projects/${encodedRepoName}/${this.plugin.getPluginName()}~lfs:config-project`)
+      .then(config => {
+        if (!config || Object.entries(config).length === 0) {
+          return;
+        }
+
+        this._appliedConfig = config
+      })
+  }
+}
diff --git a/gr-lfs/plugin.js b/web/plugin.ts
similarity index 72%
rename from gr-lfs/plugin.js
rename to web/plugin.ts
index afeacd5..04bc2ec 100644
--- a/gr-lfs/plugin.js
+++ b/web/plugin.ts
@@ -1,6 +1,6 @@
 /**
  * @license
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2025 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,9 +15,9 @@
  * limitations under the License.
  */
 
-import './gr-lfs-project-info.js';
+import '@gerritcodereview/typescript-api/gerrit';
+import './gr-lfs';
 
-Gerrit.install(plugin => {
-  plugin.registerCustomComponent(
-    'repo-config', 'gr-lfs-project-info');
+window.Gerrit?.install(plugin => {
+  plugin.registerCustomComponent('repo-config', 'gr-lfs');
 });
diff --git a/web/tsconfig.json b/web/tsconfig.json
new file mode 100644
index 0000000..2df6d21
--- /dev/null
+++ b/web/tsconfig.json
@@ -0,0 +1,9 @@
+{
+  "extends": "../../tsconfig-plugins-base.json",
+  "compilerOptions": {
+    "outDir": "../../../.ts-out/plugins/lfs" /* overridden by bazel */
+  },
+  "include": [
+    "**/*.ts"
+  ]
+}