diff --git a/package-lock.json b/package-lock.json
index 470de18b..608b4a8c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,6 @@
"@ffprobe-installer/ffprobe": "2.1.2",
"@iharbeck/ngx-virtual-scroller": "17.0.2",
"@ngx-translate/core": "15.0.0",
- "@tweenjs/tween.js": "^25.0.0",
"an-qrcode": "1.0.7",
"async": "3.2.6",
"body-parser": "1.20.3",
@@ -46,6 +45,7 @@
"@angular/platform-browser": "18.2.8",
"@angular/platform-browser-dynamic": "18.2.8",
"@angular/router": "18.2.8",
+ "@tweenjs/tween.js": "25.0.0",
"@types/node": "22.7.8",
"@typescript-eslint/eslint-plugin": "8.11.0",
"@typescript-eslint/parser": "8.11.0",
@@ -5711,6 +5711,7 @@
"version": "25.0.0",
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-25.0.0.tgz",
"integrity": "sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==",
+ "dev": true,
"license": "MIT"
},
"node_modules/@types/body-parser": {
diff --git a/src/app/app.component.html b/src/app/app.component.html
index 66d11b44..9b9e097f 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,3 +1,22 @@
-
+
+
+
+ 0" class="ml-4">
+ Found {{ duplicates.length }} duplicates
+
+
+
+
+ 0" class="p-4 bg-gray-50">
+
Duplicate videos:
+
+ -
+ {{ d.fileName }}
+ β Tags: {{ d.tags.join(', ') }}
+
+
+
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 5bd4a44f..652c186c 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,21 +1,31 @@
-import { Component } from '@angular/core';
-import { ElectronService } from './providers/electron.service';
+import { Component, OnInit } from '@angular/core';
+import { ElectronService } from './providers/electron.service';
+import { ImageElementService } from './services/image-element.service';
+import type { ImageElement } from '../../interfaces/final-object.interface';
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
-export class AppComponent {
+export class AppComponent implements OnInit {
+ duplicates: ImageElement[] = [];
constructor(
- public electronService: ElectronService
- ) {
+ public electronService: ElectronService,
+ private imageElementService: ImageElementService
+ ) {}
- if (electronService.isElectron()) {
+ ngOnInit(): void {
+ if (this.electronService.isElectron()) {
console.log('Mode electron');
} else {
console.log('Mode web');
}
}
+ /** Called when the user clicks βFind Duplicatesβ */
+ checkDuplicates(): void {
+ this.duplicates = this.imageElementService.findDuplicatesByTagsOrName();
+ console.log('Found duplicates:', this.duplicates);
+ }
}
diff --git a/src/app/components/home.component.ts b/src/app/components/home.component.ts
index 7169ebce..9f7b50af 100644
--- a/src/app/components/home.component.ts
+++ b/src/app/components/home.component.ts
@@ -758,15 +758,27 @@ export class HomeComponent implements OnInit, AfterViewInit {
this.vhaFileHistory = (settingsObject.vhaFileHistory || []);
this.restoreSettingsFromBefore(settingsObject);
this.setOrRestoreLanguage(settingsObject.appState.language, locale);
+
if (this.appState.currentZoomLevel !== 1) {
+ // restore zoom
this.electronService.webFrame.setZoomFactor(this.appState.currentZoomLevel);
+
+ // βββββββββββββββββββββββββ
+ // Immediately recompute gallery layout
+ // βββββββββββββββββββββββββ
+ setTimeout(() => {
+ this.computePreviewWidth();
+ this.virtualScroller.refresh();
+ }, 0);
}
+
if (settingsObject.appState.currentVhaFile) {
this.loadThisVhaFile(settingsObject.appState.currentVhaFile);
} else {
this.wizard.showWizard = true;
this.flickerReduceOverlay = false;
}
+
if (settingsObject.shortcuts) {
this.shortcutService.initializeFromSaved(settingsObject.shortcuts);
}
diff --git a/src/app/components/top/top.component.html b/src/app/components/top/top.component.html
index 390f4d53..23747abc 100644
--- a/src/app/components/top/top.component.html
+++ b/src/app/components/top/top.component.html
@@ -3,7 +3,11 @@
class="folder-container"
[ngClass]="{ 'dark-mode-override': darkMode }"
>
-
+
-
+
+
+
+
+
diff --git a/src/app/components/top/top.component.ts b/src/app/components/top/top.component.ts
index 2d9d076d..a56d3179 100644
--- a/src/app/components/top/top.component.ts
+++ b/src/app/components/top/top.component.ts
@@ -1,10 +1,13 @@
import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { ImageElementService } from '../../services/image-element.service';
@Component({
selector: 'app-top-component',
templateUrl: './top.component.html',
- styleUrls: ['./top.component.scss',
- '../../fonts/icons.scss']
+ styleUrls: [
+ './top.component.scss',
+ '../../fonts/icons.scss'
+ ]
})
export class TopComponent {
@@ -37,12 +40,13 @@ export class TopComponent {
public folderNameArray: string[];
public fileNameArray: string[];
+ constructor(private imageElementService: ImageElementService) {}
+
public folderWordClicked(item: string): void {
this.onFolderWordClicked.emit(item.trim());
}
public fileWordClicked(item: string): void {
- // Strip away any of: {}()[].,
const regex = /{|}|\(|\)|\[|\]|\.|\,/g;
item = item.replace(regex, '');
this.onFileWordClicked.emit(item.trim());
@@ -52,4 +56,8 @@ export class TopComponent {
this.onOpenInExplorer.emit(true);
}
+ /** Called by the toolbar button to trigger duplicate detection */
+ public checkDuplicates(): void {
+ this.imageElementService.findDuplicatesByTagsOrName();
+ }
}
diff --git a/src/app/components/views/filmstrip/filmstrip.component.html b/src/app/components/views/filmstrip/filmstrip.component.html
index 265bbc54..a04f54ed 100644
--- a/src/app/components/views/filmstrip/filmstrip.component.html
+++ b/src/app/components/views/filmstrip/filmstrip.component.html
@@ -14,7 +14,8 @@
height: imgHeight + 'px',
'background-image': 'url(' + fullFilePath + ')',
'background-position-x': '-' + filmXoffset + 'px',
- 'background-size': 'auto ' + imgHeight + 'px'
+ 'background-size': 'auto ' + imgHeight + 'px',
+ 'padding': isVertical(video) ? '2px 4px' : '10px'
}"
>
{{ video.durationDisplay }}
diff --git a/src/app/components/views/filmstrip/filmstrip.component.ts b/src/app/components/views/filmstrip/filmstrip.component.ts
index 7c6a0f52..3764d1f8 100644
--- a/src/app/components/views/filmstrip/filmstrip.component.ts
+++ b/src/app/components/views/filmstrip/filmstrip.component.ts
@@ -29,7 +29,6 @@ export class FilmstripComponent implements OnInit {
@Output() rightClick = new EventEmitter();
@Input() video: ImageElement;
-
@Input() compactView: boolean;
@Input() darkMode: boolean;
@Input() elHeight: number;
@@ -55,6 +54,14 @@ export class FilmstripComponent implements OnInit {
this.fullFilePath = this.filePathService.createFilePath(this.folderPath, this.hubName, 'filmstrips', this.video.hash);
}
+ /**
+ * Return true if this video is portrait (vertical) orientation
+ */
+ public isVertical(video: ImageElement): boolean {
+ console.log('Video dimensions:', video.width, video.height);
+ return video.width < video.height;
+ }
+
updateFilmXoffset($event) {
if (this.hoverScrub) {
const imgWidth = this.imgHeight * (16 / 9); // hardcoded aspect ratio
diff --git a/src/app/services/image-element.service.ts b/src/app/services/image-element.service.ts
index 8507cb79..86895503 100644
--- a/src/app/services/image-element.service.ts
+++ b/src/app/services/image-element.service.ts
@@ -125,5 +125,27 @@ constructor() { }
splice(this.imageElements[position].tags.indexOf(emission.tag), 1);
}
}
+ /**
+ * Find duplicates based on exact fileName match or shared tags
+ */
+ public findDuplicatesByTagsOrName(): ImageElement[] {
+ // 1) Finds all filenames that occur more than once
+ const allNames = this.imageElements.map(el => el.fileName);
+ const dupNames = new Set(
+ allNames.filter((name, i, arr) => arr.indexOf(name) !== i)
+ );
+
+ // 2) Finds all tags that occur more than once
+ const allTags = this.imageElements.flatMap(el => el.tags || []);
+ const dupTags = new Set(
+ allTags.filter((tag, i, arr) => arr.indexOf(tag) !== i)
+ );
+
+ // 3) Returns every element whose name or whose tags hit one of those duplicates
+ return this.imageElements.filter(el =>
+ dupNames.has(el.fileName) ||
+ (el.tags ?? []).some(tag => dupTags.has(tag))
+ );
+}
}