Compare commits
No commits in common. "ae9e7e17ed0a0e3fea951cb1d789f742cce94ea6" and "7d1d8cf8c06115130bb0d8eb27be602b600fad91" have entirely different histories.
ae9e7e17ed
...
7d1d8cf8c0
10 changed files with 44 additions and 183 deletions
|
@ -1,6 +1,5 @@
|
||||||
import type { RunnerController } from './components/runner/Runner.tsx'
|
|
||||||
|
|
||||||
import { ColorModeProvider, ColorModeScript } from '@kobalte/core'
|
import { ColorModeProvider, ColorModeScript } from '@kobalte/core'
|
||||||
|
|
||||||
import { workerInit } from 'mtcute-repl-worker/client'
|
import { workerInit } from 'mtcute-repl-worker/client'
|
||||||
import { createSignal, lazy, onCleanup, onMount, Show } from 'solid-js'
|
import { createSignal, lazy, onCleanup, onMount, Show } from 'solid-js'
|
||||||
import { EditorTabs } from './components/editor/EditorTabs.tsx'
|
import { EditorTabs } from './components/editor/EditorTabs.tsx'
|
||||||
|
@ -17,7 +16,6 @@ export function App() {
|
||||||
const [updating, setUpdating] = createSignal(true)
|
const [updating, setUpdating] = createSignal(true)
|
||||||
const [showSettings, setShowSettings] = createSignal(false)
|
const [showSettings, setShowSettings] = createSignal(false)
|
||||||
const [settingsTab, setSettingsTab] = createSignal<SettingsTab>('accounts')
|
const [settingsTab, setSettingsTab] = createSignal<SettingsTab>('accounts')
|
||||||
const [runnerController, setRunnerController] = createSignal<RunnerController>()
|
|
||||||
|
|
||||||
const [isResizing, setIsResizing] = createSignal(false)
|
const [isResizing, setIsResizing] = createSignal(false)
|
||||||
const [sizes, setSizes] = createSignal([0.5, 0.5])
|
const [sizes, setSizes] = createSignal([0.5, 0.5])
|
||||||
|
@ -78,10 +76,7 @@ export function App() {
|
||||||
<Resizable sizes={sizes()} onSizesChange={e => setSizes(e)} orientation="horizontal" class="size-full max-h-[calc(100vh-57px)]">
|
<Resizable sizes={sizes()} onSizesChange={e => setSizes(e)} orientation="horizontal" class="size-full max-h-[calc(100vh-57px)]">
|
||||||
<ResizablePanel class="h-full overflow-x-auto overflow-y-hidden" minSize={0.2}>
|
<ResizablePanel class="h-full overflow-x-auto overflow-y-hidden" minSize={0.2}>
|
||||||
<EditorTabs />
|
<EditorTabs />
|
||||||
<Editor
|
<Editor class="size-full" />
|
||||||
class="size-full"
|
|
||||||
onRun={() => runnerController()?.run()}
|
|
||||||
/>
|
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle
|
<ResizableHandle
|
||||||
withHandle
|
withHandle
|
||||||
|
@ -95,10 +90,7 @@ export function App() {
|
||||||
class="flex max-h-full flex-col overflow-hidden"
|
class="flex max-h-full flex-col overflow-hidden"
|
||||||
minSize={0.2}
|
minSize={0.2}
|
||||||
>
|
>
|
||||||
<Runner
|
<Runner isResizing={isResizing()} />
|
||||||
isResizing={isResizing()}
|
|
||||||
controllerRef={setRunnerController}
|
|
||||||
/>
|
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</Resizable>
|
</Resizable>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useColorModeValue } from '@kobalte/core'
|
import { useColorModeValue } from '@kobalte/core'
|
||||||
import { KeyCode, KeyMod, editor as mEditor, Uri } from 'monaco-editor'
|
import { editor as mEditor, Uri } from 'monaco-editor'
|
||||||
|
|
||||||
import { createEffect, on, onMount } from 'solid-js'
|
import { createEffect, on, onMount } from 'solid-js'
|
||||||
import { $activeTab, $tabs, type EditorTab } from '../../store/tabs.ts'
|
import { $activeTab, $tabs, type EditorTab } from '../../store/tabs.ts'
|
||||||
|
@ -9,7 +9,6 @@ import './Editor.css'
|
||||||
|
|
||||||
export interface EditorProps {
|
export interface EditorProps {
|
||||||
class?: string
|
class?: string
|
||||||
onRun: () => void
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_CODE = `
|
const DEFAULT_CODE = `
|
||||||
|
@ -23,8 +22,6 @@ export const self = await tg.getMe()
|
||||||
console.log(self)
|
console.log(self)
|
||||||
`.trimStart()
|
`.trimStart()
|
||||||
|
|
||||||
const LOCAL_STORAGE_PREFIX = 'repl:tab-content:'
|
|
||||||
|
|
||||||
function findChangedTab(a: EditorTab[], b: EditorTab[]) {
|
function findChangedTab(a: EditorTab[], b: EditorTab[]) {
|
||||||
const set = new Set(a.map(tab => tab.id))
|
const set = new Set(a.map(tab => tab.id))
|
||||||
for (const tab of b) {
|
for (const tab of b) {
|
||||||
|
@ -42,6 +39,7 @@ export default function Editor(props: EditorProps) {
|
||||||
let editor: mEditor.IStandaloneCodeEditor | undefined
|
let editor: mEditor.IStandaloneCodeEditor | undefined
|
||||||
|
|
||||||
const monacoTheme = useColorModeValue('latte', 'mocha')
|
const monacoTheme = useColorModeValue('latte', 'mocha')
|
||||||
|
// const monacoTheme = () => scheme() === 'dark' ? 'ayu-dark' : 'ayu-light'
|
||||||
const modelsByTab = new Map<string, mEditor.ITextModel>()
|
const modelsByTab = new Map<string, mEditor.ITextModel>()
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
@ -77,24 +75,15 @@ export default function Editor(props: EditorProps) {
|
||||||
await setupMonaco()
|
await setupMonaco()
|
||||||
|
|
||||||
for (const tab of tabs()) {
|
for (const tab of tabs()) {
|
||||||
const storedCode = localStorage.getItem(LOCAL_STORAGE_PREFIX + tab.id)
|
const model = mEditor.createModel(tab.main ? DEFAULT_CODE : '', 'typescript', Uri.parse(`file:///${tab.id}.ts`))
|
||||||
const model = mEditor.createModel(storedCode ?? (tab.main ? DEFAULT_CODE : ''), 'typescript', Uri.parse(`file:///${tab.id}.ts`))
|
|
||||||
modelsByTab.set(tab.id, model)
|
modelsByTab.set(tab.id, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
editor.setModel(modelsByTab.get(activeTab())!)
|
editor.setModel(modelsByTab.get(activeTab())!)
|
||||||
|
|
||||||
editor.addCommand(KeyMod.CtrlCmd | KeyCode.Enter, () => {
|
// editor.onDidChangeModelContent(() => {
|
||||||
props.onRun()
|
// props.onCodeChange(editor?.getValue() ?? '')
|
||||||
})
|
// })
|
||||||
|
|
||||||
editor.onDidChangeModelContent(() => {
|
|
||||||
const currentTab = tabs().find(tab => tab.id === activeTab())!
|
|
||||||
const content = editor?.getModel()?.getValue()
|
|
||||||
if (!currentTab || !content) return
|
|
||||||
|
|
||||||
localStorage.setItem(LOCAL_STORAGE_PREFIX + currentTab.id, content)
|
|
||||||
})
|
|
||||||
|
|
||||||
return () => editor?.dispose()
|
return () => editor?.dispose()
|
||||||
})
|
})
|
||||||
|
@ -147,7 +136,6 @@ export default function Editor(props: EditorProps) {
|
||||||
if (!changed) return
|
if (!changed) return
|
||||||
modelsByTab.get(changed.id)?.dispose()
|
modelsByTab.get(changed.id)?.dispose()
|
||||||
modelsByTab.delete(changed.id)
|
modelsByTab.delete(changed.id)
|
||||||
localStorage.removeItem(LOCAL_STORAGE_PREFIX + changed.id)
|
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
|
import type { DropdownMenuTriggerProps } from '@kobalte/core/dropdown-menu'
|
||||||
import type { mtcute } from 'mtcute-repl-worker/client'
|
import type { mtcute } from 'mtcute-repl-worker/client'
|
||||||
import type { Setter } from 'solid-js'
|
|
||||||
import type { CustomTypeScriptWorker } from '../editor/utils/custom-worker.ts'
|
import type { CustomTypeScriptWorker } from '../editor/utils/custom-worker.ts'
|
||||||
import { timers } from '@fuman/utils'
|
import { timers } from '@fuman/utils'
|
||||||
import { persistentAtom } from '@nanostores/persistent'
|
import { persistentAtom } from '@nanostores/persistent'
|
||||||
import { LucideCheck, LucidePlay, LucidePlug, LucideRefreshCw, LucideSettings2, LucideSkull, LucideUnplug } from 'lucide-solid'
|
import { LucideCheck, LucidePlay, LucidePlug, LucideRefreshCw, LucideSkull, LucideUnplug } from 'lucide-solid'
|
||||||
import { languages, Uri } from 'monaco-editor/esm/vs/editor/editor.api.js'
|
import { languages, Uri } from 'monaco-editor/esm/vs/editor/editor.api.js'
|
||||||
import { createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
import { createEffect, createSignal, on, onCleanup, onMount } from 'solid-js'
|
||||||
import { Dynamic } from 'solid-js/web'
|
import { Dynamic } from 'solid-js/web'
|
||||||
import { toast } from 'solid-sonner'
|
|
||||||
import { Button } from '../../lib/components/ui/button.tsx'
|
import { Button } from '../../lib/components/ui/button.tsx'
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
@ -39,14 +37,7 @@ const $enableVerbose = persistentAtom('repl:verboseLogs', false, {
|
||||||
decode: value => value === 'true',
|
decode: value => value === 'true',
|
||||||
})
|
})
|
||||||
|
|
||||||
export interface RunnerController {
|
export function Runner(props: { isResizing: boolean }) {
|
||||||
run: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Runner(props: {
|
|
||||||
isResizing: boolean
|
|
||||||
controllerRef: Setter<RunnerController | undefined>
|
|
||||||
}) {
|
|
||||||
const [devtoolsIframe, setDevtoolsIframe] = createSignal<HTMLIFrameElement | undefined>()
|
const [devtoolsIframe, setDevtoolsIframe] = createSignal<HTMLIFrameElement | undefined>()
|
||||||
const [runnerIframe, setRunnerIframe] = createSignal<HTMLIFrameElement>()
|
const [runnerIframe, setRunnerIframe] = createSignal<HTMLIFrameElement>()
|
||||||
const [runnerLoaded, setRunnerLoaded] = createSignal(false)
|
const [runnerLoaded, setRunnerLoaded] = createSignal(false)
|
||||||
|
@ -163,10 +154,6 @@ export function Runner(props: {
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.addEventListener('message', handleMessage)
|
window.addEventListener('message', handleMessage)
|
||||||
|
|
||||||
props.controllerRef({
|
|
||||||
run: () => handleRun(),
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
onCleanup(() => {
|
onCleanup(() => {
|
||||||
window.removeEventListener('message', handleMessage)
|
window.removeEventListener('message', handleMessage)
|
||||||
|
@ -181,11 +168,6 @@ export function Runner(props: {
|
||||||
}, { defer: true }))
|
}, { defer: true }))
|
||||||
|
|
||||||
async function handleRun() {
|
async function handleRun() {
|
||||||
if ($activeAccountId.get() === undefined) {
|
|
||||||
toast('You need to log in to run a script')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const getWorker = await languages.typescript.getTypeScriptWorker()
|
const getWorker = await languages.typescript.getTypeScriptWorker()
|
||||||
const worker = await getWorker(Uri.parse('file:///main.ts')) as unknown as CustomTypeScriptWorker
|
const worker = await getWorker(Uri.parse('file:///main.ts')) as unknown as CustomTypeScriptWorker
|
||||||
|
|
||||||
|
@ -268,34 +250,30 @@ export function Runner(props: {
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<div class="mr-2 flex items-center text-xs font-medium">
|
|
||||||
{{
|
|
||||||
offline: 'Disconnected',
|
|
||||||
connecting: 'Connecting...',
|
|
||||||
updating: 'Updating...',
|
|
||||||
connected: 'Ready',
|
|
||||||
}[connectionState()]}
|
|
||||||
<div class={cn(
|
|
||||||
'ml-2 size-2 rounded-full',
|
|
||||||
{
|
|
||||||
connected: 'bg-green-500',
|
|
||||||
updating: 'bg-yellow-500',
|
|
||||||
connecting: 'bg-yellow-500',
|
|
||||||
offline: 'bg-neutral-400',
|
|
||||||
}[connectionState()],
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger
|
<DropdownMenuTrigger
|
||||||
as={(props: DropdownMenuTriggerProps) => (
|
as={(props: DropdownMenuTriggerProps) => (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="xs"
|
||||||
class="size-7"
|
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<LucideSettings2 class="size-4" />
|
{{
|
||||||
|
offline: 'Disconnected',
|
||||||
|
connecting: 'Connecting...',
|
||||||
|
updating: 'Updating...',
|
||||||
|
connected: 'Ready',
|
||||||
|
}[connectionState()]}
|
||||||
|
<div class={cn(
|
||||||
|
'ml-2 size-2 rounded-full',
|
||||||
|
{
|
||||||
|
connected: 'bg-green-500',
|
||||||
|
updating: 'bg-yellow-500',
|
||||||
|
connecting: 'bg-yellow-500',
|
||||||
|
offline: 'bg-neutral-400',
|
||||||
|
}[connectionState()],
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -13,7 +13,6 @@ import {
|
||||||
LucideRefreshCw,
|
LucideRefreshCw,
|
||||||
LucideSearch,
|
LucideSearch,
|
||||||
LucideTrash,
|
LucideTrash,
|
||||||
LucideTriangleAlert,
|
|
||||||
LucideUser,
|
LucideUser,
|
||||||
LucideX,
|
LucideX,
|
||||||
} from 'lucide-solid'
|
} from 'lucide-solid'
|
||||||
|
@ -23,7 +22,6 @@ import { nanoid } from 'nanoid'
|
||||||
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from 'solid-js'
|
import { createEffect, createMemo, createSignal, For, on, onCleanup, Show } from 'solid-js'
|
||||||
import { toast } from 'solid-sonner'
|
import { toast } from 'solid-sonner'
|
||||||
import { copyToClipboard } from '../../lib/clipboard.tsx'
|
import { copyToClipboard } from '../../lib/clipboard.tsx'
|
||||||
import { Alert, AlertDescription, AlertTitle } from '../../lib/components/ui/alert.tsx'
|
|
||||||
import { Badge } from '../../lib/components/ui/badge.tsx'
|
import { Badge } from '../../lib/components/ui/badge.tsx'
|
||||||
import { Button } from '../../lib/components/ui/button.tsx'
|
import { Button } from '../../lib/components/ui/button.tsx'
|
||||||
import { Dialog, DialogContent } from '../../lib/components/ui/dialog.tsx'
|
import { Dialog, DialogContent } from '../../lib/components/ui/dialog.tsx'
|
||||||
|
@ -300,7 +298,7 @@ export function AccountsTab() {
|
||||||
return accounts()
|
return accounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
return accounts()?.filter((account) => {
|
return accounts().filter((account) => {
|
||||||
return account.name.toLowerCase().includes(query) || account.telegramId.toString().includes(query)
|
return account.name.toLowerCase().includes(query) || account.telegramId.toString().includes(query)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -308,25 +306,9 @@ export function AccountsTab() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Show
|
<Show
|
||||||
when={accounts()?.length !== 0}
|
when={accounts().length !== 0}
|
||||||
fallback={(
|
fallback={(
|
||||||
<div class="flex h-full flex-col items-center justify-center gap-4 px-2 text-muted-foreground">
|
<div class="flex h-full flex-col items-center justify-center gap-4 text-muted-foreground">
|
||||||
<Alert variant="destructive" class="max-w-md">
|
|
||||||
<LucideTriangleAlert class="size-4" />
|
|
||||||
<AlertTitle class="font-bold">Warning</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an
|
|
||||||
{' '}
|
|
||||||
<b>unofficial</b>
|
|
||||||
{' '}
|
|
||||||
Telegram application.
|
|
||||||
<br />
|
|
||||||
You might trigger anti-spam measures and get banned.
|
|
||||||
<br />
|
|
||||||
Proceed at your own risk.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
No accounts yet
|
No accounts yet
|
||||||
<div class="flex flex-row gap-2">
|
<div class="flex flex-row gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function TdataImportDialog(props: {
|
||||||
const [error, setError] = createSignal<string | undefined>('')
|
const [error, setError] = createSignal<string | undefined>('')
|
||||||
const [loading, setLoading] = createSignal(false)
|
const [loading, setLoading] = createSignal(false)
|
||||||
|
|
||||||
const accountExists = (id: number) => $accounts.get()?.some(it => it.telegramId === id)
|
const accountExists = (id: number) => $accounts.get().some(it => it.telegramId === id)
|
||||||
|
|
||||||
let abortController: AbortController | undefined
|
let abortController: AbortController | undefined
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
|
|
|
@ -81,7 +81,7 @@ function QrLoginStep(props: StepProps<'qr'>) {
|
||||||
/>
|
/>
|
||||||
) : <Spinner indeterminate class="size-10" />}
|
) : <Spinner indeterminate class="size-10" />}
|
||||||
</div>
|
</div>
|
||||||
<ol class="mt-4 list-inside list-decimal text-sm text-muted-foreground">
|
<ol class="text-muted-foreground mt-4 list-inside list-decimal text-sm">
|
||||||
<li>Open Telegram on your phone</li>
|
<li>Open Telegram on your phone</li>
|
||||||
<li>
|
<li>
|
||||||
Go to
|
Go to
|
||||||
|
@ -147,7 +147,7 @@ function PhoneNumberStep(props: StepProps<'phone'>) {
|
||||||
<h2 class="mt-4 text-xl font-bold">
|
<h2 class="mt-4 text-xl font-bold">
|
||||||
Log in with phone number
|
Log in with phone number
|
||||||
</h2>
|
</h2>
|
||||||
<div class="mt-2 text-center text-sm text-muted-foreground">
|
<div class="text-muted-foreground mt-2 text-center text-sm">
|
||||||
Please confirm your country code
|
Please confirm your country code
|
||||||
<br />
|
<br />
|
||||||
and enter your phone number
|
and enter your phone number
|
||||||
|
@ -177,7 +177,7 @@ function PhoneNumberStep(props: StepProps<'phone'>) {
|
||||||
<TextFieldErrorMessage>{error()}</TextFieldErrorMessage>
|
<TextFieldErrorMessage>{error()}</TextFieldErrorMessage>
|
||||||
</TextFieldRoot>
|
</TextFieldRoot>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
<div class="text-center text-sm text-muted-foreground">
|
<div class="text-muted-foreground text-center text-sm">
|
||||||
or,
|
or,
|
||||||
{' '}
|
{' '}
|
||||||
<a
|
<a
|
||||||
|
@ -309,7 +309,7 @@ function OtpStep(props: StepProps<'otp'>) {
|
||||||
Wrong number?
|
Wrong number?
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 text-center text-sm text-muted-foreground">
|
<div class="text-muted-foreground mt-4 text-center text-sm">
|
||||||
{description()}
|
{description()}
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4 flex flex-col items-center text-center">
|
<div class="mt-4 flex flex-col items-center text-center">
|
||||||
|
@ -363,7 +363,7 @@ function OtpStep(props: StepProps<'otp'>) {
|
||||||
</OTPFieldGroup>
|
</OTPFieldGroup>
|
||||||
</OTPField>
|
</OTPField>
|
||||||
{error() && (
|
{error() && (
|
||||||
<div class="mt-1 text-sm text-error-foreground">{error()}</div>
|
<div class="text-error-foreground mt-1 text-sm">{error()}</div>
|
||||||
)}
|
)}
|
||||||
</Show>
|
</Show>
|
||||||
|
|
||||||
|
@ -438,7 +438,7 @@ function PasswordStep(props: StepProps<'password'>) {
|
||||||
<h2 class="text-xl font-bold">
|
<h2 class="text-xl font-bold">
|
||||||
2FA password
|
2FA password
|
||||||
</h2>
|
</h2>
|
||||||
<div class="mt-4 text-center text-sm text-muted-foreground">
|
<div class="text-muted-foreground mt-4 text-center text-sm">
|
||||||
Your account is protected with an additional password.
|
Your account is protected with an additional password.
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
|
@ -483,9 +483,9 @@ function DoneStep(props: StepProps<'done'>) {
|
||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<AccountAvatar
|
<AccountAvatar
|
||||||
account={props.ctx.account}
|
account={props.ctx.account}
|
||||||
class="mb-4 size-24 animate-scale-up shadow-sm fill-mode-forwards"
|
class="animate-scale-up fill-mode-forwards mb-4 size-24 shadow-sm"
|
||||||
/>
|
/>
|
||||||
<div class="animate-fade-out-down text-center font-medium fill-mode-forwards">
|
<div class="animate-fade-out-down fill-mode-forwards text-center font-medium">
|
||||||
Welcome,
|
Welcome,
|
||||||
{' '}
|
{' '}
|
||||||
{props.ctx.account.name}
|
{props.ctx.account.name}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
import type { AlertRootProps } from '@kobalte/core/alert'
|
|
||||||
import type { PolymorphicProps } from '@kobalte/core/polymorphic'
|
|
||||||
import type { VariantProps } from 'class-variance-authority'
|
|
||||||
import type { ComponentProps, ValidComponent } from 'solid-js'
|
|
||||||
import { Alert as AlertPrimitive } from '@kobalte/core/alert'
|
|
||||||
import { cva } from 'class-variance-authority'
|
|
||||||
import { splitProps } from 'solid-js'
|
|
||||||
import { cn } from '../../utils.ts'
|
|
||||||
|
|
||||||
export const alertVariants = cva(
|
|
||||||
'relative w-full rounded-lg border px-4 py-3 text-sm [&:has(svg)]:pl-11 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground',
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: 'bg-background text-foreground',
|
|
||||||
destructive:
|
|
||||||
'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: 'default',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
type alertProps<T extends ValidComponent = 'div'> = AlertRootProps<T> &
|
|
||||||
VariantProps<typeof alertVariants> & {
|
|
||||||
class?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Alert<T extends ValidComponent = 'div'>(props: PolymorphicProps<T, alertProps<T>>) {
|
|
||||||
const [local, rest] = splitProps(props as alertProps, ['class', 'variant'])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<AlertPrimitive
|
|
||||||
class={cn(
|
|
||||||
alertVariants({
|
|
||||||
variant: props.variant,
|
|
||||||
}),
|
|
||||||
local.class,
|
|
||||||
)}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AlertTitle(props: ComponentProps<'div'>) {
|
|
||||||
const [local, rest] = splitProps(props, ['class'])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
class={cn('font-medium leading-5 tracking-tight', local.class)}
|
|
||||||
{...rest}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function AlertDescription(props: ComponentProps<'div'>) {
|
|
||||||
const [local, rest] = splitProps(props, ['class'])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div class={cn('text-sm [&_p]:leading-relaxed', local.class)} {...rest} />
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -2,13 +2,13 @@ import type { TelegramAccount } from 'mtcute-repl-worker/client'
|
||||||
import { computed } from 'nanostores'
|
import { computed } from 'nanostores'
|
||||||
import { linkedAtom } from './link.ts'
|
import { linkedAtom } from './link.ts'
|
||||||
|
|
||||||
export const $accounts = linkedAtom<TelegramAccount[] | undefined>('accounts')
|
export const $accounts = linkedAtom<TelegramAccount[]>('accounts')
|
||||||
export const $activeAccountId = linkedAtom<string | undefined>('activeAccountId')
|
export const $activeAccountId = linkedAtom<string | undefined>('activeAccountId')
|
||||||
|
|
||||||
export const $activeAccount = computed([$accounts, $activeAccountId], (accounts, activeAccountId) => {
|
export const $activeAccount = computed([$accounts, $activeAccountId], (accounts, activeAccountId) => {
|
||||||
if (!activeAccountId) return null
|
if (!activeAccountId) return null
|
||||||
|
|
||||||
const account = accounts?.find(account => account.id === activeAccountId)
|
const account = accounts.find(account => account.id === activeAccountId)
|
||||||
if (!account) return null
|
if (!account) return null
|
||||||
|
|
||||||
return account
|
return account
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { persistentAtom } from '@nanostores/persistent'
|
|
||||||
import { atom } from 'nanostores'
|
import { atom } from 'nanostores'
|
||||||
|
|
||||||
export interface EditorTab {
|
export interface EditorTab {
|
||||||
|
@ -7,15 +6,12 @@ export interface EditorTab {
|
||||||
main: boolean
|
main: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const $tabs = persistentAtom<EditorTab[]>('repl:tabs', [
|
export const $tabs = atom<EditorTab[]>([
|
||||||
{
|
{
|
||||||
id: 'main',
|
id: 'main',
|
||||||
fileName: 'main.ts',
|
fileName: 'main.ts',
|
||||||
main: true,
|
main: true,
|
||||||
},
|
},
|
||||||
], {
|
])
|
||||||
encode: JSON.stringify,
|
|
||||||
decode: JSON.parse,
|
|
||||||
})
|
|
||||||
|
|
||||||
export const $activeTab = atom('main')
|
export const $activeTab = atom('main')
|
||||||
|
|
|
@ -141,16 +141,6 @@ window.addEventListener('message', async ({ data }) => {
|
||||||
currentScriptId = nanoid()
|
currentScriptId = nanoid()
|
||||||
await swInvokeMethodInner({ event: 'UPLOAD_SCRIPT', name: currentScriptId, files: data.files }, asNonNull(navigator.serviceWorker.controller))
|
await swInvokeMethodInner({ event: 'UPLOAD_SCRIPT', name: currentScriptId, files: data.files }, asNonNull(navigator.serviceWorker.controller))
|
||||||
|
|
||||||
if (!window.tg) {
|
|
||||||
// shouldnt happen but just in case
|
|
||||||
console.warn('[mtcute-repl] Telegram client not initialized yet')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastConnectionState === 'offline') {
|
|
||||||
await window.tg.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
const el = document.createElement('script')
|
const el = document.createElement('script')
|
||||||
el.type = 'module'
|
el.type = 'module'
|
||||||
let script = `import * as result from "/sw/runtime/script/${currentScriptId}/main.js";`
|
let script = `import * as result from "/sw/runtime/script/${currentScriptId}/main.js";`
|
||||||
|
@ -187,7 +177,6 @@ window.addEventListener('message', async ({ data }) => {
|
||||||
initClient(lastAccountId, data.verboseLogs)
|
initClient(lastAccountId, data.verboseLogs)
|
||||||
}
|
}
|
||||||
window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, HOST_ORIGIN)
|
window.parent.postMessage({ event: 'CONNECTION_STATE', value: 'offline' }, HOST_ORIGIN)
|
||||||
lastConnectionState = 'offline'
|
|
||||||
} else if (data.event === 'RECONNECT') {
|
} else if (data.event === 'RECONNECT') {
|
||||||
if (window.tg !== undefined) {
|
if (window.tg !== undefined) {
|
||||||
window.tg.connect()
|
window.tg.connect()
|
||||||
|
|
Loading…
Reference in a new issue