From 8ff555f9e42a868d3e12d37932799b133ae6c77e Mon Sep 17 00:00:00 2001 From: xue jiahao Date: Fri, 13 Mar 2026 15:25:44 +0800 Subject: [PATCH] update --- electron.vite.config.ts | 15 +- src/main/__tests__/kintone-api.test.ts | 6 +- src/main/ipc-handlers.ts | 483 +++++++++--------- src/main/kintone-api.ts | 102 +--- src/preload/index.d.ts | 1 - .../src/components/AppDetail/AppDetail.tsx | 17 +- .../src/components/AppList/AppList.tsx | 6 +- src/shared/types/domain.ts | 4 +- src/shared/types/ipc.ts | 9 +- src/shared/types/kintone.ts | 73 ++- src/shared/utils/fileDisplay.ts | 48 ++ 11 files changed, 393 insertions(+), 371 deletions(-) create mode 100644 src/shared/utils/fileDisplay.ts diff --git a/electron.vite.config.ts b/electron.vite.config.ts index e7dc681..1887aa5 100644 --- a/electron.vite.config.ts +++ b/electron.vite.config.ts @@ -4,15 +4,28 @@ import react from '@vitejs/plugin-react' export default defineConfig({ main: { + resolve: { + alias: { + '@shared': resolve('src/shared'), + '@main': resolve('src/main') + } + }, plugins: [externalizeDepsPlugin()] }, preload: { + resolve: { + alias: { + '@shared': resolve('src/shared'), + '@preload': resolve('src/preload') + } + }, plugins: [externalizeDepsPlugin()] }, renderer: { resolve: { alias: { - '@renderer': resolve('src/renderer/src') + '@renderer': resolve('src/renderer/src'), + '@shared': resolve('src/shared') } }, plugins: [react()] diff --git a/src/main/__tests__/kintone-api.test.ts b/src/main/__tests__/kintone-api.test.ts index 01526ca..07f3107 100644 --- a/src/main/__tests__/kintone-api.test.ts +++ b/src/main/__tests__/kintone-api.test.ts @@ -9,7 +9,7 @@ */ import { describe, expect, it, beforeAll, afterAll } from "vitest"; -import { SelfKintoneClient, createKintoneClient } from "@main/kintone-api"; +import { KintoneClient, createKintoneClient } from "@main/kintone-api"; import type { DomainWithPassword } from "@shared/types/domain"; // Test configuration @@ -25,7 +25,7 @@ const TEST_CONFIG: DomainWithPassword = { }; describe("SelfKintoneClient - API Integration Tests", () => { - let client: SelfKintoneClient; + let client: KintoneClient; beforeAll(() => { // Create client with test credentials @@ -137,7 +137,7 @@ describe("SelfKintoneClient - API Integration Tests", () => { describe("Create Kintone Client Factory", () => { it("should create a client instance", () => { const client = createKintoneClient(TEST_CONFIG); - expect(client).toBeInstanceOf(SelfKintoneClient); + expect(client).toBeInstanceOf(KintoneClient); }); it("should return the correct domain", () => { diff --git a/src/main/ipc-handlers.ts b/src/main/ipc-handlers.ts index ca9742e..fd86209 100644 --- a/src/main/ipc-handlers.ts +++ b/src/main/ipc-handlers.ts @@ -16,7 +16,7 @@ import { saveDownload, saveBackup, } from "./storage"; -import { SelfKintoneClient, createKintoneClient } from "./kintone-api"; +import { KintoneClient, createKintoneClient } from "./kintone-api"; import type { Result } from "@shared/types/ipc"; import type { CreateDomainParams, @@ -32,21 +32,27 @@ import type { GetVersionsParams, RollbackParams, } from "@shared/types/ipc"; -import type { Domain, DomainWithStatus, DomainWithPassword } from "@shared/types/domain"; +import type { + Domain, + DomainWithStatus, + DomainWithPassword, +} from "@shared/types/domain"; import type { Version, DownloadMetadata, BackupMetadata, + DownloadFile, } from "@shared/types/version"; -import type { AppCustomizeResponse } from "@shared/types/kintone"; +import type { AppCustomizeParameter, AppDetail } from "@shared/types/kintone"; +import { getDisplayName, getFileKey } from "@shared/utils/fileDisplay"; // Cache for Kintone clients -const clientCache = new Map(); +const clientCache = new Map(); /** * Get or create a Kintone client for a domain */ -async function getClient(domainId: string): Promise { +async function getClient(domainId: string): Promise { if (clientCache.has(domainId)) { return clientCache.get(domainId)!; } @@ -64,28 +70,15 @@ async function getClient(domainId: string): Promise { /** * Helper to wrap IPC handlers with error handling */ -function handle(channel: string, handler: () => Promise): void { - ipcMain.handle(channel, async (): Promise> => { - try { - const data = await handler(); - return { success: true, data }; - } catch (error) { - const message = error instanceof Error ? error.message : "Unknown error"; - return { success: false, error: message }; - } - }); -} - -/** - * Helper to wrap IPC handlers with parameters - */ -function handleWithParams( +function handle

( channel: string, handler: (params: P) => Promise, ): void { ipcMain.handle(channel, async (_event, params: P): Promise> => { try { - const data = await handler(params); + // For handlers without params (P=void), params will be undefined but handler ignores it + // For handlers with params, params will be the typed value + const data = await handler(params as P); return { success: true, data }; } catch (error) { const message = error instanceof Error ? error.message : "Unknown error"; @@ -110,89 +103,83 @@ function registerGetDomains(): void { * Deduplication: Check if domain+username already exists */ function registerCreateDomain(): void { - handleWithParams( - "createDomain", - async (params) => { - // Check for duplicate domain+username - const existingDomains = await listDomains(); - const duplicate = existingDomains.find( - (d) => - d.domain.toLowerCase() === params.domain.toLowerCase() && - d.username.toLowerCase() === params.username.toLowerCase() + handle("createDomain", async (params) => { + // Check for duplicate domain+username + const existingDomains = await listDomains(); + const duplicate = existingDomains.find( + (d) => + d.domain.toLowerCase() === params.domain.toLowerCase() && + d.username.toLowerCase() === params.username.toLowerCase(), + ); + + if (duplicate) { + throw new Error( + `Domain "${params.domain}" with user "${params.username}" already exists. Please edit the existing domain instead.`, ); + } - if (duplicate) { - throw new Error( - `Domain "${params.domain}" with user "${params.username}" already exists. Please edit the existing domain instead.` - ); - } + const now = new Date().toISOString(); + const domain: Domain = { + id: uuidv4(), + name: params.name, + domain: params.domain, + username: params.username, + authType: params.authType, + apiToken: params.apiToken, + createdAt: now, + updatedAt: now, + }; - const now = new Date().toISOString(); - const domain: Domain = { - id: uuidv4(), - name: params.name, - domain: params.domain, - username: params.username, - authType: params.authType, - apiToken: params.apiToken, - createdAt: now, - updatedAt: now, - }; - - await saveDomain(domain, params.password); - return domain; - }, - ); + await saveDomain(domain, params.password); + return domain; + }); } /** * Update an existing domain */ function registerUpdateDomain(): void { - handleWithParams( - "updateDomain", - async (params) => { - const domains = await listDomains(); - const existing = domains.find((d) => d.id === params.id); + handle("updateDomain", async (params) => { + const domains = await listDomains(); + const existing = domains.find((d) => d.id === params.id); - if (!existing) { - throw new Error(`Domain not found: ${params.id}`); + if (!existing) { + throw new Error(`Domain not found: ${params.id}`); + } + + const updated: Domain = { + ...existing, + name: params.name ?? existing.name, + domain: params.domain ?? existing.domain, + username: params.username ?? existing.username, + authType: params.authType ?? existing.authType, + apiToken: params.apiToken ?? existing.apiToken, + updatedAt: new Date().toISOString(), + }; + + // If password is provided, update it + if (params.password) { + await saveDomain(updated, params.password); + } else { + // Get existing password and re-save + const existingWithPassword = await getDomain(params.id); + if (existingWithPassword) { + await saveDomain(updated, existingWithPassword.password); } + } - const updated: Domain = { - ...existing, - name: params.name ?? existing.name, - domain: params.domain ?? existing.domain, - username: params.username ?? existing.username, - authType: params.authType ?? existing.authType, - apiToken: params.apiToken ?? existing.apiToken, - updatedAt: new Date().toISOString(), - }; + // Clear cached client + clientCache.delete(params.id); - // If password is provided, update it - if (params.password) { - await saveDomain(updated, params.password); - } else { - // Get existing password and re-save - const existingWithPassword = await getDomain(params.id); - if (existingWithPassword) { - await saveDomain(updated, existingWithPassword.password); - } - } - - // Clear cached client - clientCache.delete(params.id); - - return updated; - }, - ); + return updated; + }); } /** * Delete a domain */ function registerDeleteDomain(): void { - handleWithParams("deleteDomain", async (id) => { + handle("deleteDomain", async (id) => { await deleteDomain(id); clientCache.delete(id); }); @@ -202,7 +189,7 @@ function registerDeleteDomain(): void { * Test domain connection */ function registerTestConnection(): void { - handleWithParams("testConnection", async (id) => { + handle("testConnection", async (id) => { const domainWithPassword = await getDomain(id); if (!domainWithPassword) { throw new Error(`Domain not found: ${id}`); @@ -223,7 +210,7 @@ function registerTestConnection(): void { * Test domain connection with temporary credentials */ function registerTestDomainConnection(): void { - handleWithParams( + handle( "testDomainConnection", async (params) => { const tempDomain: DomainWithPassword = { @@ -256,27 +243,25 @@ function registerTestDomainConnection(): void { * Get apps */ function registerGetApps(): void { - handleWithParams< - GetAppsParams, - Awaited> - >("getApps", async (params) => { - const client = await getClient(params.domainId); - return client.getApps({ - limit: params.limit, - offset: params.offset, - }); - }); + handle>>( + "getApps", + async (params) => { + const client = await getClient(params.domainId); + return client.getApps({ + limit: params.limit, + offset: params.offset, + }); + }, + ); } - - /** * Get app detail */ function registerGetAppDetail(): void { - handleWithParams< + handle< GetAppDetailParams, - Awaited> + Awaited> >("getAppDetail", async (params) => { const client = await getClient(params.domainId); return client.getAppDetail(params.appId); @@ -287,9 +272,9 @@ function registerGetAppDetail(): void { * Get file content */ function registerGetFileContent(): void { - handleWithParams< + handle< GetFileContentParams, - Awaited> + Awaited> >("getFileContent", async (params) => { const client = await getClient(params.domainId); return client.getFileContent(params.fileKey); @@ -298,11 +283,45 @@ function registerGetFileContent(): void { // ==================== Deploy IPC Handlers ==================== +/** + * Add files to backup for a specific platform and file type + */ +async function addFilesToBackup( + platform: "desktop" | "mobile", + fileType: "js" | "css", + client: KintoneClient, + appDetail: AppDetail, + backupFiles: Map, + backupFileList: DownloadFile[] +): Promise { + const files = appDetail.customization?.[platform]?.[fileType] || []; + + for (const [index, file] of files.entries()) { + const fileKey = getFileKey(file); + if (fileKey) { + const fileContent = await client.getFileContent(fileKey); + const content = Buffer.from(fileContent.content || "", "base64"); + const fileName = getDisplayName(file, fileType, index); + const type = platform === "desktop" ? "pc" : "mobile"; + + backupFiles.set(`${type}/${fileName}`, content); + backupFileList.push({ + type, + fileType, + fileName, + fileKey, + size: content.length, + path: `${type}/${fileName}`, + }); + } + } +} + /** * Deploy files to Kintone */ function registerDeploy(): void { - handleWithParams("deploy", async (params) => { + handle("deploy", async (params) => { const client = await getClient(params.domainId); const domainWithPassword = await getDomain(params.domainId); @@ -317,39 +336,14 @@ function registerDeploy(): void { const backupFiles = new Map(); const backupFileList: BackupMetadata["files"] = []; - // Add JS files to backup - for (const js of appDetail.customization?.desktop?.js || []) { - if (js.file?.fileKey) { - const fileContent = await client.getFileContent(js.file.fileKey); - const content = Buffer.from(fileContent.content || "", "base64"); - backupFiles.set(`pc/${js.file.name || js.file.fileKey}.js`, content); - backupFileList.push({ - type: "pc", - fileType: "js", - fileName: js.file.name || js.file.fileKey, - fileKey: js.file.fileKey, - size: content.length, - path: `pc/${js.file.name || js.file.fileKey}.js`, - }); - } - } + // Add desktop files to backup + await addFilesToBackup("desktop", "js", client, appDetail, backupFiles, backupFileList); + await addFilesToBackup("desktop", "css", client, appDetail, backupFiles, backupFileList); + + // Add mobile files to backup + await addFilesToBackup("mobile", "js", client, appDetail, backupFiles, backupFileList); + await addFilesToBackup("mobile", "css", client, appDetail, backupFiles, backupFileList); - // Add CSS files to backup - for (const css of appDetail.customization?.desktop?.css || []) { - if (css.file?.fileKey) { - const fileContent = await client.getFileContent(css.file.fileKey); - const content = Buffer.from(fileContent.content || "", "base64"); - backupFiles.set(`pc/${css.file.name || css.file.fileKey}.css`, content); - backupFileList.push({ - type: "pc", - fileType: "css", - fileName: css.file.name || css.file.fileKey, - fileKey: css.file.fileKey, - size: content.length, - path: `pc/${css.file.name || css.file.fileKey}.css`, - }); - } - } // Save backup const backupMetadata: BackupMetadata = { backedUpAt: new Date().toISOString(), @@ -363,59 +357,42 @@ function registerDeploy(): void { const backupPath = await saveBackup(backupMetadata, backupFiles); - // Upload new files - const uploadedFiles: Array<{ - type: "js" | "css"; - position: string; - fileKey: string; - }> = []; + // Upload new files and build customization config directly + const newConfig: AppCustomizeParameter = { + app: params.appId, + scope: "ALL", + desktop: { + js: [], + css: [], + }, + mobile: { + js: [], + css: [], + }, + }; for (const file of params.files) { const fileKey = await client.uploadFile(file.content, file.fileName); + const fileEntry = { type: "FILE" as const, file: { fileKey: fileKey.fileKey } }; - uploadedFiles.push({ - type: file.fileType, - position: file.position, - fileKey: fileKey.fileKey, - }); + // Add to corresponding field based on file type and position + if (file.fileType === "js") { + if (file.position.startsWith("pc_")) { + newConfig.desktop!.js!.push(fileEntry); + } else if (file.position.startsWith("mobile_")) { + newConfig.mobile!.js!.push(fileEntry); + } + } else if (file.fileType === "css") { + if (file.position === "pc_css") { + newConfig.desktop!.css!.push(fileEntry); + } else if (file.position === "mobile_css") { + newConfig.mobile!.css!.push(fileEntry); + } + } } - // Build new customization config - // Note: This is simplified - real implementation would merge with existing config - const newConfig: AppCustomizeResponse = { - desktop: { - js: uploadedFiles - .filter((f) => f.type === "js" && f.position.startsWith("pc_")) - .map((f) => ({ - type: "FILE" as const, - file: { fileKey: f.fileKey }, - })), - css: uploadedFiles - .filter((f) => f.type === "css" && f.position === "pc_css") - .map((f) => ({ - type: "FILE" as const, - file: { fileKey: f.fileKey }, - })), - }, - mobile: { - js: uploadedFiles - .filter((f) => f.type === "js" && f.position.startsWith("mobile_")) - .map((f) => ({ - type: "FILE" as const, - file: { fileKey: f.fileKey }, - })), - css: uploadedFiles - .filter((f) => f.type === "css" && f.position === "mobile_css") - .map((f) => ({ - type: "FILE" as const, - file: { fileKey: f.fileKey }, - })), - }, - scope: "ALL", - revision: "-1", - }; - // Update app customization + await client.updateAppCustomize(params.appId, newConfig); // Deploy the changes await client.deployApp(params.appId); @@ -434,81 +411,79 @@ function registerDeploy(): void { * Download files from Kintone */ function registerDownload(): void { - handleWithParams( - "download", - async (params) => { - const client = await getClient(params.domainId); - const domainWithPassword = await getDomain(params.domainId); + handle("download", async (params) => { + const client = await getClient(params.domainId); + const domainWithPassword = await getDomain(params.domainId); - if (!domainWithPassword) { - throw new Error(`Domain not found: ${params.domainId}`); - } + if (!domainWithPassword) { + throw new Error(`Domain not found: ${params.domainId}`); + } - const appDetail = await client.getAppDetail(params.appId); - const downloadFiles = new Map(); - const downloadFileList: DownloadMetadata["files"] = []; + const appDetail = await client.getAppDetail(params.appId); + const downloadFiles = new Map(); + const downloadFileList: DownloadMetadata["files"] = []; - // Download based on requested file types - const fileTypes = params.fileTypes || [ - "pc_js", - "pc_css", - "mobile_js", - "mobile_css", - ]; + // Download based on requested file types + const fileTypes = params.fileTypes || [ + "pc_js", + "pc_css", + "mobile_js", + "mobile_css", + ]; - for (const fileType of fileTypes) { - const files = - fileType === "pc_js" - ? appDetail.customization?.desktop?.js - : fileType === "pc_css" - ? appDetail.customization?.desktop?.css - : fileType === "mobile_js" - ? appDetail.customization?.mobile?.js - : appDetail.customization?.mobile?.css; + for (const fileType of fileTypes) { + const files = + fileType === "pc_js" + ? appDetail.customization?.desktop?.js + : fileType === "pc_css" + ? appDetail.customization?.desktop?.css + : fileType === "mobile_js" + ? appDetail.customization?.mobile?.js + : appDetail.customization?.mobile?.css; - for (const file of files || []) { - if (file.file?.fileKey) { - const content = await client.getFileContent(file.file.fileKey); - const buffer = Buffer.from(content.content || "", "base64"); - const fileName = file.file.name || file.file.fileKey; - const type = fileType.includes("mobile") ? "mobile" : "pc"; - const ext = fileType.includes("js") ? "js" : "css"; + for (const [index, file] of (files || []).entries()) { + const fileKey = getFileKey(file); + if (fileKey) { + const content = await client.getFileContent(fileKey); + const buffer = Buffer.from(content.content || "", "base64"); + const ext = fileType.includes("js") ? "js" : "css"; + const fileName = getDisplayName(file, ext, index); + const type = fileType.includes("mobile") ? "mobile" : "pc"; - downloadFiles.set(`${type}/${fileName}`, buffer); - downloadFileList.push({ - type, - fileType: ext, - fileName, - fileKey: file.file.fileKey, - size: buffer.length, - path: `${type}/${fileName}`, - }); - } + downloadFiles.set(`${type}/${fileName}`, buffer); + downloadFileList.push({ + type, + fileType: ext, + fileName, + fileKey, + size: buffer.length, + path: `${type}/${fileName}`, + }); } } + } - // Save download metadata - const metadata: DownloadMetadata = { - downloadedAt: new Date().toISOString(), - domain: domainWithPassword.domain, - domainId: params.domainId, - appId: params.appId, - appName: appDetail.name, - spaceId: appDetail.spaceId || "", - spaceName: "", // Would need to fetch from space API - files: downloadFileList, - source: "kintone", - }; + // Save download metadata + const metadata: DownloadMetadata = { + downloadedAt: new Date().toISOString(), + domain: domainWithPassword.domain, + domainId: params.domainId, + appId: params.appId, + appName: appDetail.name, + spaceId: appDetail.spaceId || "", + spaceName: "", // Would need to fetch from space API + files: downloadFileList, + source: "kintone", + }; - const path = await saveDownload(metadata, downloadFiles); + const path = await saveDownload(metadata, downloadFiles); - return { - success: true, - path, - metadata, - }; - }, - ); + return { + success: true, + path, + metadata, + }; + }); } // ==================== Version IPC Handlers ==================== @@ -517,19 +492,16 @@ function registerDownload(): void { * Get versions for an app */ function registerGetVersions(): void { - handleWithParams( - "getVersions", - async (params) => { - return listVersions(params.domainId, params.appId); - }, - ); + handle("getVersions", async (params) => { + return listVersions(params.domainId, params.appId); + }); } /** * Delete a version */ function registerDeleteVersion(): void { - handleWithParams("deleteVersion", async (id) => { + handle("deleteVersion", async (id) => { return deleteVersion(id); }); } @@ -538,7 +510,7 @@ function registerDeleteVersion(): void { * Rollback to a previous version */ function registerRollback(): void { - handleWithParams("rollback", async (_params) => { + handle("rollback", async (_params) => { // This would read the version file and redeploy // Simplified implementation - would need full implementation throw new Error("Rollback not yet implemented"); @@ -577,3 +549,4 @@ export function registerIpcHandlers(): void { console.log("IPC handlers registered"); } + diff --git a/src/main/kintone-api.ts b/src/main/kintone-api.ts index 9fdbb8d..ff73dd4 100644 --- a/src/main/kintone-api.ts +++ b/src/main/kintone-api.ts @@ -1,14 +1,13 @@ -import { KintoneRestAPIClient } from "@kintone/rest-api-client"; -import type { KintoneRestAPIError } from "@kintone/rest-api-client"; -import type { DomainWithPassword } from "@shared/types/domain"; -import type { - AppResponse, - AppCustomizeResponse, - AppDetail, - FileContent, - KintoneApiError, - FileConfig, -} from "@shared/types/kintone"; +import { KintoneRestAPIClient } from '@kintone/rest-api-client'; +import type { KintoneRestAPIError } from '@kintone/rest-api-client'; +import type { DomainWithPassword } from '@shared/types/domain'; +import { + type AppResponse, + type AppDetail, + type FileContent, + type KintoneApiError, + AppCustomizeParameter, +} from '@shared/types/kintone'; /** * Custom error class for Kintone API errors @@ -18,13 +17,9 @@ export class KintoneError extends Error { public readonly id?: string; public readonly statusCode?: number; - constructor( - message: string, - apiError?: KintoneApiError, - statusCode?: number, - ) { + constructor(message: string, apiError?: KintoneApiError, statusCode?: number) { super(message); - this.name = "KintoneError"; + this.name = 'KintoneError'; this.code = apiError?.code; this.id = apiError?.id; this.statusCode = statusCode; @@ -34,7 +29,7 @@ export class KintoneError extends Error { /** * Kintone REST API Client */ -export class SelfKintoneClient { +export class KintoneClient { private client: KintoneRestAPIClient; private domain: string; @@ -42,8 +37,8 @@ export class SelfKintoneClient { this.domain = domainConfig.domain; const auth = - domainConfig.authType === "api_token" - ? { apiToken: domainConfig.apiToken || "" } + domainConfig.authType === 'api_token' + ? { apiToken: domainConfig.apiToken || '' } : { username: domainConfig.username, password: domainConfig.password, @@ -56,7 +51,7 @@ export class SelfKintoneClient { } private convertError(error: unknown): KintoneError { - if (error && typeof error === "object" && "code" in error) { + if (error && typeof error === 'object' && 'code' in error) { const apiError = error as KintoneRestAPIError; return new KintoneError( apiError.message, @@ -69,7 +64,7 @@ export class SelfKintoneClient { return new KintoneError(error.message); } - return new KintoneError("Unknown error occurred"); + return new KintoneError('Unknown error occurred'); } private async withErrorHandling(operation: () => Promise): Promise { @@ -80,31 +75,13 @@ export class SelfKintoneClient { } } - private buildCustomizeSection( - files?: FileConfig[], - ): NonNullable< - Parameters[0]["desktop"] - >["js"] { - if (!files || files.length === 0) return undefined; - - return files.map((item) => { - if (item.type === "FILE" && item.file) { - return { type: "FILE" as const, file: { fileKey: item.file.fileKey } }; - } - return { type: "URL" as const, url: item.url || "" }; - }); - } - // ==================== App APIs ==================== /** * Get all apps with pagination support * Fetches all apps by making multiple requests if needed */ - async getApps(options?: { - limit?: number; - offset?: number; - }): Promise { + async getApps(options?: { limit?: number; offset?: number }): Promise { return this.withErrorHandling(async () => { // If pagination options provided, use them directly if (options?.limit !== undefined || options?.offset !== undefined) { @@ -157,23 +134,19 @@ export class SelfKintoneClient { return this.withErrorHandling(async () => { const data = await this.client.file.downloadFile({ fileKey }); const buffer = Buffer.from(data); - const content = buffer.toString("base64"); + const content = buffer.toString('base64'); return { fileKey, name: fileKey, size: buffer.byteLength, - mimeType: "application/octet-stream", + mimeType: 'application/octet-stream', content, }; }); } - async uploadFile( - content: string | Buffer, - fileName: string, - _mimeType?: string, - ): Promise<{ fileKey: string }> { + async uploadFile(content: string | Buffer, fileName: string, _mimeType?: string): Promise<{ fileKey: string }> { return this.withErrorHandling(async () => { const response = await this.client.file.uploadFile({ file: { name: fileName, data: content }, @@ -184,23 +157,9 @@ export class SelfKintoneClient { // ==================== Deploy APIs ==================== - async updateAppCustomize( - appId: string, - config: AppCustomizeResponse, - ): Promise { + async updateAppCustomize(appId: string, config: Omit): Promise { return this.withErrorHandling(async () => { - await this.client.app.updateAppCustomize({ - app: appId, - desktop: { - js: this.buildCustomizeSection(config.desktop?.js), - css: this.buildCustomizeSection(config.desktop?.css), - }, - mobile: { - js: this.buildCustomizeSection(config.mobile?.js), - css: this.buildCustomizeSection(config.mobile?.css), - }, - scope: config.scope || "ALL", - }); + await this.client.app.updateAppCustomize({ ...config, app: appId }); }); } @@ -210,12 +169,10 @@ export class SelfKintoneClient { }); } - async getDeployStatus( - appId: string, - ): Promise<"PROCESSING" | "SUCCESS" | "FAIL" | "CANCEL"> { + async getDeployStatus(appId: string): Promise<'PROCESSING' | 'SUCCESS' | 'FAIL' | 'CANCEL'> { return this.withErrorHandling(async () => { const response = await this.client.app.getDeployStatus({ apps: [appId] }); - return response.apps[0]?.status || "FAIL"; + return response.apps[0]?.status || 'FAIL'; }); } @@ -229,8 +186,7 @@ export class SelfKintoneClient { } catch (error) { return { success: false, - error: - error instanceof KintoneError ? error.message : "Connection failed", + error: error instanceof KintoneError ? error.message : 'Connection failed', }; } } @@ -240,8 +196,6 @@ export class SelfKintoneClient { } } -export function createKintoneClient( - domain: DomainWithPassword, -): SelfKintoneClient { - return new SelfKintoneClient(domain); +export function createKintoneClient(domain: DomainWithPassword): KintoneClient { + return new KintoneClient(domain); } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index ecd3575..f875330 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -45,7 +45,6 @@ export interface SelfAPI { ) => Promise>; // ==================== Browse ==================== - getSpaces: (params: { domainId: string }) => Promise>; getApps: (params: GetAppsParams) => Promise>; getAppDetail: (params: GetAppDetailParams) => Promise>; getFileContent: ( diff --git a/src/renderer/src/components/AppDetail/AppDetail.tsx b/src/renderer/src/components/AppDetail/AppDetail.tsx index 5f9299e..4e7bdb6 100644 --- a/src/renderer/src/components/AppDetail/AppDetail.tsx +++ b/src/renderer/src/components/AppDetail/AppDetail.tsx @@ -24,8 +24,8 @@ import { createStyles } from "antd-style"; import { useAppStore } from "@renderer/stores"; import { useDomainStore } from "@renderer/stores"; import { CodeViewer } from "../CodeViewer"; -import { FileConfig } from "@shared/types/kintone"; - +import { FileConfigResponse } from "@shared/types/kintone"; +import { getDisplayName, getFileKey, isFileUpload } from "@shared/utils/fileDisplay"; const useStyles = createStyles(({ token, css }) => ({ container: css` height: 100%; @@ -129,7 +129,7 @@ const AppDetail: React.FC = () => { const [activeTab, setActiveTab] = React.useState("info"); const [selectedFile, setSelectedFile] = React.useState<{ type: "js" | "css"; - fileKey: string; + fileKey?: string; name: string; } | null>(null); @@ -168,7 +168,7 @@ const AppDetail: React.FC = () => { } const renderFileList = ( - files: (FileConfig)[] | undefined, + files: (FileConfigResponse)[] | undefined, type: "js" | "css", ) => { if (!files || files.length === 0) { @@ -178,8 +178,9 @@ const AppDetail: React.FC = () => { return (

{files.map((file, index) => { - const fileName = file.file?.name || file.url || `文件 ${index + 1}`; - const fileKey = file.file?.fileKey; + const fileName = getDisplayName(file, type, index); + const fileKey = getFileKey(file); + const canView = isFileUpload(file); return (
{
- {fileKey && ( + {canView && (