Skip to content

Commit

Permalink
Merge pull request #47 from Gahara-Editor/feat/search-inserted-clips-…
Browse files Browse the repository at this point in the history
…in-timeline

feat: enhance search list functionality
  • Loading branch information
k1nho authored Jul 7, 2024
2 parents 3efe596 + 8aa5601 commit 1a8cc8f
Show file tree
Hide file tree
Showing 11 changed files with 325 additions and 99 deletions.
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

0 comments on commit 1a8cc8f

Please sign in to comment.