Skip to content

Commit eee496b

Browse files
committed
feat(images): enhance image slider with multiple file upload support (#209)
1 parent f1a8d6c commit eee496b

30 files changed

+964
-352
lines changed

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
"@rollup/plugin-terser": "^0.4.4",
3838
"@types/leaflet": "^1.9.20",
3939
"@types/leaflet-providers": "^1.2.5",
40+
"@types/lodash": "^4.17.20",
41+
"@types/node": "^24.7.1",
4042
"@typescript-eslint/eslint-plugin": "^8.45.0",
4143
"@typescript-eslint/parser": "^8.45.0",
4244
"@watchable/unpromise": "^1.0.2",
@@ -51,7 +53,6 @@
5153
"leaflet": "^1.9.4",
5254
"leaflet-providers": "^2.0.0",
5355
"lint-staged": "^16.2.3",
54-
"npm": "^11.6.1",
5556
"postcss-preset-env": "^10.4.0",
5657
"rollup": "^4.52.4",
5758
"rollup-plugin-postcss": "^4.0.2",
@@ -64,6 +65,7 @@
6465
},
6566
"scripts": {
6667
"start": "rollup -c rollup.config.js --bundleConfigAsCjs --watch",
68+
"dev": "pnpm run start",
6769
"build": "pnpm run lint && pnpm run rollup",
6870
"lint": "eslint src/**/*.ts",
6971
"rollup": "rollup -c rollup.config.js --bundleConfigAsCjs",
@@ -74,4 +76,4 @@
7476
"clean_delfiles": "rm -rf node_modules/.cache/rollup-plugin-typescript2/*",
7577
"clean_deldirs": "rm -rf node_modules/.cache/rollup-plugin-typescript2/rpt2_*"
7678
}
77-
}
79+
}

pnpm-lock.yaml

Lines changed: 108 additions & 162 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rollup.config.js

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,13 @@ export default [
4545
{
4646
file: fileOutput,
4747
format: 'es',
48-
sourcemap: dev,
4948
inlineDynamicImports: true,
50-
banner: custombanner,
51-
sourcemapIgnoreList: (relativeSourcePath, sourcemapPath) => {
52-
// will ignore-list all files with node_modules in their paths
53-
return relativeSourcePath.includes('node_modules');
54-
},
49+
sourcemap: dev,
50+
banner: !dev ? custombanner : undefined,
5551
},
5652
],
5753
watch: {
58-
chokidar: {
59-
// Workaround for WSL2-based Docker
60-
usePolling: true,
61-
},
54+
exclude: 'node_modules/**',
6255
},
6356
plugins: [...defaultPlugins, ...plugins],
6457
moduleContext: (id) => {

src/components/shared/vsc-default-card.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty } from 'es-toolkit/compat';
1+
import isEmpty from 'es-toolkit/compat/isEmpty';
22
import { css, CSSResultGroup, html, nothing, TemplateResult } from 'lit';
33
import { customElement, property, state } from 'lit/decorators.js';
44

src/components/vsc-images-slide.ts

Lines changed: 46 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { css, CSSResultGroup, html, PropertyValues, TemplateResult, unsafeCSS } from 'lit';
1+
import { css, CSSResultGroup, html, nothing, PropertyValues, TemplateResult, unsafeCSS } from 'lit';
22
import { customElement, property, state } from 'lit/decorators.js';
33
import { styleMap } from 'lit/directives/style-map.js';
44
import Swiper from 'swiper';
@@ -7,7 +7,7 @@ import swipercss from 'swiper/swiper-bundle.css';
77
import { SwiperOptions } from 'swiper/types';
88

99
import { COMPONENT } from '../constants/const';
10-
import { VehicleStatusCardConfig, ImageItem } from '../types/config';
10+
import { VehicleStatusCardConfig, ImageItem, ImagesSwipeConfig } from '../types/config';
1111
import { SECTION } from '../types/section';
1212
import './shared/vsc-image-item';
1313
import { BaseElement } from '../utils/base-element';
@@ -43,34 +43,20 @@ export class ImagesSlide extends BaseElement {
4343
return html``;
4444
}
4545
const images = this._images;
46-
let styleImages: Record<string, string> = {};
47-
const { height, width, hide_pagination } = this.config.layout_config?.images_swipe || {};
48-
if (height) {
49-
styleImages['--vic-images-slide-height'] = `${height}px`;
50-
}
51-
if (width) {
52-
styleImages['--vic-images-slide-width'] = `${width}px`;
53-
}
46+
const hide_pagination = this.config.layout_config?.images_swipe?.hide_pagination;
5447

5548
return html`
56-
<section id="swiper" style=${styleMap(styleImages)}>
57-
<div class="swiper-container">
58-
<div class="swiper-wrapper">
59-
${images.map(
60-
(image, index) => html`
61-
<vsc-image-item
62-
class="swiper-slide"
63-
id="image-slide-${index}"
64-
.hass=${this.hass}
65-
._imageConfig=${image}
66-
>
67-
</vsc-image-item>
68-
`
69-
)}
70-
</div>
71-
<div class="swiper-pagination" ?hidden=${hide_pagination}></div>
49+
<div class="swiper-container" style=${this._computeStyle()}>
50+
<div class="swiper-wrapper">
51+
${images.map(
52+
(image, index) => html`
53+
<vsc-image-item class="swiper-slide" id="image-slide-${index}" .hass=${this.hass} ._imageConfig=${image}>
54+
</vsc-image-item>
55+
`
56+
)}
7257
</div>
73-
</section>
58+
${!hide_pagination ? html`<div class="swiper-pagination"></div>` : nothing}
59+
</div>
7460
`;
7561
}
7662

@@ -136,6 +122,26 @@ export class ImagesSlide extends BaseElement {
136122
this.swiper = new Swiper(swiperCon, swiperConfig());
137123
}
138124

125+
private _computeStyle() {
126+
const slideConfig = this.config.layout_config?.images_swipe as ImagesSwipeConfig;
127+
const { height, width, hide_pagination } = slideConfig || {};
128+
129+
let styleImages: Record<string, string> = {};
130+
if (height) {
131+
styleImages['--vic-images-slide-height'] = `${height}px`;
132+
}
133+
if (width) {
134+
styleImages['--vic-images-slide-width'] = `${width}px`;
135+
}
136+
if (hide_pagination || this.swiper?.isLocked) {
137+
styleImages['padding-bottom'] = '0';
138+
}
139+
if (this.parentElement?.previousElementSibling !== null) {
140+
styleImages['padding-top'] = 'var(--vic-card-padding)';
141+
}
142+
return styleMap(styleImages);
143+
}
144+
139145
public showImage(index: number): void {
140146
this.updateComplete.then(() => {
141147
const imgId = `image-slide-${index}`;
@@ -156,28 +162,25 @@ export class ImagesSlide extends BaseElement {
156162

157163
static get styles(): CSSResultGroup {
158164
return [
159-
super.styles,
160165
unsafeCSS(swipercss),
161166
css`
162167
:host {
163-
--swiper-pagination-bottom: -4px;
168+
--swiper-pagination-bottom: 0px;
164169
--swiper-theme-color: var(--primary-text-color);
165170
}
166-
* [hidden] {
167-
display: none !important;
168-
}
169-
section {
170-
display: block;
171-
padding: 1em 0px 8px;
172-
}
173-
.swiper-wrapper {
174-
display: flex;
175-
}
171+
176172
.swiper-container {
173+
padding: 0 0 var(--vic-card-padding) 0;
174+
border: none !important;
175+
background: none !important;
176+
overflow: visible;
177177
width: 100%;
178178
height: 100%;
179-
display: block;
180179
}
180+
/* .swiper-wrapper {
181+
flex-direction: initial;
182+
flex-wrap: wrap;
183+
} */
181184
.swiper-slide {
182185
display: flex;
183186
justify-content: center;
@@ -189,20 +192,11 @@ export class ImagesSlide extends BaseElement {
189192
.swiper-slide:active {
190193
scale: 1.02;
191194
}
192-
.swiper-slide .image-index {
193-
position: absolute;
194-
bottom: 0;
195-
left: var(--vic-card-padding);
196-
padding: var(--vic-gutter-gap);
197-
background-color: var(--swiper-theme-color);
198-
color: var(--primary-background-color);
199-
font-size: 1rem;
200-
font-weight: bold;
201-
z-index: 1;
202-
}
203195
204196
.swiper-pagination {
205-
display: block;
197+
/* margin-top: var(--swiper-pagination-bottom); */
198+
display: flex;
199+
justify-content: center;
206200
}
207201
.swiper-pagination-bullet {
208202
background-color: var(--swiper-theme-color);

src/editor/base-editor.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isEmpty } from 'es-toolkit/compat';
1+
import isEmpty from 'es-toolkit/compat/isEmpty';
22
import { HomeAssistantStylesManager } from 'home-assistant-styles-manager';
33
import { css, CSSResultGroup, html, LitElement, TemplateResult } from 'lit';
44
import { property } from 'lit/decorators.js';
@@ -8,12 +8,13 @@ import { EditorConfigAreaSelectedEvent } from '../events';
88
import { EditorIndicatorRowSelectedEvent } from '../events/editor-indicator-row';
99
import { fireEvent, HomeAssistant } from '../ha';
1010
import { showFormDialog } from '../ha/dialogs/form/show-form-dialog';
11+
import { showToast } from '../ha/panels/lovelace/toast';
1112
import { SectionOrder, VehicleStatusCardConfig } from '../types/config';
1213
import { ConfigArea } from '../types/config-area';
1314
import { SECTION } from '../types/section';
1415
import { Create } from '../utils';
1516
import { getSectionFromConfigArea } from '../utils/editor/area-select';
16-
import { selectTree } from '../utils/helpers-dom';
17+
import * as DomHelper from '../utils/helpers-dom';
1718
import { Store } from '../utils/store';
1819
import { VehicleStatusCard } from '../vehicle-status-card';
1920
import { VehicleStatusCardEditor } from './editor';
@@ -57,7 +58,7 @@ export class BaseEditor extends LitElement {
5758
@property({ attribute: false }) public _hass!: HomeAssistant;
5859
@property({ attribute: false }) protected _store!: Store;
5960

60-
@property() _domHelper = selectTree;
61+
@property({ attribute: false }) _domHelper = DomHelper;
6162

6263
protected _stylesManager: HomeAssistantStylesManager;
6364

@@ -123,6 +124,16 @@ export class BaseEditor extends LitElement {
123124
});
124125
}
125126

127+
protected showToast = (message: string, duration = 3000) => {
128+
const localId = this.localName || 'base-editor';
129+
const params = {
130+
id: localId,
131+
message,
132+
duration,
133+
};
134+
return showToast(this, params);
135+
};
136+
126137
protected _getButtonGridCols(): number {
127138
const cols = this._cardConfig?.layout_config?.button_grid?.columns || 2;
128139
return Math.max(2, Math.min(cols, 4)); // Clamp between 2 and 4

src/editor/components/button-card/panel-button-card-main.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { capitalize } from 'es-toolkit';
2-
import { isEmpty, omit } from 'es-toolkit/compat';
1+
import isEmpty from 'es-toolkit/compat/isEmpty';
2+
import omit from 'es-toolkit/compat/omit';
3+
import { capitalize } from 'es-toolkit/string';
34
import { html, TemplateResult, CSSResultGroup, css, PropertyValues, nothing } from 'lit';
45
import { customElement, query, state } from 'lit/decorators.js';
56

@@ -224,10 +225,12 @@ export class PanelButtonCardMain extends ButtonCardBaseEditor {
224225
let secondary = isBase ? btnIndexLabel : `${btnIndexLabel} · Sub-Card`;
225226
const icon = isBase ? 'close' : 'back';
226227
if (area === ButtonArea.DEFAULT_CARD && !isEmpty(this._subLabelSecondary)) {
227-
label = this._subLabelSecondary.label;
228-
if (this._subLabelSecondary.secondary) {
229-
// label += ` · ${this._subLabelSecondary.secondary}`;
230-
secondary = this._subLabelSecondary.secondary;
228+
const subLabelSecondary = this._subLabelSecondary;
229+
if (subLabelSecondary) {
230+
label = subLabelSecondary.label;
231+
if (subLabelSecondary.secondary) {
232+
secondary = subLabelSecondary.secondary;
233+
}
231234
}
232235
}
233236

src/editor/components/slide-images/index.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

0 commit comments

Comments
 (0)