add store and UI fix

This commit is contained in:
2026-03-16 17:27:09 +08:00
parent 53541f8b46
commit b62ce11e23
6 changed files with 146 additions and 180 deletions

View File

@@ -17,8 +17,7 @@ import {
ArrowLeft, ArrowLeft,
} from "lucide-react"; } from "lucide-react";
import { createStyles } from "antd-style"; import { createStyles } from "antd-style";
import { useAppStore } from "@renderer/stores"; import { useAppStore, useDomainStore, useSessionStore } from "@renderer/stores";
import { useDomainStore } from "@renderer/stores";
import { CodeViewer } from "../CodeViewer"; import { CodeViewer } from "../CodeViewer";
import { FileConfigResponse, isFileResource } from "@shared/types/kintone"; import { FileConfigResponse, isFileResource } from "@shared/types/kintone";
import { getDisplayName, getFileKey } from "@shared/utils/fileDisplay"; import { getDisplayName, getFileKey } from "@shared/utils/fileDisplay";
@@ -51,6 +50,7 @@ const useStyles = createStyles(({ token, css }) => ({
flex: 1; flex: 1;
overflow: auto; overflow: auto;
padding: ${token.paddingMD}px; padding: ${token.paddingMD}px;
padding-top: ${token.paddingXS}px;
`, `,
loading: css` loading: css`
display: flex; display: flex;
@@ -129,8 +129,9 @@ const useStyles = createStyles(({ token, css }) => ({
`, `,
// Back button - no border, left aligned with text // Back button - no border, left aligned with text
backButton: css` backButton: css`
padding: 0; padding: ${token.marginSM}px 0;
margin-bottom: ${token.marginMD}px; padding-left: ${token.marginXS}px;
margin-bottom: ${token.marginXS}px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
@@ -152,14 +153,7 @@ const AppDetail: React.FC = () => {
const { currentDomain } = useDomainStore(); const { currentDomain } = useDomainStore();
const { currentApp, selectedAppId, loading, setCurrentApp, setLoading } = const { currentApp, selectedAppId, loading, setCurrentApp, setLoading } =
useAppStore(); useAppStore();
const { viewMode, selectedFile, setViewMode, setSelectedFile } = useSessionStore();
// View mode: 'list' shows file sections, 'code' shows code viewer
const [viewMode, setViewMode] = React.useState<"list" | "code">("list");
const [selectedFile, setSelectedFile] = React.useState<{
type: "js" | "css";
fileKey: string;
name: string;
} | null>(null);
// Download state: track which file is being downloaded // Download state: track which file is being downloaded
const [downloadingKey, setDownloadingKey] = React.useState<string | null>( const [downloadingKey, setDownloadingKey] = React.useState<string | null>(
@@ -463,7 +457,7 @@ const AppDetail: React.FC = () => {
<div className={styles.title}> <div className={styles.title}>
<LayoutGrid size={24} style={{ color: "#1890ff" }} /> <LayoutGrid size={24} style={{ color: "#1890ff" }} />
<h3 className={styles.appName}>{currentApp.name}</h3> <h3 className={styles.appName}>{currentApp.name}</h3>
<Tag color="blue">{currentApp.appId}</Tag> <Tag>ID: {currentApp.appId}</Tag>
</div> </div>
<Space> <Space>
<Button icon={<History size={16} />}> <Button icon={<History size={16} />}>

View File

@@ -59,34 +59,6 @@ const useStyles = createStyles(({ token, css }) => ({
align-items: center; align-items: center;
height: 300px; height: 300px;
`, `,
listItemMotion: css`
cursor: pointer;
padding: ${token.paddingSM}px ${token.paddingMD}px !important;
border-bottom: 1px solid ${token.colorBorderSecondary} !important;
position: relative;
&:hover {
background: ${token.colorBgTextHover};
}
`,
listItemActive: css`
background: ${token.colorPrimaryBgHover} !important;
border-left: 3px solid ${token.colorPrimary} !important;
`,
listItemPinned: css`
background: ${token.colorWarningBg} !important;
&:hover {
background: ${token.colorWarningBgHover} !important;
}
`,
appName: css`
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1;
`,
appId: css` appId: css`
font-family: monospace; font-family: monospace;
font-size: 12px; font-size: 12px;
@@ -105,54 +77,6 @@ const useStyles = createStyles(({ token, css }) => ({
color: ${token.colorTextSecondary}; color: ${token.colorTextSecondary};
font-size: 12px; font-size: 12px;
`, `,
appInfoWrapper: css`
display: flex;
align-items: center;
gap: ${token.paddingSM}px;
flex: 1;
min-width: 0;
position: relative;
`,
iconWrapper: css`
position: relative;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
`,
pinOverlay: css`
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.15s ease;
z-index: 1;
`,
pinOverlayVisible: css`
opacity: 1;
`,
pinButton: css`
color: ${token.colorTextTertiary};
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: ${token.colorWarning};
}
`,
pinButtonPinned: css`
color: ${token.colorWarning};
`,
})); }));
const AppList: React.FC = () => { const AppList: React.FC = () => {
@@ -374,7 +298,6 @@ const AppList: React.FC = () => {
isPinned={currentPinnedApps.includes(app.appId)} isPinned={currentPinnedApps.includes(app.appId)}
onItemClick={handleItemClick} onItemClick={handleItemClick}
onPinToggle={handlePinToggle} onPinToggle={handlePinToggle}
styles={styles}
t={t} t={t}
/> />
)) ))

View File

@@ -5,81 +5,89 @@
import React from "react"; import React from "react";
import { motion } from "motion/react"; import { motion } from "motion/react";
import { Typography } from "antd"; import { Tooltip, Tag } from "@lobehub/ui";
import { Tooltip } from "@lobehub/ui";
import { Pin, LayoutGrid } from "lucide-react"; import { Pin, LayoutGrid } from "lucide-react";
import { createStyles } from "antd-style";
import type { AppDetail } from "@shared/types/kintone"; import type { AppDetail } from "@shared/types/kintone";
const { Text } = Typography; const useStyles = createStyles(({ token, css }) => ({
listItemMotion: css`
cursor: pointer;
padding: ${token.paddingSM}px ${token.paddingMD}px !important;
border-bottom: 1px solid ${token.colorBorderSecondary} !important;
position: relative;
// Styles for AppListItem - defined inline to be self-contained &:hover {
const listItemStyles = { background: ${token.colorBgTextHover};
listItemMotion: { }
cursor: "pointer", `,
padding: "8px 16px !important", listItemActive: css`
borderBottom: background: ${token.colorPrimaryBgHover} !important;
"1px solid var(--ant-color-border-secondary, #f0f0f0) !important", border-left: 3px solid ${token.colorPrimary} !important;
position: "relative" as const, `,
}, listItemPinned: css`
listItemActive: { background: ${token.colorWarningBg} !important;
background: "var(--ant-color-primary-bg-hover, #e6f7ff) !important",
borderLeft: "3px solid var(--ant-color-primary, #1890ff) !important", &:hover {
}, background: ${token.colorWarningBgHover} !important;
listItemPinned: { }
background: "var(--ant-color-warning-bg, #fffbe6) !important", `,
}, appInfoWrapper: css`
appInfoWrapper: { display: flex;
display: "flex", align-items: center;
alignItems: "center", gap: ${token.paddingSM}px;
gap: "8px", flex: 1;
flex: 1, min-width: 0;
minWidth: 0, position: relative;
position: "relative" as const, `,
}, iconWrapper: css`
iconWrapper: { position: relative;
position: "relative" as const, width: 20px;
width: "20px", height: 20px;
height: "20px", display: flex;
display: "flex", align-items: center;
alignItems: "center", justify-content: center;
justifyContent: "center", flex-shrink: 0;
flexShrink: 0, `,
}, pinOverlay: css`
pinOverlay: { position: absolute;
position: "absolute" as const, top: 0;
top: 0, left: 0;
left: 0, width: 20px;
width: "20px", height: 20px;
height: "20px", display: flex;
display: "flex", align-items: center;
alignItems: "center", justify-content: center;
justifyContent: "center", opacity: 0;
opacity: 0, transition: opacity 0.15s ease;
transition: "opacity 0.15s ease", z-index: 1;
zIndex: 1, `,
}, pinOverlayVisible: css`
appName: { opacity: 1;
fontWeight: 500, `,
overflow: "hidden", appName: css`
textOverflow: "ellipsis", font-weight: 500;
whiteSpace: "nowrap", overflow: hidden;
flex: 1, text-overflow: ellipsis;
}, white-space: nowrap;
appId: { flex: 1;
fontFamily: "monospace", `,
fontSize: "12px", pinButton: css`
color: "var(--ant-color-text-secondary, #8c8c8c)", color: ${token.colorTextTertiary};
flexShrink: 0, cursor: pointer;
}, font-size: 14px;
pinButton: { display: flex;
color: "var(--ant-color-text-tertiary, #bfbfbf)", align-items: center;
cursor: "pointer", justify-content: center;
fontSize: "14px",
display: "flex", &:hover {
alignItems: "center", color: ${token.colorWarning};
justifyContent: "center", }
}, `,
}; pinButtonPinned: css`
color: ${token.colorWarning};
`,
}));
export interface AppListItemProps { export interface AppListItemProps {
app: AppDetail; app: AppDetail;
@@ -88,19 +96,6 @@ export interface AppListItemProps {
onItemClick: (app: AppDetail) => void; onItemClick: (app: AppDetail) => void;
onPinToggle: (e: React.MouseEvent, appId: string) => void; onPinToggle: (e: React.MouseEvent, appId: string) => void;
t: (key: string) => string; t: (key: string) => string;
styles: {
listItemMotion: string;
listItemActive: string;
listItemPinned: string;
appInfoWrapper: string;
iconWrapper: string;
pinOverlay: string;
pinOverlayVisible: string;
pinButton: string;
pinButtonPinned: string;
appName: string;
appId: string;
};
} }
const AppListItem: React.FC<AppListItemProps> = ({ const AppListItem: React.FC<AppListItemProps> = ({
@@ -109,9 +104,9 @@ const AppListItem: React.FC<AppListItemProps> = ({
isPinned, isPinned,
onItemClick, onItemClick,
onPinToggle, onPinToggle,
styles,
t, t,
}) => { }) => {
const { styles } = useStyles();
const [isHovered, setIsHovered] = React.useState(false); const [isHovered, setIsHovered] = React.useState(false);
// Pin overlay is visible when: // Pin overlay is visible when:
@@ -155,9 +150,7 @@ const AppListItem: React.FC<AppListItemProps> = ({
<Tooltip title={app.name}> <Tooltip title={app.name}>
<span className={styles.appName}>{app.name}</span> <span className={styles.appName}>{app.name}</span>
</Tooltip> </Tooltip>
<Text code className={styles.appId}> <Tag>ID: {app.appId}</Tag>
ID: {app.appId}
</Text>
</div> </div>
</motion.div> </motion.div>
); );

View File

@@ -98,6 +98,7 @@ export const useAppStore = create<AppState>()(
partialize: (state) => ({ partialize: (state) => ({
apps: state.apps, apps: state.apps,
loadedAt: state.loadedAt, loadedAt: state.loadedAt,
selectedAppId: state.selectedAppId,
}), }),
}, },
), ),

View File

@@ -7,5 +7,6 @@ export { useDomainStore } from "./domainStore";
export { useAppStore } from "./appStore"; export { useAppStore } from "./appStore";
export { useDeployStore } from "./deployStore"; export { useDeployStore } from "./deployStore";
export { useVersionStore } from "./versionStore"; export { useVersionStore } from "./versionStore";
export { useUIStore } from "./uiStore"; export { useUIStore } from "./uiStore";
export { useSessionStore } from "./sessionStore";
export type { ViewMode, SelectedFile } from "./sessionStore";

View File

@@ -0,0 +1,54 @@
/**
* Session Store
* Manages temporary session state that should persist across app restarts
* Stored in localStorage - no need for file-based persistence
*/
import { create } from "zustand";
import { persist } from "zustand/middleware";
export type ViewMode = "list" | "code";
export interface SelectedFile {
type: "js" | "css";
fileKey: string;
name: string;
}
interface SessionState {
// View state
viewMode: ViewMode;
selectedFile: SelectedFile | null;
// Actions
setViewMode: (mode: ViewMode) => void;
setSelectedFile: (file: SelectedFile | null) => void;
resetViewState: () => void;
}
const initialState = {
viewMode: "list" as ViewMode,
selectedFile: null as SelectedFile | null,
};
export const useSessionStore = create<SessionState>()(
persist(
(set) => ({
...initialState,
setViewMode: (viewMode) => set({ viewMode }),
setSelectedFile: (selectedFile) => set({ selectedFile }),
resetViewState: () => set(initialState),
}),
{
name: "session-storage",
// Only persist view state, not transient data
partialize: (state) => ({
viewMode: state.viewMode,
selectedFile: state.selectedFile,
}),
},
),
);