add store and UI fix
This commit is contained in:
@@ -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} />}>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
))
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -98,6 +98,7 @@ export const useAppStore = create<AppState>()(
|
||||
partialize: (state) => ({
|
||||
apps: state.apps,
|
||||
loadedAt: state.loadedAt,
|
||||
selectedAppId: state.selectedAppId,
|
||||
}),
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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";
|
||||
54
src/renderer/src/stores/sessionStore.ts
Normal file
54
src/renderer/src/stores/sessionStore.ts
Normal 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,
|
||||
}),
|
||||
},
|
||||
),
|
||||
);
|
||||
Reference in New Issue
Block a user