Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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 bun.lock

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

9 changes: 9 additions & 0 deletions packages/app/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Suspense, JSX } from "solid-js"

const Home = lazy(() => import("@/pages/home"))
const Session = lazy(() => import("@/pages/session"))
const Automations = lazy(() => import("@/pages/automations"))
const Loading = () => <div class="size-full" />

function UiI18nBridge(props: ParentProps) {
Expand Down Expand Up @@ -140,6 +141,14 @@ export function AppInterface(props: { defaultUrl?: string; children?: JSX.Elemen
</Suspense>
)}
/>
<Route
path="/automations"
component={() => (
<Suspense fallback={<Loading />}>
<Automations />
</Suspense>
)}
/>
<Route path="/:dir" component={DirectoryLayout}>
<Route path="/" component={() => <Navigate href="session" />} />
<Route
Expand Down
36 changes: 36 additions & 0 deletions packages/app/src/components/automation-day-toggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import type { Component } from "solid-js"

interface DayToggleProps {
label: string
active: boolean
onChange: (value: boolean) => void
}

export const AutomationDayToggle: Component<DayToggleProps> = (props) => {
const active = () => props.active
const short = () => {
return props.label.slice(0, 2)
}

const handleClick = () => {
props.onChange(!active())
}

return (
<button
type="button"
aria-pressed={active()}
aria-label={props.label}
title={props.label}
onClick={handleClick}
class="size-8 rounded-full border text-12-medium flex items-center justify-center transition-colors"
classList={{
"bg-surface-base-active border-border-base text-text-strong shadow-xs-border": active(),
"bg-surface-base border-border-weak-base text-text-weak hover:bg-surface-base-hover hover:border-border-base hover:text-text-base":
!active(),
}}
>
{short()}
</button>
)
}
80 changes: 80 additions & 0 deletions packages/app/src/components/automation-time-row.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Select } from "@opencode-ai/ui/select"
import { createMemo, type Component } from "solid-js"

interface TimeRowProps {
index: number
value: string
hours: string[]
minutes: string[]
removeLabel: string
onChange: (value: string) => void
onRemove: () => void
}

export const AutomationTimeRow: Component<TimeRowProps> = (props) => {
const parts = createMemo(() => props.value.split(":"))
const hour = () => {
const value = parts()[0] ?? "00"
return value.padStart(2, "0")
}
const minute = () => {
const value = parts()[1] ?? "00"
return value.padStart(2, "0")
}

const setHour = (value?: string) => {
if (!value) return

props.onChange(`${value}:${minute()}`)
}

const setMinute = (value?: string) => {
if (!value) return

props.onChange(`${hour()}:${value}`)
}

return (
<div class="group flex items-center gap-2 rounded-md bg-surface-base px-2.5 py-2 transition-colors hover:bg-surface-base-hover">
<div class="size-6 rounded-full border border-border-weak-base bg-surface-raised-base text-11-medium text-text-weak flex items-center justify-center">
{props.index + 1}
</div>
<div class="flex-1 flex items-center gap-2">
<Select
options={props.hours}
current={hour()}
value={(value) => value}
label={(value) => value}
onSelect={setHour}
size="small"
variant="secondary"
valueClass="font-mono text-12-medium"
triggerStyle={{ "min-width": "72px" }}
portal={false}
/>
<span class="text-12-medium text-text-weak">:</span>
<Select
options={props.minutes}
current={minute()}
value={(value) => value}
label={(value) => value}
onSelect={setMinute}
size="small"
variant="secondary"
valueClass="font-mono text-12-medium"
triggerStyle={{ "min-width": "72px" }}
portal={false}
/>
</div>
<IconButton
type="button"
icon="trash"
variant="ghost"
onClick={props.onRemove}
aria-label={props.removeLabel}
class="shrink-0 opacity-70 group-hover:opacity-100"
/>
</div>
)
}
22 changes: 22 additions & 0 deletions packages/app/src/components/dialog-automation-delete.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DialogConfirm } from "@/components/dialog-confirm"
import { useLanguage } from "@/context/language"
import type { Automation } from "@opencode-ai/sdk/v2/client"

export function DialogAutomationDelete(props: {
automation: Automation
onDelete: (automation: Automation) => Promise<void>
}) {
const language = useLanguage()

const handleDelete = () => props.onDelete(props.automation)

return (
<DialogConfirm
title={language.t("automations.delete.title")}
message={language.t("automations.delete.confirm", { name: props.automation.name })}
confirmLabel={language.t("automations.delete.button")}
cancelLabel={language.t("common.cancel")}
onConfirm={handleDelete}
/>
)
}
Loading
Loading