fix(ui): theme-derived sftp/monitor surfaces, transfer cancel button, dialog cleanup; drop dead autoWrap; gentler bg-image default
This commit is contained in:
1 parent
fb65981bff
commit
b7938de464
11 files changed
+108
-56
No files matched your search
@@ -1,11 +1,13 @@
|
||||
/* M3 server sysinfo monitor panel (SSH sessions only). */
|
||||
|
||||
/* Panel surface derived from the theme (a fixed dark value turned every
|
||||
white-based child below unreadable on a light theme). */
|
||||
.mm-root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
background: #1a1a1a;
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 6%, var(--chrome-bg, #181818));
|
||||
color: var(--chrome-fg);
|
||||
font-size: 12px;
|
||||
overflow-y: auto;
|
||||
@@ -32,14 +34,14 @@
|
||||
gap: 1px;
|
||||
line-height: 1.3;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #262626;
|
||||
border-bottom: 1px solid var(--chrome-border, #2d2d2d);
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.mm-host {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.92);
|
||||
color: var(--chrome-fg, #cccccc);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -55,9 +57,9 @@
|
||||
|
||||
/* ---------- blocks ---------- */
|
||||
.mm-block {
|
||||
border: 1px solid #2a2a2a;
|
||||
border: 1px solid var(--chrome-border, #2d2d2d);
|
||||
border-radius: 8px;
|
||||
background: rgba(255, 255, 255, 0.018);
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 4%, transparent);
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -108,7 +110,7 @@
|
||||
height: 5px;
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
background: #2c2c2c;
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 12%, transparent);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@@ -121,7 +123,7 @@
|
||||
/* ---------- memory ---------- */
|
||||
.mm-mem-size {
|
||||
font-size: 12px;
|
||||
color: rgba(255, 255, 255, 0.78);
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 78%, transparent);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@
|
||||
}
|
||||
|
||||
.mm-mount {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 75%, transparent);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -214,7 +216,7 @@
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 82%, transparent);
|
||||
}
|
||||
|
||||
.mm-net-unit {
|
||||
@@ -243,7 +245,7 @@
|
||||
}
|
||||
|
||||
.mm-uptime {
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 85%, transparent);
|
||||
}
|
||||
|
||||
/* ---------- empty / collecting state ---------- */
|
||||
@@ -264,7 +266,7 @@
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.4);
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 55%, transparent);
|
||||
animation: mm-pulse 1.2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Button, Modal, Popconfirm, Tabs } from 'antd'
|
||||
import type { TabsProps } from 'antd'
|
||||
import { t } from '@shared/i18n'
|
||||
@@ -33,6 +33,16 @@ export function SettingsDialog({ open, onClose }: SettingsDialogProps): React.JS
|
||||
// is open, a main-window resize re-derives that default in real time.
|
||||
const [size, setSize] = useState<{ w: number; h: number } | null>(null)
|
||||
const [winSize, setWinSize] = useState({ w: window.innerWidth, h: window.innerHeight })
|
||||
/** Tears down the drag in progress; null while no drag is active. */
|
||||
const resizeCleanupRef = useRef<(() => void) | null>(null)
|
||||
|
||||
const endResize = useCallback((): void => {
|
||||
const cleanup = resizeCleanupRef.current
|
||||
if (!cleanup) return
|
||||
resizeCleanupRef.current = null
|
||||
cleanup()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (open) setSize(null)
|
||||
}, [open])
|
||||
@@ -42,6 +52,13 @@ export function SettingsDialog({ open, onClose }: SettingsDialogProps): React.JS
|
||||
window.addEventListener('resize', onResize)
|
||||
return () => window.removeEventListener('resize', onResize)
|
||||
}, [open])
|
||||
// A drag whose mouseup is never delivered (released outside the window) must
|
||||
// not outlive the dialog: closing or unmounting drops its listeners and the
|
||||
// body styles it installed.
|
||||
useEffect(() => {
|
||||
if (!open) endResize()
|
||||
}, [open, endResize])
|
||||
useEffect(() => endResize, [endResize])
|
||||
|
||||
const clampToWindow = (w: number, h: number): { w: number; h: number } => ({
|
||||
w: Math.max(720, Math.min(w, winSize.w - 96)),
|
||||
@@ -56,24 +73,33 @@ export function SettingsDialog({ open, onClose }: SettingsDialogProps): React.JS
|
||||
const startResize = (e: React.MouseEvent<HTMLSpanElement>): void => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
if (resizeCleanupRef.current) return
|
||||
const startX = e.clientX
|
||||
const startY = e.clientY
|
||||
const base = dialogSize
|
||||
const prevCursor = document.body.style.cursor
|
||||
const prevUserSelect = document.body.style.userSelect
|
||||
const onMove = (ev: MouseEvent): void => {
|
||||
const w = Math.max(720, Math.min(window.innerWidth - 96, base.w + (ev.clientX - startX)))
|
||||
const h = Math.max(440, Math.min(window.innerHeight - 140, base.h + (ev.clientY - startY)))
|
||||
setSize({ w, h })
|
||||
}
|
||||
const onUp = (): void => {
|
||||
// One teardown for every way the drag can end: mouseup, the window losing
|
||||
// focus (the release lands on another window and never reaches us), the
|
||||
// dialog closing, or unmount.
|
||||
const cleanup = (): void => {
|
||||
window.removeEventListener('mousemove', onMove)
|
||||
window.removeEventListener('mouseup', onUp)
|
||||
document.body.style.cursor = ''
|
||||
document.body.style.userSelect = ''
|
||||
window.removeEventListener('mouseup', cleanup)
|
||||
window.removeEventListener('blur', cleanup)
|
||||
document.body.style.cursor = prevCursor
|
||||
document.body.style.userSelect = prevUserSelect
|
||||
}
|
||||
resizeCleanupRef.current = cleanup
|
||||
document.body.style.cursor = 'nwse-resize'
|
||||
document.body.style.userSelect = 'none'
|
||||
window.addEventListener('mousemove', onMove)
|
||||
window.addEventListener('mouseup', onUp)
|
||||
window.addEventListener('mouseup', cleanup)
|
||||
window.addEventListener('blur', cleanup)
|
||||
}
|
||||
|
||||
const tabItems: TabsProps['items'] = [
|
||||
|
||||
@@ -253,16 +253,6 @@ export function RenderSettingsTab(): React.JSX.Element {
|
||||
<div className="settings-block-hint settings-render-hint">
|
||||
{rendererModes.find((o) => o.value === settings.terminal.rendererMode)?.hint}
|
||||
</div>
|
||||
<SettingRow
|
||||
label={t('settings.render.autoWrap')}
|
||||
desc={t('settings.render.autoWrapDesc')}
|
||||
control={
|
||||
<Switch
|
||||
checked={settings.terminal.autoWrap}
|
||||
onChange={(checked) => void updateTerminal({ autoWrap: checked })}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label={t('settings.render.copyOnSelect')}
|
||||
desc={t('settings.render.copyOnSelectDesc')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { Button, ColorPicker, Popconfirm, Slider } from 'antd'
|
||||
import { t } from '@shared/i18n'
|
||||
import type { TerminalSettings } from '@shared/settings'
|
||||
@@ -31,6 +31,7 @@ export function ThemeSettingsTab({
|
||||
const updateTerminal = useSettingsStore((s) => s.updateTerminal)
|
||||
const setCustomThemes = useSettingsStore((s) => s.setCustomThemes)
|
||||
const activeTheme = useResolvedTheme()
|
||||
const [opacityDraft, setOpacityDraft] = useState<number | null>(null)
|
||||
|
||||
const themeCards = useMemo(
|
||||
() => [...BUILTIN_THEMES, ...settings.customThemes],
|
||||
@@ -80,8 +81,12 @@ export function ThemeSettingsTab({
|
||||
min={10}
|
||||
max={100}
|
||||
step={5}
|
||||
value={settings.terminal.backgroundImageOpacity}
|
||||
onChange={(v) => void updateTerminal({ backgroundImageOpacity: v })}
|
||||
value={opacityDraft ?? settings.terminal.backgroundImageOpacity}
|
||||
onChange={(v) => setOpacityDraft(v)}
|
||||
onChangeComplete={(v) => {
|
||||
setOpacityDraft(null)
|
||||
void updateTerminal({ backgroundImageOpacity: v })
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -123,7 +123,21 @@ export function TransferPanel(): React.JSX.Element | null {
|
||||
? t('panels.transfer.zmodemUploading')
|
||||
: t('panels.transfer.zmodemDownloading')}
|
||||
</span>
|
||||
{(tr.state === 'error' || tr.state === 'done') && (
|
||||
{/* Only the SFTP engine's own transfers can be cancelled; zmodem
|
||||
runs in a separate engine that ignores cancelTransfer. */}
|
||||
{tr.state === 'running' && (tr.kind === 'upload' || tr.kind === 'download') && (
|
||||
<button
|
||||
type="button"
|
||||
className="sftp-transfer-cancel"
|
||||
onClick={() => window.api.cancelTransfer(id)}
|
||||
>
|
||||
{t('common.cancel')}
|
||||
</button>
|
||||
)}
|
||||
{/* Cancelled rows dismiss themselves after 3s, but one that also
|
||||
carries an error (a cancel racing a failed file) skips that
|
||||
path, so every terminal state keeps a manual close. */}
|
||||
{(tr.state === 'error' || tr.state === 'done' || tr.state === 'cancelled') && (
|
||||
<button
|
||||
type="button"
|
||||
className="sftp-transfer-close"
|
||||
|
||||
@@ -135,6 +135,8 @@
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Preview strip inside the permission dialog: the same derived surface as the
|
||||
transfer card, so the mode string keeps its contrast on a light theme. */
|
||||
.sftp-perm-mode {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -144,13 +146,13 @@
|
||||
font-size: 14px;
|
||||
color: var(--chrome-fg);
|
||||
padding: 6px 10px;
|
||||
background: #141414;
|
||||
border: 1px solid #2d2d2d;
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 8%, var(--chrome-bg, #181818));
|
||||
border: 1px solid color-mix(in srgb, var(--chrome-fg, #cccccc) 20%, transparent);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.sftp-perm-sym {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
color: var(--chrome-fg, #cccccc);
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
@@ -179,7 +181,7 @@
|
||||
}
|
||||
|
||||
.sftp-perm-grid-label {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 70%, transparent);
|
||||
}
|
||||
|
||||
.sftp-perm-grid-head span:first-child,
|
||||
@@ -237,8 +239,12 @@
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
/* The card floats over the terminal canvas, so its surface is derived from the
|
||||
theme rather than pinned to a dark value: mixing the theme foreground into the
|
||||
theme background lifts it above the canvas on a dark theme and drops it below
|
||||
a light one, and var(--chrome-fg) text stays readable either way. */
|
||||
.sftp-transfer {
|
||||
background: #1f1f1f;
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 8%, var(--chrome-bg, #181818));
|
||||
border: 1px solid var(--chrome-border);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
@@ -253,6 +259,7 @@
|
||||
}
|
||||
|
||||
.sftp-transfer-title {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: var(--chrome-fg);
|
||||
font-weight: 600;
|
||||
@@ -267,24 +274,42 @@
|
||||
}
|
||||
|
||||
.sftp-transfer-cancelled .sftp-transfer-title {
|
||||
color: #8a8a8a;
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 70%, transparent);
|
||||
}
|
||||
|
||||
.sftp-transfer-cancel {
|
||||
flex: 0 0 auto;
|
||||
border: 1px solid color-mix(in srgb, var(--chrome-fg, #cccccc) 25%, transparent);
|
||||
background: none;
|
||||
border-radius: 4px;
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 75%, transparent);
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
padding: 2px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sftp-transfer-cancel:hover {
|
||||
color: var(--chrome-fg, #cccccc);
|
||||
border-color: color-mix(in srgb, var(--chrome-fg, #cccccc) 45%, transparent);
|
||||
}
|
||||
|
||||
.sftp-transfer-close {
|
||||
flex: 0 0 auto;
|
||||
border: none;
|
||||
background: none;
|
||||
color: #8a8a8a;
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 65%, transparent);
|
||||
cursor: pointer;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
.sftp-transfer-close:hover {
|
||||
color: #ffffff;
|
||||
color: var(--chrome-fg, #cccccc);
|
||||
}
|
||||
|
||||
.sftp-transfer-file {
|
||||
font-size: 11px;
|
||||
color: #9a9a9a;
|
||||
color: color-mix(in srgb, var(--chrome-fg, #cccccc) 75%, transparent);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -294,7 +319,7 @@
|
||||
.sftp-transfer-bar {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
background: color-mix(in srgb, var(--chrome-fg, #cccccc) 14%, transparent);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -69,8 +69,6 @@ const settings: Record<string, string> = {
|
||||
'Pure CPU rendering with the widest compatibility; best without a GPU or over remote desktop',
|
||||
'settings.render.modeLabel': 'Renderer',
|
||||
'settings.render.modeHint': 'How terminal cells are accelerated; applies to every terminal',
|
||||
'settings.render.autoWrap': 'Auto wrap',
|
||||
'settings.render.autoWrapDesc': 'Wrap lines that exceed the width',
|
||||
'settings.render.copyOnSelect': 'Copy on select',
|
||||
'settings.render.copyOnSelectDesc': 'Copy text automatically when you select it with the mouse',
|
||||
'settings.render.pasteRiskConfirm': 'Confirm risky paste',
|
||||
@@ -286,7 +284,7 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.accent': 'Tab accent color',
|
||||
'settings.theme.accentDesc': 'Accent color for the active tab border, the sidebar indicator, and SSH badges',
|
||||
'settings.theme.backgroundImage': 'Background image',
|
||||
'settings.theme.backgroundImageDesc': 'Show an image behind the terminal; remove it to go back to a solid background',
|
||||
'settings.theme.backgroundImageDesc': 'Show an image behind the terminal; remove it to go back to a solid background. Lower the opacity if text gets hard to read (20–40% works well for bright images)',
|
||||
'settings.theme.chooseImage': 'Choose image',
|
||||
'settings.theme.clearImage': 'Remove',
|
||||
'settings.theme.imageOpacity': 'Image opacity',
|
||||
|
||||
@@ -67,8 +67,6 @@ const settings: Record<string, string> = {
|
||||
'settings.rendererMode.domHint': 'CPU のみで描画し、互換性を最優先します。GPU がない環境やリモートデスクトップに適します',
|
||||
'settings.render.modeLabel': '描画モード',
|
||||
'settings.render.modeHint': 'ターミナルセルの描画を高速化する方式。変更はすべてのターミナルに適用されます',
|
||||
'settings.render.autoWrap': '自動折り返し',
|
||||
'settings.render.autoWrapDesc': '幅を超えた行を自動で折り返します',
|
||||
'settings.render.copyOnSelect': '選択時にコピー',
|
||||
'settings.render.copyOnSelectDesc': 'マウスで文字を選択すると自動でコピーします',
|
||||
'settings.render.pasteRiskConfirm': '貼り付け前の確認',
|
||||
@@ -281,7 +279,7 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.accent': 'タブのアクセントカラー',
|
||||
'settings.theme.accentDesc': 'アクティブなタブの枠線、サイドバーのインジケーター、SSH バッジのアクセントカラー',
|
||||
'settings.theme.backgroundImage': '背景画像',
|
||||
'settings.theme.backgroundImageDesc': 'ターミナルの背後に画像を表示します。削除すると無地の背景に戻ります',
|
||||
'settings.theme.backgroundImageDesc': 'ターミナルの背後に画像を表示します。削除すると無地の背景に戻ります。文字が読みにくい場合は不透明度を下げてください(明るい画像は 20–40% が目安です)',
|
||||
'settings.theme.chooseImage': '画像を選択',
|
||||
'settings.theme.clearImage': '削除',
|
||||
'settings.theme.imageOpacity': '画像の不透明度',
|
||||
|
||||
@@ -67,8 +67,6 @@ const settings: Record<string, string> = {
|
||||
'settings.rendererMode.domHint': '纯 CPU 渲染,兼容性最好,适合无 GPU 或远程桌面环境',
|
||||
'settings.render.modeLabel': '渲染模式',
|
||||
'settings.render.modeHint': '终端单元渲染加速方式,改动将应用到所有终端',
|
||||
'settings.render.autoWrap': '自动换行',
|
||||
'settings.render.autoWrapDesc': '超出宽度时自动折行',
|
||||
'settings.render.copyOnSelect': '选中即复制',
|
||||
'settings.render.copyOnSelectDesc': '鼠标选中文字时自动复制',
|
||||
'settings.render.pasteRiskConfirm': '粘贴风险确认',
|
||||
@@ -270,7 +268,7 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.accent': '标签强调色',
|
||||
'settings.theme.accentDesc': '活动标签边框、侧栏指示条与 SSH 徽章的强调颜色',
|
||||
'settings.theme.backgroundImage': '背景图片',
|
||||
'settings.theme.backgroundImageDesc': '为终端设置一张背景图片;移除后恢复纯色背景',
|
||||
'settings.theme.backgroundImageDesc': '为终端设置一张背景图片;移除后恢复纯色背景;文字看不清时调低透明度(亮图建议 20–40%)',
|
||||
'settings.theme.chooseImage': '选择图片',
|
||||
'settings.theme.clearImage': '移除图片',
|
||||
'settings.theme.imageOpacity': '图片不透明度',
|
||||
|
||||
@@ -67,8 +67,6 @@ const settings: Record<string, string> = {
|
||||
'settings.rendererMode.domHint': '純 CPU 渲染,相容性最佳,適合沒有 GPU 或遠端桌面的環境',
|
||||
'settings.render.modeLabel': '渲染模式',
|
||||
'settings.render.modeHint': '終端儲存格的渲染加速方式,變更將套用到所有終端',
|
||||
'settings.render.autoWrap': '自動換行',
|
||||
'settings.render.autoWrapDesc': '超出寬度時自動折行',
|
||||
'settings.render.copyOnSelect': '選取即複製',
|
||||
'settings.render.copyOnSelectDesc': '以滑鼠選取文字時自動複製',
|
||||
'settings.render.pasteRiskConfirm': '貼上風險確認',
|
||||
@@ -270,7 +268,7 @@ const settings: Record<string, string> = {
|
||||
'settings.theme.accent': '標籤強調色',
|
||||
'settings.theme.accentDesc': '作用中標籤的邊框、側邊欄指示條與 SSH 徽章的強調顏色',
|
||||
'settings.theme.backgroundImage': '背景圖片',
|
||||
'settings.theme.backgroundImageDesc': '為終端設定一張背景圖片;移除後恢復純色背景',
|
||||
'settings.theme.backgroundImageDesc': '為終端設定一張背景圖片;移除後恢復純色背景;文字看不清時調低透明度(亮圖建議 20–40%)',
|
||||
'settings.theme.chooseImage': '選擇圖片',
|
||||
'settings.theme.clearImage': '移除圖片',
|
||||
'settings.theme.imageOpacity': '圖片不透明度',
|
||||
|
||||
@@ -63,7 +63,6 @@ export interface TerminalSettings {
|
||||
cursorInactiveStyle: 'outline' | 'block' | 'bar' | 'underline' | 'none'
|
||||
themeId: string
|
||||
rendererMode: 'auto' | 'webgl' | 'dom'
|
||||
autoWrap: boolean
|
||||
copyOnSelect: boolean
|
||||
/** confirm before pasting multi-line / suspicious text */
|
||||
pasteRiskConfirm: boolean
|
||||
@@ -445,7 +444,6 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
cursorInactiveStyle: 'outline',
|
||||
themeId: 'material-dark',
|
||||
rendererMode: 'auto',
|
||||
autoWrap: true,
|
||||
copyOnSelect: false,
|
||||
pasteRiskConfirm: true,
|
||||
suggestEnabled: false,
|
||||
@@ -461,7 +459,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
highlightThemeColors: false,
|
||||
highlightPerHost: false,
|
||||
backgroundImage: '',
|
||||
backgroundImageOpacity: 60
|
||||
backgroundImageOpacity: 30
|
||||
},
|
||||
customThemes: [],
|
||||
highlightRules: DEFAULT_HIGHLIGHT_RULES,
|
||||
|
||||
Reference in new issue
Block a user