Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: enhance search list functionality #47

Merged
merged 1 commit into from
Jul 7, 2024
Merged
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: 3 additions & 0 deletions app.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,9 @@ func (a *App) EnableVideoMenus() {
vimCommandsMenu.AddText("Open Search List", keys.Key("/"), func(cd *menu.CallbackData) {
wruntime.EventsEmit(a.ctx, video.EVT_OPEN_SEARCH_LIST)
})
vimCommandsMenu.AddText("Search Timeline Clip", keys.Shift("f"), func(cd *menu.CallbackData) {
wruntime.EventsEmit(a.ctx, video.EVT_SEARCH_TIMELINE_CLIP)
})

appMenu := a.AppMenu()
appMenu.Items = append(appMenu.Items, &menu.MenuItem{
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/components/List.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<script lang="ts">
import { isVideo, isVideoNode } from "../lib/utils";
import { searchListstore } from "../stores";
import VideoNodeItem from "./VideoNodeItem.svelte";
import VideoItem from "./VideoItem.svelte";
const { activeList, searchIdx } = searchListstore;
</script>

<div
id="content-wrap"
class="z-10 bg-gblue0 min-h-[40vh] max-h-[40vh] min-w-[60vw] max-w-[60vw] rounded border-white border-2 p-2 overflow-y-scroll"
>
<div id="video-clip-list">
<h1 class="text-center font-semibold text-lg">Results</h1>
<ul class="divide-y divide-gray-200 text-white">
{#each $activeList as item, idx}
{#if isVideo(item)}
<VideoItem {item} {idx} selected={idx === $searchIdx} />
{:else if isVideoNode(item)}
<VideoNodeItem {item} {idx} selected={idx === $searchIdx} />
{/if}
{/each}
</ul>
</div>
</div>
97 changes: 19 additions & 78 deletions frontend/src/components/SearchList.svelte
Original file line number Diff line number Diff line change
@@ -1,77 +1,42 @@
<script lang="ts">
import { videoStore, toolingStore, videoFiles, trackStore } from "../stores";
import type { main } from "../../wailsjs/go/models";
import { InsertInterval } from "../../wailsjs/go/main/App";
import { toolingStore, searchListstore } from "../stores";
import List from "./List.svelte";

const { setVimMode, isOpenSearchList, setIsOpenSearchList, setActionMsg } =
toolingStore;
const {
videoNodePos,
setVimMode,
isOpenSearchList,
setIsOpenSearchList,
setVideoNode,
} = toolingStore;
const { addVideoToTrack } = trackStore;
const { searchFiles } = videoFiles;
const { source, setVideoSrc, setCurrentTime, viewVideo } = videoStore;

let searchTerm = "";
let searchIdx = -1;
let searchList: main.Video[] = [];
searchTerm,
moveSearchIdx,
search,
executeAction,
resetSearchListStore,
} = searchListstore;

function handleSearch(
e: Event & {
currentTarget: EventTarget & HTMLInputElement;
},
) {
e.stopPropagation();
searchList = searchFiles(searchTerm);
if (searchList && searchList.length > 0) searchIdx = 0;
search();
}

function keydown(e: KeyboardEvent) {
async function keydown(e: KeyboardEvent) {
e.stopPropagation();
if (e.key === "Escape") {
searchIdx = -1;
searchTerm = "";
searchList = [];
resetSearchListStore();
setIsOpenSearchList(false);
setVimMode(true);
}
if (e.key === "ArrowDown" || (e.key === "n" && e.ctrlKey)) moveSearchIdx(1);
if (e.key === "ArrowUp" || (e.key === "p" && e.ctrlKey)) moveSearchIdx(-1);
if (e.key === "Enter") {
if (searchIdx >= 0 && searchIdx < searchList.length) {
viewVideo(searchList[searchIdx]);

InsertInterval(
$source,
searchList[searchIdx].name,
0,
searchList[searchIdx].duration,
$videoNodePos,
)
.then((tVideo) => {
addVideoToTrack(0, tVideo, $videoNodePos);
setVideoNode(tVideo);
setVideoSrc(tVideo.rid);
setCurrentTime(tVideo.start);
})
.catch(() =>
toolingStore.setActionMsg(
`could not insert ${searchList[searchIdx].name}`,
),
);
}
await executeAction()
.then()
.catch((e) => setActionMsg(e));
}
}

function moveSearchIdx(inc: number) {
if (searchIdx === -1) return;
if (searchIdx + inc < 0) searchIdx = searchList.length - 1;
else if (searchIdx + inc >= searchList.length) searchIdx = 0;
else searchIdx = searchIdx + inc;
}

function transitionEnd(e: TransitionEvent) {
const node = e.target as HTMLElement;
node.focus();
Expand Down Expand Up @@ -115,38 +80,14 @@
class="absolute w-full h-full opacity-40"
on:click={() => setIsOpenSearchList(false)}
/>
<div
id="content-wrap"
class="z-10 bg-gblue0 min-h-[40vh] max-h-[40vh] min-w-[60vw] max-w-[60vw] rounded border-white border-2 p-2 overflow-y-scroll"
>
<div id="video-clip-list">
<h1 class="text-center font-semibold text-lg">Results</h1>
<ul class="divide-y divide-gray-200 text-white">
{#each searchList as video, i}
{#if i === searchIdx}
<li class=" bg-obsternary p-2 truncate rounded-md">
<span class="text-teal font-semibold">></span>
{video.name}
</li>
{:else}
<li
class=" bg-obsbg p-2 truncate rounded-md"
on:click={() => (searchIdx = i)}
>
{video.name}
</li>
{/if}
{/each}
</ul>
</div>
</div>
<List />
<div
class="flex flex-col items-center bg-obsbg min-w-[60vw] rounded border-white border-2 py-2 gap-2"
>
<h1 class="font-semibold">Find Clips</h1>
<h1 class="font-semibold">Search</h1>
<input
type="text"
bind:value={searchTerm}
bind:value={$searchTerm}
on:input={handleSearch}
class="p-1 rounded-sm text-black"
autocorrect="off"
Expand Down
9 changes: 9 additions & 0 deletions frontend/src/components/Timeline.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
toolingStore,
trackStore,
createBooleanStore,
searchListstore,
} from "../stores";
import Playhead from "../icons/Playhead.svelte";
import { slide } from "svelte/transition";
Expand Down Expand Up @@ -75,6 +76,7 @@
unmarkAllLossless,
resetTrackStore,
} = trackStore;
const { setSearchTerm } = searchListstore;

let selectedID = 0;
let trackNode: HTMLDivElement;
Expand Down Expand Up @@ -357,6 +359,13 @@
setIsOpenSearchList(true);
}
});
EventsOn("evt_search_timeline_clip", () => {
if ($vimMode) {
setVimMode(false);
setSearchTerm("/x ");
setIsOpenSearchList(true);
}
});
EventsOn("evt_yank_clip", () => {
if ($videoNode) {
setClipRegister($videoNode);
Expand Down
30 changes: 30 additions & 0 deletions frontend/src/components/VideoItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
import { searchListstore } from "../stores";
import type { main } from "../../wailsjs/go/models";
import { scrollVertical } from "../lib/searchlist";
export let idx: number;
export let item: main.Video;
export let selected: boolean;

let el: HTMLLIElement = null;
const { setSearchIdx } = searchListstore;
$: if (selected && el) {
scrollVertical(el);
}
</script>

{#if selected}
<li class=" bg-obsternary p-2 truncate rounded-md" bind:this={el}>
<span class="text-teal font-semibold">></span>
<span class="font-semibold text-gyellow">[CLIP]</span>
{item.name}
</li>
{:else}
<li
class=" bg-obsbg p-2 truncate rounded-md"
on:click={() => setSearchIdx(idx)}
>
<span class="font-semibold text-gyellow">[CLIP]</span>
{item.name}
</li>
{/if}
30 changes: 30 additions & 0 deletions frontend/src/components/VideoNodeItem.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script lang="ts">
import { searchListstore } from "../stores";
import type { video } from "wailsjs/go/models";
import { scrollVertical } from "../lib/searchlist";
export let idx: number;
export let item: video.VideoNode;
export let selected: boolean;

let el: HTMLLIElement = null;
const { setSearchIdx } = searchListstore;
$: if (selected && el) {
scrollVertical(el);
}
</script>

{#if selected}
<li class=" bg-obsternary p-2 truncate rounded-md" bind:this={el}>
<span class="text-teal font-semibold">></span>
<span class="font-semibold text-teal">[TIMELINE]</span>
{item.name}
</li>
{:else}
<li
class=" bg-obsbg p-2 truncate rounded-md"
on:click={() => setSearchIdx(idx)}
>
<span class="font-semibold text-teal">[TIMELINE]</span>
{item.name}
</li>
{/if}
16 changes: 16 additions & 0 deletions frontend/src/lib/searchlist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function scrollVertical(node: HTMLLIElement) {
const listContainer = document.getElementById("content-wrap");
const listRect = listContainer.getBoundingClientRect();
const nodeRect = node.getBoundingClientRect();

const isNodeVisible =
nodeRect.top >= listRect.top && nodeRect.bottom <= listRect.bottom;

if (!isNodeVisible) {
const scrollY = nodeRect.top - listRect.top + listContainer.scrollTop;
listContainer.scrollTo({
top: scrollY,
behavior: "smooth",
});
}
}
30 changes: 30 additions & 0 deletions frontend/src/lib/timeline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { toolingStore } from "../stores";

export function handleKeybindTrackClipMove() {
const videoNodeDiv = document
.getElementById(`track-0`)
?.querySelector(`div:nth-child(${toolingStore.getCursorIdx() + 1})`)
?.querySelector("div");
if (videoNodeDiv) {
videoNodeDiv.click();
scrollToNode(videoNodeDiv);
}
}

function scrollToNode(node: HTMLDivElement) {
const timelineContainer = document.getElementById("timeline");
const timelineRect = timelineContainer.getBoundingClientRect();
const nodeRect = node.getBoundingClientRect();

const isNodeVisible =
nodeRect.left >= timelineRect.left && nodeRect.right <= timelineRect.right;

if (!isNodeVisible) {
const scrollX =
nodeRect.left - timelineRect.left + timelineContainer.scrollLeft;
timelineContainer.scrollTo({
left: scrollX,
behavior: "smooth",
});
}
}
12 changes: 12 additions & 0 deletions frontend/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { main, video } from "../../wailsjs/go/models";

export function formatSecondsToHMS(seconds: number): string {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
Expand All @@ -10,3 +12,13 @@ export function formatSecondsToHMS(seconds: number): string {

return `${formattedHours}:${formattedMinutes}:${formattedSeconds}`;
}

export type ListType = main.Video | video.VideoNode;

export function isVideoNode(unit: ListType): unit is video.VideoNode {
return (unit as video.VideoNode).losslessexport !== undefined;
}

export function isVideo(unit: ListType): unit is main.Video {
return (unit as main.Video).duration !== undefined;
}
Loading
Loading