From ae2c56805c6d8f849d692f430e8ad1fda9802973 Mon Sep 17 00:00:00 2001 From: lencx Date: Sat, 14 Jan 2023 23:31:12 +0800 Subject: [PATCH] chore: export --- src-tauri/src/app/cmd.rs | 18 +++- src-tauri/src/scripts/export.js | 25 +++-- src/hooks/{useColumns.ts => useColumns.tsx} | 43 ++++++++- src/hooks/useJson.ts | 9 +- src/hooks/useTable.tsx | 33 +++++-- src/main.scss | 19 +++- src/view/download/config.tsx | 44 +++++++-- src/view/download/index.scss | 12 --- src/view/download/index.tsx | 100 +++++++++++++++++--- src/view/model/SyncPrompts/config.tsx | 5 +- src/view/model/SyncPrompts/index.tsx | 5 +- src/view/model/SyncRecord/config.tsx | 5 +- src/view/model/SyncRecord/index.tsx | 5 +- src/view/model/UserCustom/config.tsx | 5 +- src/view/model/UserCustom/index.tsx | 5 +- 15 files changed, 264 insertions(+), 69 deletions(-) rename src/hooks/{useColumns.ts => useColumns.tsx} (52%) delete mode 100644 src/view/download/index.scss diff --git a/src-tauri/src/app/cmd.rs b/src-tauri/src/app/cmd.rs index c27fd56..6369ac1 100644 --- a/src-tauri/src/app/cmd.rs +++ b/src-tauri/src/app/cmd.rs @@ -207,8 +207,8 @@ pub fn download_list(pathname: &str, filename: Option, id: Option, id: Option { + file_data.name = v.clone(); + v + } + _ => "".to_string(), + }; + } + + if filename.is_some() && id.is_some() { if let Some(ref v) = id { if fid == v { if let Some(ref v2) = filename { diff --git a/src-tauri/src/scripts/export.js b/src-tauri/src/scripts/export.js index 50c1078..d5af93b 100644 --- a/src-tauri/src/scripts/export.js +++ b/src-tauri/src/scripts/export.js @@ -52,11 +52,13 @@ function shouldAddButtons(actionsArea) { return !/download-/.test(button.id); }); - if (/Stop generating/ig.test(buttons[0].innerText)) { + const stopBtn = buttons?.[0]?.innerText; + + if (/Stop generating/ig.test(stopBtn)) { return false; } - if (buttons.length === 2 && (/Regenerate response/ig.test(buttons[0].innerText) || buttons[1].innerText === '')) { + if (buttons.length === 2 && (/Regenerate response/ig.test(stopBtn) || buttons[1].innerText === '')) { return true; } @@ -132,7 +134,7 @@ function addActionsButtons(actionsArea, TryAgainButton) { async function exportMarkdown() { const data = ExportMD.turndown(document.querySelector("main div>div>div").innerHTML); - await invoke('save_file', { name: `notes/${Date.now().toString(36)}.md`, content: data }); + await invoke('save_file', { name: `notes/${uid().toString(36)}.md`, content: data }); } function downloadThread({ as = Format.PNG } = {}) { @@ -164,8 +166,9 @@ async function handleImg(imgData) { for (let i = 0; i < binaryData.length; i++) { data.push(binaryData.charCodeAt(i)); } - await invoke('download', { name: `download/img/${Date.now().toString(36)}.png`, blob: data }); - await invoke('download_list'); + const { pathname, id, filename } = getName(); + await invoke('download', { name: `download/img/${id}.png`, blob: data }); + await invoke('download_list', { pathname, filename, id }); } async function handlePdf(imgData, canvas, pixelRatio) { @@ -178,10 +181,16 @@ async function handlePdf(imgData, canvas, pixelRatio) { var pdfWidth = pdf.internal.pageSize.getWidth(); var pdfHeight = pdf.internal.pageSize.getHeight(); pdf.addImage(imgData, "PNG", 0, 0, pdfWidth, pdfHeight, '', 'FAST'); - + const { pathname, id, filename } = getName(); const data = pdf.__private__.getArrayBuffer(pdf.__private__.buildDocument()); - await invoke('download', { name: `download/pdf/${Date.now().toString(36)}.pdf`, blob: Array.from(new Uint8Array(data)) }); - await invoke('download_list'); + await invoke('download', { name: `download/pdf/${id}.pdf`, blob: Array.from(new Uint8Array(data)) }); + await invoke('download_list', { pathname, filename, id }); +} + +function getName() { + const id = uid().toString(36); + const name = document.querySelector('nav .overflow-y-auto a.hover\\:bg-gray-800')?.innerText?.trim() || ''; + return { filename: name ? name : id, id, pathname: 'chat.download.json' }; } class Elements { diff --git a/src/hooks/useColumns.ts b/src/hooks/useColumns.tsx similarity index 52% rename from src/hooks/useColumns.ts rename to src/hooks/useColumns.tsx index ed6c19a..acc0dfa 100644 --- a/src/hooks/useColumns.ts +++ b/src/hooks/useColumns.tsx @@ -1,4 +1,7 @@ -import { useState, useCallback } from 'react'; +import { FC, useState, useCallback } from 'react'; +import { Input } from 'antd'; + +import { DISABLE_AUTO_COMPLETE } from '@/utils'; export default function useColumns(columns: any[] = []) { const [opType, setOpType] = useState(''); @@ -41,4 +44,40 @@ export default function useColumns(columns: any[] = []) { setExtra, opExtra, }; -} \ No newline at end of file +} + +interface EditRowProps { + rowKey: string; + row: Record; + actions: any; +} +export const EditRow: FC = ({ rowKey, row, actions }) => { + const [isEdit, setEdit] = useState(false); + const [val, setVal] = useState(row[rowKey]); + const handleEdit = () => { + setEdit(true); + }; + const handleChange = (e: React.ChangeEvent) => { + setVal(e.target.value) + }; + + const handleSave = () => { + setEdit(false); + row[rowKey] = val; + actions?.setRecord(row, 'rowedit') + }; + + return isEdit + ? ( + + ) + : ( +
{val}
+ ); +}; diff --git a/src/hooks/useJson.ts b/src/hooks/useJson.ts index a06873a..00a5bd8 100644 --- a/src/hooks/useJson.ts +++ b/src/hooks/useJson.ts @@ -1,6 +1,6 @@ import { useState } from 'react'; -import { readJSON } from '@/utils'; +import { readJSON, writeJSON } from '@/utils'; import useInit from '@/hooks/useInit'; export default function useJson(file: string) { @@ -11,7 +11,12 @@ export default function useJson(file: string) { setData(data); }; + const updateJson = async (data: any) => { + await writeJSON(file, data); + await refreshJson(); + }; + useInit(refreshJson); - return { json, refreshJson }; + return { json, refreshJson, updateJson }; } diff --git a/src/hooks/useTable.tsx b/src/hooks/useTable.tsx index 741ab8b..4fc2c65 100644 --- a/src/hooks/useTable.tsx +++ b/src/hooks/useTable.tsx @@ -4,14 +4,35 @@ import type { TableRowSelection } from 'antd/es/table/interface'; import { safeKey } from '@/hooks/useData'; -export default function useTableRowSelection() { +type rowSelectionOptions = { + key: 'id' | string; + rowType: 'id' | 'row' | 'all'; +} +export function useTableRowSelection(options: Partial = {}) { + const { key = 'id', rowType = 'id' } = options; const [selectedRowKeys, setSelectedRowKeys] = useState([]); const [selectedRowIDs, setSelectedRowIDs] = useState([]); + const [selectedRows, setSelectedRows] = useState[]>([]); - const onSelectChange = (newSelectedRowKeys: React.Key[], selectedRows: Record) => { - const keys = selectedRows.map((i: any) => i[safeKey]); - setSelectedRowIDs(keys); + const onSelectChange = (newSelectedRowKeys: React.Key[], newSelectedRows: Record[]) => { + const keys = newSelectedRows.map((i: any) => i[safeKey] || i[key]); setSelectedRowKeys(newSelectedRowKeys); + if (rowType === 'id') { + setSelectedRowIDs(keys); + } + if (rowType === 'row') { + setSelectedRows(newSelectedRows); + } + if (rowType === 'all') { + setSelectedRowIDs(keys); + setSelectedRows(newSelectedRows); + } + }; + + const rowReset = () => { + setSelectedRowKeys([]); + setSelectedRowIDs([]); + setSelectedRows([]); }; const rowSelection: TableRowSelection> = { @@ -24,14 +45,14 @@ export default function useTableRowSelection() { ], }; - return { rowSelection, selectedRowIDs }; + return { rowSelection, selectedRowIDs, selectedRows, rowReset }; } export const TABLE_PAGINATION = { hideOnSinglePage: true, showSizeChanger: true, showQuickJumper: true, - defaultPageSize: 5, + defaultPageSize: 10, pageSizeOptions: [5, 10, 15, 20], showTotal: (total: number) => Total {total} items, }; \ No newline at end of file diff --git a/src/main.scss b/src/main.scss index 9bb6a33..a2eabaa 100644 --- a/src/main.scss +++ b/src/main.scss @@ -31,10 +31,27 @@ html, body { overflow: hidden; text-overflow: ellipsis; display: -webkit-box; - -webkit-line-clamp: 3; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; } +.ellipsis-line { + display: inline-block; + width: 180px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.rowedit { + padding: 2px 5px; + + &:hover { + box-shadow: 0 0 2px rgba(237, 122, 60, 0.8); + border-radius: 4px; + } +} + .chat-add-btn { margin-bottom: 5px; } diff --git a/src/view/download/config.tsx b/src/view/download/config.tsx index dc915f4..274c208 100644 --- a/src/view/download/config.tsx +++ b/src/view/download/config.tsx @@ -1,37 +1,57 @@ +import { useState } from 'react'; import { Tag, Space, Popconfirm } from 'antd'; +import { path, shell } from '@tauri-apps/api'; -import { fmtDate } from '@/utils'; +import { EditRow } from '@/hooks/useColumns'; + +import useInit from '@/hooks/useInit'; +import { fmtDate, chatRoot } from '@/utils'; const colorMap: any = { pdf: 'blue', png: 'orange', } -export const syncColumns = () => [ +export const downloadColumns = () => [ { title: 'Name', dataIndex: 'name', fixed: 'left', key: 'name', + width: 240, + render: (_: string, row: any, actions: any) => ( + + ), }, { title: 'Extension', dataIndex: 'ext', key: 'ext', + width: 120, render: (v: string) => {v}, }, + { + title: 'Path', + dataIndex: 'path', + key: 'path', + width: 200, + render: (_: string, row: any) => , + }, { title: 'Created', dataIndex: 'created', key: 'created', + width: 150, render: fmtDate, }, { title: 'Action', + fixed: 'right', + width: 150, render: (_: any, row: any, actions: any) => { return ( - actions.setRecord(row, 'view')}>View + actions.setRecord(row, 'preview')}>Preview actions.setRecord(row, 'delete')} @@ -46,9 +66,15 @@ export const syncColumns = () => [ } ]; -// { -// id: '', -// name: '', -// type: '.png', -// created: '2022.01.01', -// } \ No newline at end of file +const RenderPath = ({ row }: any) => { + const [filePath, setFilePath] = useState(''); + useInit(async () => { + setFilePath(await getPath(row)); + }) + return shell.open(filePath)}>{filePath}; +}; + +export const getPath = async (row: any) => { + const isImg = ['png'].includes(row?.ext); + return await path.join(await chatRoot(), 'download', isImg ? 'img' : row.ext, row.id) + `.${row.ext}`; +} diff --git a/src/view/download/index.scss b/src/view/download/index.scss deleted file mode 100644 index 4e3ba63..0000000 --- a/src/view/download/index.scss +++ /dev/null @@ -1,12 +0,0 @@ -.chat-table-tip, .chat-table-btns { - display: flex; - justify-content: space-between; -} - -.chat-table-btns { - margin-bottom: 5px; - - .num { - margin-left: 10px; - } -} diff --git a/src/view/download/index.tsx b/src/view/download/index.tsx index 324cc98..ae3909e 100644 --- a/src/view/download/index.tsx +++ b/src/view/download/index.tsx @@ -1,14 +1,14 @@ import { useEffect, useState } from 'react'; -import { Table, Modal } from 'antd'; -import { path, shell, fs } from '@tauri-apps/api'; +import { Table, Modal, Popconfirm, Button, message } from 'antd'; +import { invoke, path, shell, fs } from '@tauri-apps/api'; import useInit from '@/hooks/useInit'; import useJson from '@/hooks/useJson'; +import useData from '@/hooks/useData'; import useColumns from '@/hooks/useColumns'; -import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { chatRoot, CHAT_DOWNLOAD_JSON } from '@/utils'; -import { syncColumns } from './config'; -import './index.scss'; +import { downloadColumns } from './config'; function renderFile(buff: Uint8Array, type: string) { const renderType = { @@ -19,52 +19,124 @@ function renderFile(buff: Uint8Array, type: string) { } export default function SyncPrompts() { - const { rowSelection, selectedRowIDs } = useTable(); - const { columns, ...opInfo } = useColumns(syncColumns()); const [downloadPath, setDownloadPath] = useState(''); - const { json } = useJson(CHAT_DOWNLOAD_JSON); const [source, setSource] = useState(''); const [isVisible, setVisible] = useState(false); + const { opData, opInit, opReplace, opSafeKey } = useData([]); + const { columns, ...opInfo } = useColumns(downloadColumns()); + const { rowSelection, selectedRows, rowReset } = useTableRowSelection({ rowType: 'row' }); + const { json, refreshJson, updateJson } = useJson(CHAT_DOWNLOAD_JSON); + const selectedItems = rowSelection.selectedRowKeys || []; useInit(async () => { - const file = await path.join(await chatRoot(), 'chat.download.json'); + const file = await path.join(await chatRoot(), CHAT_DOWNLOAD_JSON); setDownloadPath(file); }); + useEffect(() => { + if (!json || json.length <= 0) return; + opInit(json); + }, [json?.length]); + useEffect(() => { if (!opInfo.opType) return; (async () => { const record = opInfo?.opRecord; const isImg = ['png'].includes(record?.ext); const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : record?.ext, `${record?.id}.${record?.ext}`); - if (opInfo.opType === 'view') { + if (opInfo.opType === 'preview') { const data = await fs.readBinaryFile(file); const sourceData = renderFile(data, record?.ext); setSource(sourceData); setVisible(true); + return; + } + if (opInfo.opType === 'file') { + await shell.open(file); + } + if (opInfo.opType === 'delete') { + await fs.removeFile(file); + await handleRefresh(); + } + if (opInfo.opType === 'rowedit') { + const data = opReplace(opInfo?.opRecord?.[opSafeKey], opInfo?.opRecord); + await updateJson(data); + message.success('Name has been changed!'); } opInfo.resetRecord(); })() }, [opInfo.opType]) + const handleDelete = async () => { + if (opData?.length === selectedRows.length) { + const downloadDir = await path.join(await chatRoot(), 'download'); + await fs.removeDir(downloadDir, { recursive: true }); + await handleRefresh(); + rowReset(); + message.success('All files have been cleared!'); + return; + } + + const rows = selectedRows.map(async (i) => { + const isImg = ['png'].includes(i?.ext); + const file = await path.join(await chatRoot(), 'download', isImg ? 'img' : i?.ext, `${i?.id}.${i?.ext}`); + await fs.removeFile(file); + return file; + }) + Promise.all(rows).then(async () => { + await handleRefresh(); + message.success('All files selected are cleared!'); + }); + }; + + const handleRefresh = async () => { + await invoke('download_list', { pathname: CHAT_DOWNLOAD_JSON }); + refreshJson(); + }; + + const handleCancel = () => { + setVisible(false); + opInfo.resetRecord(); + }; + return (
+
+
+ {selectedItems.length > 0 && ( + <> + + + + Selected {selectedItems.length} items + + )} +
+
setVisible(false)} + title={
{opInfo?.opRecord?.name || ''}
} + onCancel={handleCancel} footer={false} destroyOnClose > diff --git a/src/view/model/SyncPrompts/config.tsx b/src/view/model/SyncPrompts/config.tsx index 32fbf6a..b8b4ade 100644 --- a/src/view/model/SyncPrompts/config.tsx +++ b/src/view/model/SyncPrompts/config.tsx @@ -1,4 +1,4 @@ -import { Switch, Tag, Tooltip } from 'antd'; +import { Table, Switch, Tag } from 'antd'; import { genCmd } from '@/utils'; @@ -35,13 +35,14 @@ export const syncColumns = () => [ action.setRecord({ ...row, enable: v }, 'enable')} /> ), }, + Table.EXPAND_COLUMN, { title: 'Prompt', dataIndex: 'prompt', key: 'prompt', // width: 300, render: (v: string) => ( - {v} + {v} ), }, ]; diff --git a/src/view/model/SyncPrompts/index.tsx b/src/view/model/SyncPrompts/index.tsx index d5a359e..7db4d2e 100644 --- a/src/view/model/SyncPrompts/index.tsx +++ b/src/view/model/SyncPrompts/index.tsx @@ -6,7 +6,7 @@ import useInit from '@/hooks/useInit'; import useData from '@/hooks/useData'; import useColumns from '@/hooks/useColumns'; import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; -import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { fmtDate, chatRoot } from '@/utils'; import { syncColumns } from './config'; import './index.scss'; @@ -14,7 +14,7 @@ import './index.scss'; const promptsURL = 'https://github.com/f/awesome-chatgpt-prompts/blob/main/prompts.csv'; export default function SyncPrompts() { - const { rowSelection, selectedRowIDs } = useTable(); + const { rowSelection, selectedRowIDs } = useTableRowSelection(); const [jsonPath, setJsonPath] = useState(''); const { modelJson, modelSet } = useChatModel('sync_prompts'); const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath); @@ -93,6 +93,7 @@ export default function SyncPrompts() { dataSource={opData} rowSelection={rowSelection} pagination={TABLE_PAGINATION} + expandable={{expandedRowRender: (record) =>
{record.prompt}
}} /> ) diff --git a/src/view/model/SyncRecord/config.tsx b/src/view/model/SyncRecord/config.tsx index ebd2609..71321ca 100644 --- a/src/view/model/SyncRecord/config.tsx +++ b/src/view/model/SyncRecord/config.tsx @@ -1,4 +1,4 @@ -import { Switch, Tag, Tooltip } from 'antd'; +import { Switch, Tag, Table } from 'antd'; import { genCmd } from '@/utils'; @@ -37,13 +37,14 @@ export const syncColumns = () => [ action.setRecord({ ...row, enable: v }, 'enable')} /> ), }, + Table.EXPAND_COLUMN, { title: 'Prompt', dataIndex: 'prompt', key: 'prompt', // width: 300, render: (v: string) => ( - {v} + {v} ), }, ]; diff --git a/src/view/model/SyncRecord/index.tsx b/src/view/model/SyncRecord/index.tsx index f3e64dc..9f22759 100644 --- a/src/view/model/SyncRecord/index.tsx +++ b/src/view/model/SyncRecord/index.tsx @@ -7,7 +7,7 @@ import { shell, path } from '@tauri-apps/api'; import useColumns from '@/hooks/useColumns'; import useData from '@/hooks/useData'; import { useCacheModel } from '@/hooks/useChatModel'; -import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { fmtDate, chatRoot } from '@/utils'; import { getPath } from '@/view/model/SyncCustom/config'; import { syncColumns } from './config'; @@ -19,7 +19,7 @@ export default function SyncRecord() { const [jsonPath, setJsonPath] = useState(''); const state = location?.state; - const { rowSelection, selectedRowIDs } = useTable(); + const { rowSelection, selectedRowIDs } = useTableRowSelection(); const { modelCacheJson, modelCacheSet } = useCacheModel(jsonPath); const { opData, opInit, opReplace, opReplaceItems, opSafeKey } = useData([]); const { columns, ...opInfo } = useColumns(syncColumns()); @@ -79,6 +79,7 @@ export default function SyncRecord() { dataSource={opData} rowSelection={rowSelection} pagination={TABLE_PAGINATION} + expandable={{expandedRowRender: (record) =>
{record.prompt}
}} /> ) diff --git a/src/view/model/UserCustom/config.tsx b/src/view/model/UserCustom/config.tsx index c023079..0450043 100644 --- a/src/view/model/UserCustom/config.tsx +++ b/src/view/model/UserCustom/config.tsx @@ -1,4 +1,4 @@ -import { Tag, Switch, Tooltip, Space, Popconfirm } from 'antd'; +import { Tag, Switch, Space, Popconfirm, Table } from 'antd'; export const modelColumns = () => [ { @@ -33,13 +33,14 @@ export const modelColumns = () => [ action.setRecord({ ...row, enable: v }, 'enable')} /> ), }, + Table.EXPAND_COLUMN, { title: 'Prompt', dataIndex: 'prompt', key: 'prompt', width: 300, render: (v: string) => ( - {v} + {v} ), }, { diff --git a/src/view/model/UserCustom/index.tsx b/src/view/model/UserCustom/index.tsx index ed8bbad..985fe41 100644 --- a/src/view/model/UserCustom/index.tsx +++ b/src/view/model/UserCustom/index.tsx @@ -6,13 +6,13 @@ import useInit from '@/hooks/useInit'; import useData from '@/hooks/useData'; import useChatModel, { useCacheModel } from '@/hooks/useChatModel'; import useColumns from '@/hooks/useColumns'; -import useTable, { TABLE_PAGINATION } from '@/hooks/useTable'; +import { useTableRowSelection, TABLE_PAGINATION } from '@/hooks/useTable'; import { chatRoot, fmtDate } from '@/utils'; import { modelColumns } from './config'; import UserCustomForm from './Form'; export default function LanguageModel() { - const { rowSelection, selectedRowIDs } = useTable(); + const { rowSelection, selectedRowIDs } = useTableRowSelection(); const [isVisible, setVisible] = useState(false); const [jsonPath, setJsonPath] = useState(''); const { modelJson, modelSet } = useChatModel('user_custom'); @@ -123,6 +123,7 @@ export default function LanguageModel() { dataSource={opData} rowSelection={rowSelection} pagination={TABLE_PAGINATION} + expandable={{expandedRowRender: (record) =>
{record.prompt}
}} />