add store and UI fix
This commit is contained in:
@@ -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} />}>
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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";
|
||||||
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