video/vnd.dlna.mpeg-tts
•
3.72 KB
•
170 lines
import { css, html, LitElement } from "lit";
import { customElement, property, query, state } from "lit/decorators.js";
@customElement("rubhub-clone-url")
export class RubhubCloneUrl extends LitElement {
static styles = css`
:host {
display: block;
position: relative;
}
.wrapper {
display: flex;
flex-direction: column;
gap: var(--space-s);
}
#input-row {
display: flex;
align-items: stretch;
gap: var(--space-s);
}
label {
font-weight: 600;
}
input {
width: 100%;
padding: var(--space-m);
padding-right: calc(2.5rem + --space-m);
background: var(--background-color);
color: var(--text-color);
border: solid 1px var(--primary-color);
border-radius: var(--space-s);
}
.copy-btn {
display: inline-flex;
align-items: center;
gap: var(--space-xs);
white-space: nowrap;
padding: var(--space-m);
margin: 0;
background: var(--primary-color);
color: var(--white);
border: 1px solid var(--primary-color-bright);
border-bottom-color: var(--primary-color-dark);
border-right-color: var(--primary-color-dark);
border-radius: var(--space-s);
cursor: pointer;
}
#status {
min-height: 1.2rem;
background: var(--success-color-dark);
color: var(--white);
border-radius: var(--space-s);
padding: var(--space-s) var(--space-m);
position: absolute;
right: 0;
top: 100%;
margin-top:8px;
transform: scaleY(0);
transform-origin: 50% 0;
opacity: 0;
transition: opacity 400ms ease, transform 500ms cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
#status.error {
background: var(--error-color);
}
#status.visible {
transform: scaleY(1);
opacity: 1;
}
`;
@property({ type: String })
label = "Clone with SSH";
@property({ type: String })
value = "";
@state()
private status: "idle" | "copied" | "error" = "idle";
@state()
private statusMessage: string = "";
@query("input")
private input?: HTMLInputElement;
private statusTimeout: number | undefined;
disconnectedCallback() {
super.disconnectedCallback();
if (this.statusTimeout) {
window.clearTimeout(this.statusTimeout);
}
}
private async copy() {
if (!this.value) return;
try {
await navigator.clipboard.writeText(this.value);
this.setStatus("copied");
return;
} catch (_) {
// Fallback to selection-based copy for older browsers or blocked clipboard access
if (this.input) {
this.input.focus();
this.input.select();
const ok = document.execCommand("copy");
this.setStatus(ok ? "copied" : "error");
return;
}
}
this.setStatus("error");
}
private getStatusMessage(status: "idle" | "copied" | "error") {
switch (status) {
case "idle":
return "";
case "copied":
return "Copied!";
case "error":
return "Could not copy. Please copy manually.";
}
}
private setStatus(next: "idle" | "copied" | "error") {
this.status = next;
if (next !== "idle") {
this.statusMessage = this.getStatusMessage(this.status);
}
if (this.statusTimeout) {
window.clearTimeout(this.statusTimeout);
}
if (next !== "idle") {
this.statusTimeout = window.setTimeout(() => {
this.status = "idle";
}, 2000);
}
}
render() {
const showStatus = this.status !== "idle";
return html`
<div class="wrapper">
<label>${this.label}</label>
<div id="input-row">
<input name="cloneUrl" type="text" readonly .value=${this.value} aria-label=${this.label} />
<button class="copy-btn" type="button" @click=${this.copy}>Copy</button>
</div>
<div id="status" class="${showStatus ? "visible" : ""} ${this.status === "copied" ? "success" : ""} ${this.status === "error" ? "error" : ""}" role="status" aria-live="polite">
${this.statusMessage}
</div>
</div>
`;
}
}