Skip to content

Commit

Permalink
Fix some bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
patdx committed Jan 5, 2025
1 parent 1d358e6 commit 544a6d0
Show file tree
Hide file tree
Showing 7 changed files with 617 additions and 146 deletions.
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
- fix broken timer buttons
- syncing between devices
- use evolu or one(?) that crsqlite related thing
- disable unnecessary text highlighting
Expand Down
7 changes: 6 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,12 @@ export function Layout({ children }: { children: React.ReactNode }) {
</head>
<body>
<QueryClientProvider client={queryClient}>
<konsta.App id="app" safeAreas theme="ios">
<konsta.App
id="app"
safeAreas
theme="ios"
className="min-h-svh max-h-svh h-svh"
>
{children}
</konsta.App>
</QueryClientProvider>
Expand Down
241 changes: 122 additions & 119 deletions app/routes/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { Navbar, Page } from 'konsta/react'
import {
Navbar,
Page,
Block,
BlockTitle,
List,
ListItem,
Button,
} from 'konsta/react'
import type { Route } from './+types/_index'
import { useQuery } from '@tanstack/react-query'
import sampleSrtUrl from '../assets/sample.srt?url'
import { Link } from 'react-router'
import { Link as RouterLink } from 'react-router'
import { once } from 'lodash-es'
import { use } from 'react'

Expand All @@ -28,7 +36,6 @@ export default function Home({ loaderData }: Route.ComponentProps) {

const EditFilesPage = () => {
const id = useId()

const [isProcessing, setProcessing] = useSignal(false)

const result = useQuery({
Expand All @@ -40,8 +47,6 @@ const EditFilesPage = () => {
},
})

// [data, handler]

const data = () => result.data
const handler = {
refetch: result.refetch,
Expand Down Expand Up @@ -80,127 +85,125 @@ const EditFilesPage = () => {
}
}

const inputRef = useRef<HTMLInputElement>(null)

const parseVideo = use(parseVideoPromise())

return (
<div className="min-h-full bg-gray-50">
<div className="pt-safe"></div>

<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="py-8">
<h1 className="text-3xl font-bold text-gray-900">Subtitle App</h1>
<p className="mt-2 text-sm text-gray-600">Play your subtitle files</p>
</div>

<div className="rounded-lg bg-white py-8 shadow-sm">
<input
id={`${id}-file-upload`}
className="hidden"
type="file"
accept=".zip,.srt,application/zip"
onChange={async (event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
target.value = ''
if (!file) return
<>
<BlockTitle className="text-2xl px-4">Subtitle Files</BlockTitle>
<Block className="px-4">
<p className="text-sm text-gray-600">
Import and manage your subtitle files
</p>
</Block>

<Block strong inset className="space-y-4 text-center">
<input
ref={inputRef}
id={`${id}-file-upload`}
className="hidden"
type="file"
accept=".zip,.srt,application/zip"
onChange={async (event) => {
const target = event.target as HTMLInputElement
const file = target.files?.[0]
target.value = ''
if (!file) return
await handleFile(file)
}}
/>

<div>
<Button
onClick={() => inputRef.current?.click()}
// component="label"

// htmlFor={`${id}-file-upload`}
large
rounded
raised
>
Import SRT or ZIP
<Show when={isProcessing}>
<LoadingIcon />
</Show>
</Button>

<Button
clear
className="mt-4"
onClick={async () => {
const blob = await fetch(sampleSrtUrl).then((result) =>
result.blob(),
)
const file = new File([blob], 'sample.srt')
await handleFile(file)
}}
/>

<div className="flex flex-col items-center justify-center">
<label
tabIndex={0}
htmlFor={`${id}-file-upload`}
className="flex items-center justify-center gap-3 rounded-lg bg-blue-600 px-6 py-3 text-lg font-medium text-white shadow-sm transition hover:bg-blue-700 active:bg-blue-800"
>
Import SRT or ZIP
<Show when={isProcessing}>
<LoadingIcon />
</Show>
</label>

<button
type="button"
className="mt-4 text-sm text-gray-600 hover:text-gray-900 hover:underline"
onClick={async () => {
const blob = await fetch(sampleSrtUrl).then((result) =>
result.blob(),
)
const file = new File([blob], 'sample.srt')
await handleFile(file)
}}
>
or try with a sample file
</button>
</div>

<div className="mt-8 space-y-4">
<For each={data}>
{(file) => {
let metadata
try {
metadata = parseVideo(file.name)
} catch (err) {
console.warn(err)
}
>
Try with sample file
</Button>
</div>
</Block>

return (
<div
key={file.id}
className="group overflow-hidden rounded-lg border border-gray-200 bg-white transition hover:bg-gray-50"
<List strongIos outlineIos>
<For each={data}>
{(file) => {
let metadata
try {
metadata = parseVideo(file.name)
} catch (err) {
console.warn(err)
}

return (
<ListItem
key={file.id}
link
linkComponent={RouterLink}
linkProps={{ to: `/play?id=${file.id}` }}
title={file.name}
after={
<Button
clear
className="k-color-brand-red"
onClick={async (e) => {
e.preventDefault()
const db = await initAndGetDb()
const tx = db.transaction(['files', 'lines'], 'readwrite')
tx.objectStore('files').delete(file.id)
let cursor = await tx
.objectStore('lines')
.index('by-file-id')
.openKeyCursor(file.id)
while (cursor) {
await tx.objectStore('lines').delete(cursor.primaryKey)
cursor = await cursor.continue()
}
tx.commit()
handler.refetch()
}}
>
<Link to={`/play?id=${file.id}`} className="block p-4">
<div className="flex items-center justify-between">
<div className="min-w-0 flex-1">
<h3 className="truncate text-lg font-medium text-gray-900">
{file.name}
</h3>
<div className="mt-2 flex flex-wrap gap-2">
{metadata?.season && (
<BadgeRed>Season {metadata.season}</BadgeRed>
)}
{metadata?.episode?.map((item) => (
<BadgeBlue>Episode {item}</BadgeBlue>
))}
</div>
</div>
<button
className="ml-4 flex-shrink-0 text-sm font-medium text-red-600 opacity-0 transition hover:text-red-900 group-hover:opacity-100"
onClick={async (e) => {
e.preventDefault()
const db = await initAndGetDb()
const tx = db.transaction(
['files', 'lines'],
'readwrite',
)
tx.objectStore('files').delete(file.id)
let cursor = await tx
.objectStore('lines')
.index('by-file-id')
.openKeyCursor(file.id)
while (cursor) {
await tx
.objectStore('lines')
.delete(cursor.primaryKey)
cursor = await cursor.continue()
}
tx.commit()
handler.refetch()
}}
>
Delete
</button>
</div>
</Link>
Delete
</Button>
}
footer={
<div className="flex gap-2">
{metadata?.season && (
<span className="text-red-600">
Season {metadata.season}
</span>
)}
{metadata?.episode?.map((item) => (
<span className="text-blue-600">Episode {item}</span>
))}
</div>
)
}}
</For>
</div>
</div>
</div>

<div className="pb-safe"></div>
</div>
}
/>
)
}}
</For>
</List>
</>
)
}
1 change: 1 addition & 0 deletions app/shared/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export const Controls = observer(() => {
icon={<LeftIcon />}
text={'0.1s'}
onClick={() => {
console.log('left')
setClock({
lastActionAt: Date.now(),
lastTimeElapsedMs: getTimeElapsed() - 100,
Expand Down
57 changes: 32 additions & 25 deletions app/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,6 @@ export const nodeIsActive = (node: Entry, currentTime: number): boolean => {
return currentTime > node.from && currentTime < node.to
}

const elapsed = mobx.observable.box(0)
export const getTimeElapsed = () => elapsed.get()
export const setTimeElapsed = mobx.action((value: number) => elapsed.set(value))

export const getTimeElapsedAsDuration = () => {
const d = Duration.fromMillis(getTimeElapsed()).shiftTo(
'hours',
'minutes',
'seconds',
'milliseconds',
)
// console.log(d);
return d
}

export const getActiveNodes = (
nodes: Entry[] = [],
currentTime: number,
Expand Down Expand Up @@ -158,17 +143,22 @@ export class ClockStore {
playSpeed = 1
isPlaying = false

updateElapsedTime() {
if (!clock.isPlaying) {
return
}
/** is calculated based on lastActionAt, playSpeed and lastTimeElapsedMs */
actualTimeElapsedMs = 0

const timeSinceLastAction =
Math.abs(Date.now() - clock.lastActionAt) * clock.playSpeed
calculateActualTimeElapsedMs() {
const timeSinceLastAction = this.isPlaying
? Math.abs(Date.now() - clock.lastActionAt) * clock.playSpeed
: 0

setTimeElapsed(timeSinceLastAction + clock.lastTimeElapsedMs)
this.actualTimeElapsedMs = timeSinceLastAction + clock.lastTimeElapsedMs
}

requestAnimationFrame(this.updateElapsedTime)
tick() {
this.calculateActualTimeElapsedMs()
if (clock.isPlaying) {
requestAnimationFrame(this.tick)
}
}

toggleIsPlaying(isPlaying: boolean) {
Expand All @@ -182,16 +172,33 @@ export class ClockStore {
lastTimeElapsedMs: getTimeElapsed(),
isPlaying,
})
this.updateElapsedTime()
this.tick()
} else {
disableNoSleep()
}
}

setClock(value: Partial<typeof clock>) {
Object.assign(this, value)
this.calculateActualTimeElapsedMs()
}
}

export const clock = new ClockStore()

export const setClock = (value: Partial<typeof clock>) => mobx.set(clock, value)
export const setClock = (value: Partial<typeof clock>) => clock.setClock(value)
export const getTimeElapsed = () => clock.actualTimeElapsedMs

export const getTimeElapsedAsDuration = () => {
const d = Duration.fromMillis(getTimeElapsed()).shiftTo(
'hours',
'minutes',
'seconds',
'milliseconds',
)
// console.log(d);
return d
}

export const TEXT_SIZES = [
'text-sm',
Expand Down
Loading

0 comments on commit 544a6d0

Please sign in to comment.