blob: 42801395d2fb46f04133b9cc6f26ccdc243e7ad8 [file]
/**
* @license
* Copyright (C) 2026 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 {html, css, LitElement, PropertyValues} from 'lit';
import {customElement, property, state} from 'lit/decorators.js';
export interface ImageInfo {
type: string;
body: string;
}
@customElement('gr-opacity-diff-mode')
export class OpacityDiffMode extends LitElement {
static override get styles() {
return [
css`
:host {
display: block;
}
.wrapper {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
margin: 1em 0;
}
img {
display: block;
height: var(--img-height);
margin: auto;
position: absolute;
width: var(--img-width);
}
#imageRevision {
opacity: var(--my-opacity-value);
z-index: 0.5;
}
#imageDiffContainer {
height: var(--div-height);
margin: auto;
width: var(--div-width);
}
#controlsContainer {
border-top: 1px solid var(--border-color, #ddd);
display: flex;
}
#controlsBox {
display: flex;
justify-content: space-between;
margin: 0 0.5em;
min-width: 32em;
width: 100%;
}
label {
align-items: center;
display: flex;
padding: 1em 0.5em;
}
input {
margin: 0.5em;
}
#opacitySlider {
width: 10em;
}
`
];
}
@property({type: Object})
baseImage?: ImageInfo;
@property({type: Object})
revisionImage?: ImageInfo;
@property({type: Number})
opacityValue = 0.5;
@state()
protected maxHeight = 0;
@state()
protected maxWidth = 0;
@state()
protected scaledWidth: number | null = null;
@state()
protected scaledHeight: number | null = null;
override render() {
return html`
<div
class="wrapper"
style="
--my-opacity-value: ${this.opacityValue};
--img-width: ${this.scaledWidth ? this.scaledWidth + 'px' : 'initial'};
--img-height: ${this.scaledHeight ? this.scaledHeight + 'px' : 'initial'};
--div-width: ${this.maxWidth ? this.maxWidth + 'px' : 'initial'};
--div-height: ${this.maxHeight ? this.maxHeight + 'px' : 'initial'};
"
>
<div id="imageDiffContainer">
<img
@load=${this.onImageLoad}
id="imageBase"
src=${this.computeSrcString(this.baseImage)}
/>
<img
@load=${this.onImageLoad}
data-opacity=${this.opacityValue}
id="imageRevision"
src=${this.computeSrcString(this.revisionImage)}
/>
</div>
<div id="controlsContainer">
<div id="controlsBox">
<label>
<input
id="scaleSizesToggle"
@click=${this.handleScaleSizesToggle}
type="checkbox"
/>
Scale to same size
</label>
<label>
Revision image opacity
<input
id="opacitySlider"
max="1.0"
min="0.0"
@input=${this.handleOpacityChange}
step=".01"
.value=${String(this.opacityValue ?? 0.5)}
/>
</label>
</div>
</div>
</div>
`;
}
override updated(changedProperties: PropertyValues) {
if (
changedProperties.has('baseImage') ||
changedProperties.has('revisionImage')
) {
this.handleImageChange();
}
}
protected onImageLoad(e: Event) {
const target = e.target as HTMLImageElement;
this.maxHeight = Math.max(this.maxHeight, target.naturalHeight);
this.maxWidth = Math.max(this.maxWidth, target.naturalWidth);
}
protected handleImageChange() {
if (this.baseImage === undefined || this.revisionImage === undefined) return;
this.handleOpacityChange();
}
handleOpacityChange(e?: Event) {
const value = e ? (e.target as HTMLInputElement).value : this.opacityValue;
this.opacityValue = Number(value);
}
computeSrcString(image?: ImageInfo) {
if (!image) return '';
return 'data:' + image.type + ';base64, ' + image.body;
}
handleScaleSizesToggle(e?: Event) {
const isChecked = e ? (e.target as HTMLInputElement).checked : false;
if (isChecked) {
this.scaledWidth = this.maxWidth;
this.scaledHeight = this.maxHeight;
} else {
this.scaledWidth = null;
this.scaledHeight = null;
}
}
}
declare global {
interface HTMLElementTagNameMap {
'gr-opacity-diff-mode': OpacityDiffMode;
}
}