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

View File

@@ -59,34 +59,6 @@ const useStyles = createStyles(({ token, css }) => ({
align-items: center;
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`
font-family: monospace;
font-size: 12px;
@@ -105,54 +77,6 @@ const useStyles = createStyles(({ token, css }) => ({
color: ${token.colorTextSecondary};
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 = () => {
@@ -374,7 +298,6 @@ const AppList: React.FC = () => {
isPinned={currentPinnedApps.includes(app.appId)}
onItemClick={handleItemClick}
onPinToggle={handlePinToggle}
styles={styles}
t={t}
/>
))

View File

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

View File

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

View File

@@ -7,5 +7,6 @@ export { useDomainStore } from "./domainStore";
export { useAppStore } from "./appStore";
export { useDeployStore } from "./deployStore";
export { useVersionStore } from "./versionStore";
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,
}),
},
),
);