Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 20 additions & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
<router-outlet></router-outlet>
<div class="sticky top-0 bg-white z-50 p-4 shadow-sm flex items-center">
<button (click)="checkDuplicates()" class="p-2 rounded shadow">
🔍 Find Duplicates by Name/Tags
</button>

<span *ngIf="duplicates.length > 0" class="ml-4">
Found {{ duplicates.length }} duplicates
</span>
</div>

<router-outlet></router-outlet>
<app-svg-definitions></app-svg-definitions>

<div *ngIf="duplicates.length > 0" class="p-4 bg-gray-50">
<h3 class="mb-2">Duplicate videos:</h3>
<ul class="list-disc list-inside">
<li *ngFor="let d of duplicates">
{{ d.fileName }}
<span *ngIf="d.tags?.length">— Tags: {{ d.tags.join(', ') }}</span>
</li>
</ul>
</div>
22 changes: 16 additions & 6 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 12 additions & 0 deletions src/app/components/home.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
21 changes: 19 additions & 2 deletions src/app/components/top/top.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
class="folder-container"
[ngClass]="{ 'dark-mode-override': darkMode }"
>
<app-icon class="icon" [icon]="'icon-folder-blank'" (click)="openInExplorer()"></app-icon>
<app-icon
class="icon"
[icon]="'icon-folder-blank'"
(click)="openInExplorer()"
></app-icon>
<span
*ngFor="let item of folderNameArray"
(click)="folderWordClicked(item)"
Expand All @@ -18,7 +22,11 @@
class="file-container"
[ngClass]="{ 'dark-mode-override': darkMode }"
>
<app-icon class="icon" [icon]="'icon-video-blank'" (click)="openInExplorer()"></app-icon>
<app-icon
class="icon"
[icon]="'icon-video-blank'"
(click)="openInExplorer()"
></app-icon>
<span
*ngFor="let item of fileNameArray"
(click)="fileWordClicked(item)"
Expand All @@ -27,3 +35,12 @@
{{ item }}
</span>
</div>

<!-- Find Duplicates Button -->
<app-icon
class="icon"
[icon]="'icon-search'"
(click)="checkDuplicates()"
title="Find duplicates with names/tags"
></app-icon>

14 changes: 11 additions & 3 deletions src/app/components/top/top.component.ts
Original file line number Diff line number Diff line change
@@ -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 {

Expand Down Expand Up @@ -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());
Expand All @@ -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();
}
}
3 changes: 2 additions & 1 deletion src/app/components/views/filmstrip/filmstrip.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}"
>
<span *ngIf="showMeta" class="time">{{ video.durationDisplay }}</span>
Expand Down
9 changes: 8 additions & 1 deletion src/app/components/views/filmstrip/filmstrip.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export class FilmstripComponent implements OnInit {
@Output() rightClick = new EventEmitter<RightClickEmit>();

@Input() video: ImageElement;

@Input() compactView: boolean;
@Input() darkMode: boolean;
@Input() elHeight: number;
Expand All @@ -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
Expand Down
22 changes: 22 additions & 0 deletions src/app/services/image-element.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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))
);
}

}