mirror of
https://github.com/MetaCubeX/metacubexd.git
synced 2024-11-24 09:45:35 +08:00
feat(modals): add modal component (#268)
* feat: add modal component * feat: add modal component * fix: proxies page arrow button problem * fix: button radius problem * feat: add modal component * feat: add modal component --------- Signed-off-by: Alpha <61853980+AlphaGHX@users.noreply.github.com>
This commit is contained in:
parent
04c8d2524b
commit
eca9a160cc
@ -33,7 +33,12 @@ export const Button: ParentComponent<
|
||||
<div class="loading loading-spinner" />
|
||||
</Show>
|
||||
|
||||
<span class="truncate" classList={{ 'flex-1': !local.icon }}>
|
||||
<span
|
||||
class="truncate rounded-none"
|
||||
classList={{
|
||||
'flex-1': !local.icon,
|
||||
}}
|
||||
>
|
||||
{props.icon || props.children}
|
||||
</span>
|
||||
</button>
|
||||
|
@ -28,7 +28,7 @@ export const Collapse: ParentComponent<Props> = (props) => {
|
||||
<div
|
||||
class={twMerge(
|
||||
getCollapseClassName(),
|
||||
'collapse collapse-arrow select-none overflow-visible border-secondary bg-base-200 drop-shadow-md',
|
||||
'collapse collapse-arrow select-none overflow-visible border-secondary bg-base-200 shadow-md',
|
||||
)}
|
||||
>
|
||||
<div
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createForm } from '@felte/solid'
|
||||
import { validator } from '@felte/validator-zod'
|
||||
import { IconX } from '@tabler/icons-solidjs'
|
||||
import { IconNetwork, IconX } from '@tabler/icons-solidjs'
|
||||
import type {
|
||||
DragEventHandler,
|
||||
Draggable,
|
||||
@ -18,12 +18,11 @@ import {
|
||||
import { uniq } from 'lodash'
|
||||
import { Component, For, Index, Show, createSignal } from 'solid-js'
|
||||
import { z } from 'zod'
|
||||
import { Button, ConfigTitle } from '~/components'
|
||||
import { Button, ConfigTitle, Modal } from '~/components'
|
||||
import {
|
||||
CONNECTIONS_TABLE_ACCESSOR_KEY,
|
||||
CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER,
|
||||
CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY,
|
||||
MODAL,
|
||||
TAILWINDCSS_SIZE,
|
||||
} from '~/constants'
|
||||
import { useI18n } from '~/i18n'
|
||||
@ -107,13 +106,14 @@ const TagClientSourceIPWithNameForm: Component = () => {
|
||||
}
|
||||
|
||||
export const ConnectionsSettingsModal = (props: {
|
||||
ref?: (el: HTMLDialogElement) => void
|
||||
order: ConnectionsTableColumnOrder
|
||||
visible: ConnectionsTableColumnVisibility
|
||||
onOrderChange: (value: ConnectionsTableColumnOrder) => void
|
||||
onVisibleChange: (value: ConnectionsTableColumnVisibility) => void
|
||||
}) => {
|
||||
const modalID = MODAL.CONNECTIONS_SETTINGS
|
||||
const [t] = useI18n()
|
||||
|
||||
const [activeKey, setActiveKey] =
|
||||
createSignal<CONNECTIONS_TABLE_ACCESSOR_KEY | null>(null)
|
||||
|
||||
@ -179,25 +179,23 @@ export const ConnectionsSettingsModal = (props: {
|
||||
}
|
||||
|
||||
return (
|
||||
<dialog id={modalID} class="modal modal-bottom sm:modal-middle">
|
||||
<div
|
||||
class="modal-box flex flex-col gap-4"
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
>
|
||||
<div class="sticky top-0 z-50 flex items-center justify-end">
|
||||
<Modal
|
||||
ref={(el) => props.ref?.(el)}
|
||||
icon={<IconNetwork size={24} />}
|
||||
title={t('connectionsSettings')}
|
||||
action={
|
||||
<Button
|
||||
class="btn-circle btn-sm"
|
||||
class="btn-neutral btn-sm"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${modalID}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.close()
|
||||
props.onOrderChange(CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER)
|
||||
props.onVisibleChange(CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY)
|
||||
}}
|
||||
icon={<IconX size={20} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
>
|
||||
{t('reset')}
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<ConfigTitle withDivider>{t('tableSize')}</ConfigTitle>
|
||||
|
||||
@ -267,23 +265,7 @@ export const ConnectionsSettingsModal = (props: {
|
||||
</DragOverlay>
|
||||
</DragDropProvider>
|
||||
</div>
|
||||
|
||||
<div class="modal-action">
|
||||
<Button
|
||||
class="btn-neutral btn-sm ml-auto mt-4 block"
|
||||
onClick={() => {
|
||||
props.onOrderChange(CONNECTIONS_TABLE_INITIAL_COLUMN_ORDER)
|
||||
props.onVisibleChange(CONNECTIONS_TABLE_INITIAL_COLUMN_VISIBILITY)
|
||||
}}
|
||||
>
|
||||
{t('reset')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -1,32 +1,21 @@
|
||||
import { IconX } from '@tabler/icons-solidjs'
|
||||
import { IconNetwork } from '@tabler/icons-solidjs'
|
||||
import { Component, Show } from 'solid-js'
|
||||
import { MODAL } from '~/constants'
|
||||
import { Modal } from '~/components'
|
||||
import { useI18n } from '~/i18n'
|
||||
import { allConnections } from '~/signals'
|
||||
import { Button } from './Button'
|
||||
|
||||
export const ConnectionsTableDetailsModal: Component<{
|
||||
ref?: (el: HTMLDialogElement) => void
|
||||
selectedConnectionID?: string
|
||||
}> = (props) => {
|
||||
const modalID = MODAL.CONNECTIONS_TABLE_DETAILS
|
||||
const [t] = useI18n()
|
||||
|
||||
return (
|
||||
<dialog id={modalID} class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box">
|
||||
<div class="sticky top-0 z-50 flex items-center justify-end">
|
||||
<Button
|
||||
class="btn-circle btn-sm"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${modalID}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.close()
|
||||
}}
|
||||
<Modal
|
||||
ref={(el) => props.ref?.(el)}
|
||||
icon={<IconNetwork size={24} />}
|
||||
title={t('connectionsDetails')}
|
||||
>
|
||||
<IconX size={20} />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Show when={props.selectedConnectionID}>
|
||||
<pre>
|
||||
<code>
|
||||
@ -40,11 +29,6 @@ export const ConnectionsTableDetailsModal: Component<{
|
||||
</code>
|
||||
</pre>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { IconX } from '@tabler/icons-solidjs'
|
||||
import { For } from 'solid-js'
|
||||
import { Button, ConfigTitle } from '~/components'
|
||||
import { IconFileStack } from '@tabler/icons-solidjs'
|
||||
import { Component, For } from 'solid-js'
|
||||
import { ConfigTitle, Modal } from '~/components'
|
||||
import {
|
||||
LOGS_TABLE_MAX_ROWS_LIST,
|
||||
LOG_LEVEL,
|
||||
MODAL,
|
||||
TAILWINDCSS_SIZE,
|
||||
} from '~/constants'
|
||||
import { useI18n } from '~/i18n'
|
||||
@ -17,27 +16,18 @@ import {
|
||||
setLogsTableSize,
|
||||
} from '~/signals'
|
||||
|
||||
export const LogsSettingsModal = () => {
|
||||
const modalID = MODAL.LOGS_SETTINGS
|
||||
export const LogsSettingsModal: Component<{
|
||||
ref?: (el: HTMLDialogElement) => void
|
||||
}> = (props) => {
|
||||
const [t] = useI18n()
|
||||
|
||||
return (
|
||||
<dialog id={modalID} class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box flex flex-col gap-4">
|
||||
<div class="sticky top-0 z-50 flex items-center justify-end">
|
||||
<Button
|
||||
class="btn-circle btn-sm"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${modalID}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.close()
|
||||
}}
|
||||
icon={<IconX size={20} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
ref={(el) => props.ref?.(el)}
|
||||
icon={<IconFileStack size={24} />}
|
||||
title={t('logsSettings')}
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<ConfigTitle withDivider>{t('tableSize')}</ConfigTitle>
|
||||
|
||||
@ -90,10 +80,6 @@ export const LogsSettingsModal = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
53
src/components/Modal.tsx
Normal file
53
src/components/Modal.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { IconX } from '@tabler/icons-solidjs'
|
||||
import { JSX, ParentComponent, Show, children } from 'solid-js'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { Button } from '~/components'
|
||||
|
||||
type Props = {
|
||||
ref?: (el: HTMLDialogElement) => void
|
||||
icon?: JSX.Element
|
||||
title?: JSX.Element
|
||||
action?: JSX.Element
|
||||
}
|
||||
|
||||
const actionClass =
|
||||
'sticky bottom-0 z-50 flex items-center justify-end bg-base-100 bg-opacity-80 p-6 backdrop-blur'
|
||||
|
||||
export const Modal: ParentComponent<Props> = (props) => {
|
||||
let dialogRef: HTMLDialogElement | undefined
|
||||
|
||||
return (
|
||||
<dialog
|
||||
ref={(el) => (dialogRef = el) && props.ref?.(el)}
|
||||
class="modal modal-bottom sm:modal-middle"
|
||||
>
|
||||
<div class="modal-box p-0" onContextMenu={(e) => e.preventDefault()}>
|
||||
<div class={twMerge(actionClass, 'top-0 justify-between')}>
|
||||
<div class="flex items-center gap-4 text-xl font-bold">
|
||||
{props.icon}
|
||||
<span>{props.title}</span>
|
||||
</div>
|
||||
<Button
|
||||
class="btn-circle btn-sm"
|
||||
onClick={() => {
|
||||
dialogRef?.close()
|
||||
}}
|
||||
icon={<IconX size={20} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="p-6 pt-3">{children(() => props.children)()}</div>
|
||||
|
||||
<Show when={props.action}>
|
||||
<div class={actionClass}>
|
||||
<div class="flex justify-end gap-2">{props.action}</div>
|
||||
</div>
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
)
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import { IconX } from '@tabler/icons-solidjs'
|
||||
import { For } from 'solid-js'
|
||||
import { Button, ConfigTitle } from '~/components'
|
||||
import { MODAL, PROXIES_ORDERING_TYPE, PROXIES_PREVIEW_TYPE } from '~/constants'
|
||||
import { IconGlobe } from '@tabler/icons-solidjs'
|
||||
import { Component, For } from 'solid-js'
|
||||
import { ConfigTitle, Modal } from '~/components'
|
||||
import { PROXIES_ORDERING_TYPE, PROXIES_PREVIEW_TYPE } from '~/constants'
|
||||
import { useI18n } from '~/i18n'
|
||||
import {
|
||||
autoCloseConns,
|
||||
@ -18,27 +18,18 @@ import {
|
||||
urlForLatencyTest,
|
||||
} from '~/signals'
|
||||
|
||||
export const ProxiesSettingsModal = () => {
|
||||
const modalID = MODAL.PROXIES_SETTINGS
|
||||
export const ProxiesSettingsModal: Component<{
|
||||
ref?: (el: HTMLDialogElement) => void
|
||||
}> = (props) => {
|
||||
const [t] = useI18n()
|
||||
|
||||
return (
|
||||
<dialog id={modalID} class="modal modal-bottom sm:modal-middle">
|
||||
<div class="modal-box flex flex-col gap-4">
|
||||
<div class="sticky top-0 z-50 flex items-center justify-end">
|
||||
<Button
|
||||
class="btn-circle btn-sm"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${modalID}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.close()
|
||||
}}
|
||||
icon={<IconX size={20} />}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
ref={(el) => props.ref?.(el)}
|
||||
icon={<IconGlobe size={24} />}
|
||||
title={t('proxiesSettings')}
|
||||
>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div>
|
||||
<ConfigTitle withDivider>{t('autoCloseConns')}</ConfigTitle>
|
||||
|
||||
@ -126,10 +117,6 @@ export const ProxiesSettingsModal = () => {
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="dialog" class="modal-backdrop">
|
||||
<button />
|
||||
</form>
|
||||
</dialog>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ export * from './Header'
|
||||
export * from './Latency'
|
||||
export * from './LogoText'
|
||||
export * from './LogsSettingsModal'
|
||||
export * from './Modal'
|
||||
export * from './ProxiesSettingsModal'
|
||||
export * from './ProxyCardGroups'
|
||||
export * from './ProxyNodeCard'
|
||||
|
@ -163,11 +163,3 @@ export enum LOG_LEVEL {
|
||||
|
||||
export const LOGS_TABLE_MAX_ROWS_LIST = [200, 300, 500, 800, 1000]
|
||||
export const DEFAULT_LOGS_TABLE_MAX_ROWS = LOGS_TABLE_MAX_ROWS_LIST[0]
|
||||
|
||||
export enum MODAL {
|
||||
PROXIES_SETTINGS = 'proxies-settings',
|
||||
RULES_SETTINGS = 'rules-settings',
|
||||
CONNECTIONS_SETTINGS = 'connections-settings',
|
||||
CONNECTIONS_TABLE_DETAILS = 'connections-table-details',
|
||||
LOGS_SETTINGS = 'logs-settings',
|
||||
}
|
||||
|
@ -2,9 +2,13 @@ export default {
|
||||
add: 'Add',
|
||||
overview: 'Overview',
|
||||
proxies: 'Proxies',
|
||||
proxiesSettings: 'Proxies Settings',
|
||||
rules: 'Rules',
|
||||
connections: 'Connections',
|
||||
connectionsSettings: 'Connections Settings',
|
||||
connectionsDetails: 'Connections Details',
|
||||
logs: 'Logs',
|
||||
logsSettings: 'Logs Settings',
|
||||
config: 'Config',
|
||||
upload: 'Upload',
|
||||
download: 'Download',
|
||||
|
@ -4,9 +4,13 @@ export default {
|
||||
add: '添加',
|
||||
overview: '概览',
|
||||
proxies: '代理',
|
||||
proxiesSettings: '代理设置',
|
||||
rules: '规则',
|
||||
connections: '连接',
|
||||
connectionsSettings: '连接设置',
|
||||
connectionsDetails: '连接详情',
|
||||
logs: '日志',
|
||||
logsSettings: '日志设置',
|
||||
config: '配置',
|
||||
upload: '上传',
|
||||
download: '下载',
|
||||
|
@ -43,7 +43,7 @@ import {
|
||||
ConnectionsSettingsModal,
|
||||
ConnectionsTableDetailsModal,
|
||||
} from '~/components'
|
||||
import { CONNECTIONS_TABLE_ACCESSOR_KEY, MODAL } from '~/constants'
|
||||
import { CONNECTIONS_TABLE_ACCESSOR_KEY } from '~/constants'
|
||||
import { useI18n } from '~/i18n'
|
||||
import {
|
||||
allConnections,
|
||||
@ -78,6 +78,9 @@ const fuzzyFilter: FilterFn<Connection> = (row, columnId, value, addMeta) => {
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let connectionsSettingsModalRef: HTMLDialogElement | undefined
|
||||
let connectionsDetailsModalRef: HTMLDialogElement | undefined
|
||||
|
||||
const [t] = useI18n()
|
||||
|
||||
const [activeTab, setActiveTab] = createSignal(ActiveTab.activeConnections)
|
||||
@ -103,11 +106,7 @@ export default () => {
|
||||
onClick={() => {
|
||||
setSelectedConnectionID(row.original.id)
|
||||
|
||||
const modal = document.querySelector(
|
||||
`#${MODAL.CONNECTIONS_TABLE_DETAILS}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.showModal()
|
||||
connectionsDetailsModalRef?.showModal()
|
||||
}}
|
||||
icon={<IconInfoSmall size="16" />}
|
||||
/>
|
||||
@ -397,13 +396,7 @@ export default () => {
|
||||
|
||||
<Button
|
||||
class="btn join-item btn-sm sm:btn-md"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${MODAL.CONNECTIONS_SETTINGS}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.showModal()
|
||||
}}
|
||||
onClick={() => connectionsSettingsModalRef?.showModal()}
|
||||
icon={<IconSettings />}
|
||||
/>
|
||||
</div>
|
||||
@ -531,6 +524,7 @@ export default () => {
|
||||
</div>
|
||||
|
||||
<ConnectionsSettingsModal
|
||||
ref={(el) => (connectionsSettingsModalRef = el)}
|
||||
order={connectionsTableColumnOrder()}
|
||||
visible={connectionsTableColumnVisibility()}
|
||||
onOrderChange={(data) => setConnectionsTableColumnOrder(data)}
|
||||
@ -540,6 +534,7 @@ export default () => {
|
||||
/>
|
||||
|
||||
<ConnectionsTableDetailsModal
|
||||
ref={(el) => (connectionsDetailsModalRef = el)}
|
||||
selectedConnectionID={selectedConnectionID()}
|
||||
/>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ import {
|
||||
import { For, Index, createEffect, createSignal } from 'solid-js'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { Button, LogsSettingsModal } from '~/components'
|
||||
import { LOG_LEVEL, MODAL } from '~/constants'
|
||||
import { LOG_LEVEL } from '~/constants'
|
||||
import { useI18n } from '~/i18n'
|
||||
import { logsTableSize, tableSizeClassName, useWsRequest } from '~/signals'
|
||||
import { logLevel, logMaxRows } from '~/signals/config'
|
||||
@ -40,7 +40,10 @@ const fuzzyFilter: FilterFn<LogWithSeq> = (row, columnId, value, addMeta) => {
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let logsSettingsModalRef: HTMLDialogElement | undefined
|
||||
|
||||
const [t] = useI18n()
|
||||
|
||||
let seq = 1
|
||||
const [logs, setLogs] = createSignal<LogWithSeq[]>([])
|
||||
|
||||
@ -139,13 +142,7 @@ export default () => {
|
||||
|
||||
<Button
|
||||
class="join-item btn-sm sm:btn-md"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${MODAL.LOGS_SETTINGS}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.showModal()
|
||||
}}
|
||||
onClick={() => logsSettingsModalRef?.showModal()}
|
||||
icon={<IconSettings />}
|
||||
/>
|
||||
</div>
|
||||
@ -221,7 +218,7 @@ export default () => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<LogsSettingsModal />
|
||||
<LogsSettingsModal ref={(el) => (logsSettingsModalRef = el)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
RenderInTwoColumns,
|
||||
SubscriptionInfo,
|
||||
} from '~/components'
|
||||
import { MODAL } from '~/constants'
|
||||
import {
|
||||
filterProxiesByAvailability,
|
||||
sortProxiesByOrderingType,
|
||||
@ -34,7 +33,10 @@ enum ActiveTab {
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let proxiesSettingsModalRef: HTMLDialogElement | undefined
|
||||
|
||||
const [t] = useI18n()
|
||||
|
||||
const {
|
||||
proxies,
|
||||
selectProxyInGroup,
|
||||
@ -133,13 +135,7 @@ export default () => {
|
||||
<div class="ml-auto">
|
||||
<Button
|
||||
class="btn-circle btn-sm sm:btn-md"
|
||||
onClick={() => {
|
||||
const modal = document.querySelector(
|
||||
`#${MODAL.PROXIES_SETTINGS}`,
|
||||
) as HTMLDialogElement | null
|
||||
|
||||
modal?.showModal()
|
||||
}}
|
||||
onClick={() => proxiesSettingsModalRef?.showModal()}
|
||||
icon={<IconSettings />}
|
||||
/>
|
||||
</div>
|
||||
@ -310,7 +306,7 @@ export default () => {
|
||||
</Show>
|
||||
</div>
|
||||
|
||||
<ProxiesSettingsModal />
|
||||
<ProxiesSettingsModal ref={(el) => (proxiesSettingsModalRef = el)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user