mirror of
https://github.com/hicccc77/WeFlow.git
synced 2026-03-25 07:16:51 +00:00
feat(export): add more defaults modal
This commit is contained in:
@@ -261,14 +261,14 @@
|
||||
}
|
||||
|
||||
.global-export-controls {
|
||||
flex: 0 1 820px;
|
||||
width: min(820px, 100%);
|
||||
flex: 0 1 980px;
|
||||
width: min(980px, 100%);
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.7fr) minmax(260px, 1fr);
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(240px, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: stretch;
|
||||
|
||||
@@ -359,6 +359,32 @@
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
.more-export-settings-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.more-export-settings-btn {
|
||||
min-height: 38px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
padding: 0 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--primary);
|
||||
color: var(--primary);
|
||||
background: color-mix(in srgb, var(--primary) 6%, var(--bg-secondary));
|
||||
}
|
||||
}
|
||||
|
||||
.layout-trigger {
|
||||
width: 100%;
|
||||
padding: 8px 10px;
|
||||
@@ -551,6 +577,62 @@
|
||||
}
|
||||
}
|
||||
|
||||
.export-defaults-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.42);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2300;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.export-defaults-modal {
|
||||
width: min(720px, 100%);
|
||||
max-height: min(80vh, 860px);
|
||||
overflow: hidden;
|
||||
border-radius: 14px;
|
||||
border: 1px solid var(--border-color);
|
||||
background: var(--bg-primary);
|
||||
box-shadow: 0 22px 46px rgba(0, 0, 0, 0.28);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.export-defaults-modal-header {
|
||||
padding: 14px 16px 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 12px;
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.export-defaults-modal-body {
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.export-defaults-modal-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.task-center-card-label {
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
@@ -3285,9 +3367,9 @@
|
||||
.global-export-controls {
|
||||
padding: 10px;
|
||||
gap: 8px;
|
||||
flex-basis: 760px;
|
||||
width: min(760px, 100%);
|
||||
grid-template-columns: minmax(0, 1.5fr) minmax(240px, 1fr);
|
||||
flex-basis: 920px;
|
||||
width: min(920px, 100%);
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(220px, 1fr) auto;
|
||||
}
|
||||
|
||||
.format-grid {
|
||||
@@ -3333,6 +3415,10 @@
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.export-defaults-modal {
|
||||
width: min(92vw, 720px);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
|
||||
@@ -41,6 +41,7 @@ import { useContactTypeCountsStore } from '../stores/contactTypeCountsStore'
|
||||
import { SnsPostItem } from '../components/Sns/SnsPostItem'
|
||||
import { ContactSnsTimelineDialog } from '../components/Sns/ContactSnsTimelineDialog'
|
||||
import { ExportDateRangeDialog } from '../components/Export/ExportDateRangeDialog'
|
||||
import { ExportDefaultsSettingsForm, type ExportDefaultsSettingsPatch } from '../components/Export/ExportDefaultsSettingsForm'
|
||||
import type { SnsPost } from '../types/sns'
|
||||
import {
|
||||
cloneExportDateRange,
|
||||
@@ -1282,8 +1283,14 @@ function ExportPage() {
|
||||
const [snsExportLivePhotos, setSnsExportLivePhotos] = useState(false)
|
||||
const [snsExportVideos, setSnsExportVideos] = useState(false)
|
||||
const [isTimeRangeDialogOpen, setIsTimeRangeDialogOpen] = useState(false)
|
||||
const [isExportDefaultsModalOpen, setIsExportDefaultsModalOpen] = useState(false)
|
||||
const [timeRangeSelection, setTimeRangeSelection] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection())
|
||||
const [exportDefaultFormat, setExportDefaultFormat] = useState<TextExportFormat>('excel')
|
||||
const [exportDefaultDateRangeSelection, setExportDefaultDateRangeSelection] = useState<ExportDateRangeSelection>(() => createDefaultExportDateRangeSelection())
|
||||
const [exportDefaultMedia, setExportDefaultMedia] = useState(false)
|
||||
const [exportDefaultVoiceAsText, setExportDefaultVoiceAsText] = useState(false)
|
||||
const [exportDefaultExcelCompactColumns, setExportDefaultExcelCompactColumns] = useState(true)
|
||||
const [exportDefaultConcurrency, setExportDefaultConcurrency] = useState(2)
|
||||
|
||||
const [options, setOptions] = useState<ExportOptions>({
|
||||
format: 'json',
|
||||
@@ -1785,8 +1792,9 @@ function ExportPage() {
|
||||
setIsBaseConfigLoading(true)
|
||||
let isReady = true
|
||||
try {
|
||||
const [savedPath, savedMedia, savedVoiceAsText, savedExcelCompactColumns, savedTxtColumns, savedConcurrency, savedSessionMap, savedContentMap, savedSessionRecordMap, savedSnsPostCount, savedWriteLayout, savedSessionNameWithTypePrefix, savedDefaultDateRange, exportCacheScope] = await Promise.all([
|
||||
const [savedPath, savedFormat, savedMedia, savedVoiceAsText, savedExcelCompactColumns, savedTxtColumns, savedConcurrency, savedSessionMap, savedContentMap, savedSessionRecordMap, savedSnsPostCount, savedWriteLayout, savedSessionNameWithTypePrefix, savedDefaultDateRange, exportCacheScope] = await Promise.all([
|
||||
configService.getExportPath(),
|
||||
configService.getExportDefaultFormat(),
|
||||
configService.getExportDefaultMedia(),
|
||||
configService.getExportDefaultVoiceAsText(),
|
||||
configService.getExportDefaultExcelCompactColumns(),
|
||||
@@ -1817,10 +1825,14 @@ function ExportPage() {
|
||||
setLastExportByContent(savedContentMap)
|
||||
setExportRecordsBySession(savedSessionRecordMap)
|
||||
setLastSnsExportPostCount(savedSnsPostCount)
|
||||
setExportDefaultFormat((savedFormat as TextExportFormat) || 'excel')
|
||||
setExportDefaultMedia(savedMedia ?? false)
|
||||
setExportDefaultVoiceAsText(savedVoiceAsText ?? false)
|
||||
setExportDefaultExcelCompactColumns(savedExcelCompactColumns ?? true)
|
||||
setExportDefaultConcurrency(savedConcurrency ?? 2)
|
||||
const resolvedDefaultDateRange = resolveExportDateRangeConfig(savedDefaultDateRange)
|
||||
setExportDefaultDateRangeSelection(resolvedDefaultDateRange)
|
||||
setTimeRangeSelection(resolvedDefaultDateRange)
|
||||
await configService.setExportDefaultFormat('json')
|
||||
|
||||
if (cachedSnsStats && Date.now() - cachedSnsStats.updatedAt <= EXPORT_SNS_STATS_CACHE_STALE_MS) {
|
||||
setSnsStats({
|
||||
@@ -1835,7 +1847,7 @@ function ExportPage() {
|
||||
const txtColumns = savedTxtColumns && savedTxtColumns.length > 0 ? savedTxtColumns : defaultTxtColumns
|
||||
setOptions(prev => ({
|
||||
...prev,
|
||||
format: 'json',
|
||||
format: ((savedFormat as TextExportFormat) || 'excel'),
|
||||
exportMedia: savedMedia ?? prev.exportMedia,
|
||||
exportVoiceAsText: savedVoiceAsText ?? prev.exportVoiceAsText,
|
||||
excelCompactColumns: savedExcelCompactColumns ?? prev.excelCompactColumns,
|
||||
@@ -3192,8 +3204,13 @@ function ExportPage() {
|
||||
|
||||
const next: ExportOptions = {
|
||||
...prev,
|
||||
format: exportDefaultFormat,
|
||||
useAllTime: exportDefaultDateRangeSelection.useAllTime,
|
||||
dateRange: nextDateRange
|
||||
dateRange: nextDateRange,
|
||||
exportMedia: exportDefaultMedia,
|
||||
exportVoiceAsText: exportDefaultVoiceAsText,
|
||||
excelCompactColumns: exportDefaultExcelCompactColumns,
|
||||
exportConcurrency: exportDefaultConcurrency
|
||||
}
|
||||
|
||||
if (payload.scope === 'sns') {
|
||||
@@ -3220,7 +3237,14 @@ function ExportPage() {
|
||||
|
||||
return next
|
||||
})
|
||||
}, [exportDefaultDateRangeSelection])
|
||||
}, [
|
||||
exportDefaultDateRangeSelection,
|
||||
exportDefaultExcelCompactColumns,
|
||||
exportDefaultFormat,
|
||||
exportDefaultMedia,
|
||||
exportDefaultVoiceAsText,
|
||||
exportDefaultConcurrency
|
||||
])
|
||||
|
||||
const closeExportDialog = useCallback(() => {
|
||||
setExportDialog(prev => ({ ...prev, open: false }))
|
||||
@@ -5147,6 +5171,27 @@ function ExportPage() {
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleExportDefaultsChanged = useCallback((patch: ExportDefaultsSettingsPatch) => {
|
||||
if (patch.format) {
|
||||
setExportDefaultFormat(patch.format as TextExportFormat)
|
||||
}
|
||||
if (patch.dateRange) {
|
||||
setExportDefaultDateRangeSelection(patch.dateRange)
|
||||
}
|
||||
if (typeof patch.media === 'boolean') {
|
||||
setExportDefaultMedia(patch.media)
|
||||
}
|
||||
if (typeof patch.voiceAsText === 'boolean') {
|
||||
setExportDefaultVoiceAsText(patch.voiceAsText)
|
||||
}
|
||||
if (typeof patch.excelCompactColumns === 'boolean') {
|
||||
setExportDefaultExcelCompactColumns(patch.excelCompactColumns)
|
||||
}
|
||||
if (typeof patch.concurrency === 'number') {
|
||||
setExportDefaultConcurrency(patch.concurrency)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="export-board-page">
|
||||
<div className="export-top-panel">
|
||||
@@ -5186,6 +5231,16 @@ function ExportPage() {
|
||||
await configService.setExportSessionNamePrefixEnabled(enabled)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="more-export-settings-control">
|
||||
<button
|
||||
className="more-export-settings-btn"
|
||||
type="button"
|
||||
onClick={() => setIsExportDefaultsModalOpen(true)}
|
||||
>
|
||||
更多导出设置
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
@@ -5212,6 +5267,48 @@ function ExportPage() {
|
||||
onTogglePerfTask={toggleTaskPerfDetail}
|
||||
/>
|
||||
|
||||
{isExportDefaultsModalOpen && (
|
||||
<div
|
||||
className="export-defaults-modal-overlay"
|
||||
onClick={() => setIsExportDefaultsModalOpen(false)}
|
||||
>
|
||||
<div
|
||||
className="export-defaults-modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="更多导出设置"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="export-defaults-modal-header">
|
||||
<div>
|
||||
<h3>更多导出设置</h3>
|
||||
<p>这里的配置与设置页中的导出设置保持一致,并会立即生效。</p>
|
||||
</div>
|
||||
<button
|
||||
className="close-icon-btn"
|
||||
type="button"
|
||||
onClick={() => setIsExportDefaultsModalOpen(false)}
|
||||
aria-label="关闭更多导出设置"
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
<div className="export-defaults-modal-body">
|
||||
<ExportDefaultsSettingsForm onDefaultsChanged={handleExportDefaultsChanged} />
|
||||
</div>
|
||||
<div className="export-defaults-modal-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="secondary-btn"
|
||||
onClick={() => setIsExportDefaultsModalOpen(false)}
|
||||
>
|
||||
关闭
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="export-section-title-row">
|
||||
<h3 className="export-section-title">按类型批量导出</h3>
|
||||
<SectionInfoTooltip
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from 'lucide-react'
|
||||
import { Avatar } from '../components/Avatar'
|
||||
import { ExportDateRangeDialog } from '../components/Export/ExportDateRangeDialog'
|
||||
import { ExportDefaultsSettingsForm } from '../components/Export/ExportDefaultsSettingsForm'
|
||||
import {
|
||||
createDefaultExportDateRangeSelection,
|
||||
getExportDateRangeLabel,
|
||||
@@ -1588,212 +1589,9 @@ function SettingsPage() {
|
||||
}
|
||||
|
||||
const renderExportTab = () => {
|
||||
const exportExcelColumnsValue = exportDefaultExcelCompactColumns ? 'compact' : 'full'
|
||||
const exportFormatLabel = getOptionLabel(exportFormatOptions, exportDefaultFormat)
|
||||
const exportDateRangeLabel = getExportDateRangeLabel(exportDefaultDateRange)
|
||||
const exportExcelColumnsLabel = getOptionLabel(exportExcelColumnOptions, exportExcelColumnsValue)
|
||||
const exportConcurrencyLabel = String(exportDefaultConcurrency)
|
||||
|
||||
return (
|
||||
<div className="tab-content">
|
||||
<div className="form-group">
|
||||
<label>默认导出格式</label>
|
||||
<span className="form-hint">导出页面默认选中的格式</span>
|
||||
<div className="select-field" ref={exportFormatDropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={`select-trigger ${showExportFormatSelect ? 'open' : ''}`}
|
||||
onClick={() => {
|
||||
setShowExportFormatSelect(!showExportFormatSelect)
|
||||
setIsExportDateRangeDialogOpen(false)
|
||||
setShowExportExcelColumnsSelect(false)
|
||||
setShowExportConcurrencySelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="select-value">{exportFormatLabel}</span>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
{showExportFormatSelect && (
|
||||
<div className="select-dropdown">
|
||||
{exportFormatOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
className={`select-option ${exportDefaultFormat === option.value ? 'active' : ''}`}
|
||||
onClick={async () => {
|
||||
setExportDefaultFormat(option.value)
|
||||
await configService.setExportDefaultFormat(option.value)
|
||||
showMessage('已更新导出格式默认值', true)
|
||||
setShowExportFormatSelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="option-label">{option.label}</span>
|
||||
{option.desc && <span className="option-desc">{option.desc}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>默认导出时间范围</label>
|
||||
<span className="form-hint">控制导出页面的默认时间选择</span>
|
||||
<div className="settings-time-range-field">
|
||||
<button
|
||||
type="button"
|
||||
className={`settings-time-range-trigger ${isExportDateRangeDialogOpen ? 'open' : ''}`}
|
||||
onClick={() => {
|
||||
setShowExportFormatSelect(false)
|
||||
setShowExportExcelColumnsSelect(false)
|
||||
setShowExportConcurrencySelect(false)
|
||||
setIsExportDateRangeDialogOpen(true)
|
||||
}}
|
||||
>
|
||||
<span className="settings-time-range-value">{exportDateRangeLabel}</span>
|
||||
<span className="settings-time-range-arrow">></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ExportDateRangeDialog
|
||||
open={isExportDateRangeDialogOpen}
|
||||
value={exportDefaultDateRange}
|
||||
onClose={() => setIsExportDateRangeDialogOpen(false)}
|
||||
onConfirm={async (nextSelection) => {
|
||||
setExportDefaultDateRange(nextSelection)
|
||||
await configService.setExportDefaultDateRange(serializeExportDateRangeConfig(nextSelection))
|
||||
showMessage('已更新默认导出时间范围', true)
|
||||
setIsExportDateRangeDialogOpen(false)
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="form-group">
|
||||
<label>默认导出媒体文件</label>
|
||||
<span className="form-hint">控制图片/语音/表情的默认导出开关</span>
|
||||
<div className="log-toggle-line">
|
||||
<span className="log-status">{exportDefaultMedia ? '已开启' : '已关闭'}</span>
|
||||
<label className="switch" htmlFor="export-default-media">
|
||||
<input
|
||||
id="export-default-media"
|
||||
className="switch-input"
|
||||
type="checkbox"
|
||||
checked={exportDefaultMedia}
|
||||
onChange={async (e) => {
|
||||
const enabled = e.target.checked
|
||||
setExportDefaultMedia(enabled)
|
||||
await configService.setExportDefaultMedia(enabled)
|
||||
showMessage(enabled ? '已开启默认媒体导出' : '已关闭默认媒体导出', true)
|
||||
}}
|
||||
/>
|
||||
<span className="switch-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>默认语音转文字</label>
|
||||
<span className="form-hint">导出时默认将语音转写为文字</span>
|
||||
<div className="log-toggle-line">
|
||||
<span className="log-status">{exportDefaultVoiceAsText ? '已开启' : '已关闭'}</span>
|
||||
<label className="switch" htmlFor="export-default-voice-as-text">
|
||||
<input
|
||||
id="export-default-voice-as-text"
|
||||
className="switch-input"
|
||||
type="checkbox"
|
||||
checked={exportDefaultVoiceAsText}
|
||||
onChange={async (e) => {
|
||||
const enabled = e.target.checked
|
||||
setExportDefaultVoiceAsText(enabled)
|
||||
await configService.setExportDefaultVoiceAsText(enabled)
|
||||
showMessage(enabled ? '已开启默认语音转文字' : '已关闭默认语音转文字', true)
|
||||
}}
|
||||
/>
|
||||
<span className="switch-slider" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>Excel 列显示</label>
|
||||
<span className="form-hint">控制 Excel 导出的列字段</span>
|
||||
<div className="select-field" ref={exportExcelColumnsDropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={`select-trigger ${showExportExcelColumnsSelect ? 'open' : ''}`}
|
||||
onClick={() => {
|
||||
setShowExportExcelColumnsSelect(!showExportExcelColumnsSelect)
|
||||
setShowExportFormatSelect(false)
|
||||
setIsExportDateRangeDialogOpen(false)
|
||||
setShowExportConcurrencySelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="select-value">{exportExcelColumnsLabel}</span>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
{showExportExcelColumnsSelect && (
|
||||
<div className="select-dropdown">
|
||||
{exportExcelColumnOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
className={`select-option ${exportExcelColumnsValue === option.value ? 'active' : ''}`}
|
||||
onClick={async () => {
|
||||
const compact = option.value === 'compact'
|
||||
setExportDefaultExcelCompactColumns(compact)
|
||||
await configService.setExportDefaultExcelCompactColumns(compact)
|
||||
showMessage(compact ? '已启用精简列' : '已启用完整列', true)
|
||||
setShowExportExcelColumnsSelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="option-label">{option.label}</span>
|
||||
{option.desc && <span className="option-desc">{option.desc}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>导出并发数</label>
|
||||
<span className="form-hint">导出多个会话时的最大并发(1~6)</span>
|
||||
<div className="select-field" ref={exportConcurrencyDropdownRef}>
|
||||
<button
|
||||
type="button"
|
||||
className={`select-trigger ${showExportConcurrencySelect ? 'open' : ''}`}
|
||||
onClick={() => {
|
||||
setShowExportConcurrencySelect(!showExportConcurrencySelect)
|
||||
setShowExportFormatSelect(false)
|
||||
setIsExportDateRangeDialogOpen(false)
|
||||
setShowExportExcelColumnsSelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="select-value">{exportConcurrencyLabel}</span>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
{showExportConcurrencySelect && (
|
||||
<div className="select-dropdown">
|
||||
{exportConcurrencyOptions.map((option) => (
|
||||
<button
|
||||
key={option.value}
|
||||
type="button"
|
||||
className={`select-option ${exportDefaultConcurrency === option.value ? 'active' : ''}`}
|
||||
onClick={async () => {
|
||||
setExportDefaultConcurrency(option.value)
|
||||
await configService.setExportDefaultConcurrency(option.value)
|
||||
showMessage(`已将导出并发数设为 ${option.value}`, true)
|
||||
setShowExportConcurrencySelect(false)
|
||||
}}
|
||||
>
|
||||
<span className="option-label">{option.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ExportDefaultsSettingsForm onNotify={showMessage} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user