nolo-cli 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +2 -0
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +92 -0
- package/agent-runtime/types.ts +42 -0
- package/agentRunCommand.ts +74 -1
- package/agentRuntimeCommands.ts +17 -89
- package/ai/agent/streamAgentChatTurn.ts +104 -20
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +56 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +64 -0
- package/ai/llm/kimi.ts +1 -1
- package/ai/llm/providers.ts +3 -0
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/skills/skillDocProtocol.ts +95 -3
- package/ai/taskRun/taskRunProtocol.ts +1 -0
- package/ai/tools/agent/agentTools.ts +17 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/modelUsageTools.ts +5 -0
- package/client/agentRun.test.ts +257 -7
- package/client/agentRun.ts +133 -34
- package/client/localRuntimeAdapter.test.ts +2 -0
- package/client/localRuntimeAdapter.ts +15 -2
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/machineCommands.ts +400 -45
- package/package.json +4 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/tableCommands.ts +42 -5
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
// 文件路径: render/page/server/createPage.ts
|
|
2
|
+
// 后端版本的 createPage,直接操作数据库,不依赖 Redux
|
|
3
|
+
|
|
4
|
+
import { format } from "date-fns";
|
|
5
|
+
import { createPageKey } from "database/keys";
|
|
6
|
+
import { DataType } from "create/types";
|
|
7
|
+
import { markdownToSlate } from "create/editor/transforms/markdownToSlate";
|
|
8
|
+
import serverDb, { ensureServerDbOpen } from "database/server/db";
|
|
9
|
+
import type { PageData } from "../types";
|
|
10
|
+
import {
|
|
11
|
+
createEmptyParagraph,
|
|
12
|
+
splitSlateTitleAndBody,
|
|
13
|
+
type EditorContent,
|
|
14
|
+
} from "create/editor/utils/slateUtils";
|
|
15
|
+
import { slateToRenderMarkdown } from "create/editor/transforms/slateToRenderMarkdown";
|
|
16
|
+
import { parseSkillDocProtocol } from "ai/skills/skillDocProtocol";
|
|
17
|
+
|
|
18
|
+
export interface CreatePageServerArgs {
|
|
19
|
+
userId: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
content?: string; // Markdown 格式内容
|
|
22
|
+
slateData?: EditorContent; // 或者直接传 Slate 数据
|
|
23
|
+
spaceId?: string;
|
|
24
|
+
categoryId?: string;
|
|
25
|
+
tags?: string[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CreatePageServerResult {
|
|
29
|
+
success: boolean;
|
|
30
|
+
dbKey: string;
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
spaceId: string | null;
|
|
34
|
+
categoryId?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 归一化 / 过滤 categoryId
|
|
39
|
+
*/
|
|
40
|
+
const normalizeCategoryId = (raw?: string): string | undefined => {
|
|
41
|
+
const trimmed = raw?.trim();
|
|
42
|
+
if (!trimmed) return undefined;
|
|
43
|
+
|
|
44
|
+
// 过滤掉包含非 ASCII 字符的情况
|
|
45
|
+
const asciiNoSpace = /^[\x20-\x7E]+$/;
|
|
46
|
+
if (!asciiNoSpace.test(trimmed)) return undefined;
|
|
47
|
+
|
|
48
|
+
// 简单长度下限
|
|
49
|
+
if (trimmed.length < 8) return undefined;
|
|
50
|
+
|
|
51
|
+
return trimmed;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 后端创建页面
|
|
56
|
+
* 直接写入 LevelDB,不依赖 Redux
|
|
57
|
+
*/
|
|
58
|
+
export async function createPageServer(
|
|
59
|
+
args: CreatePageServerArgs
|
|
60
|
+
): Promise<CreatePageServerResult> {
|
|
61
|
+
const { userId, title: initialTitle, content, slateData, spaceId, categoryId, tags } = args;
|
|
62
|
+
|
|
63
|
+
if (!userId) {
|
|
64
|
+
throw new Error("userId is required");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 确保数据库已打开
|
|
68
|
+
await ensureServerDbOpen();
|
|
69
|
+
|
|
70
|
+
// 生成 key
|
|
71
|
+
const { dbKey, id } = createPageKey.create(userId);
|
|
72
|
+
|
|
73
|
+
// 默认标题
|
|
74
|
+
const now = new Date();
|
|
75
|
+
const dateStr = format(now, "yyyy-MM-dd HH:mm");
|
|
76
|
+
let title = initialTitle?.trim() || `${dateStr} 的笔记`;
|
|
77
|
+
let pageMeta: PageData["meta"] | undefined;
|
|
78
|
+
|
|
79
|
+
// 处理内容
|
|
80
|
+
let initialSlateData: EditorContent;
|
|
81
|
+
|
|
82
|
+
if (slateData) {
|
|
83
|
+
// 直接使用传入的 Slate 数据
|
|
84
|
+
initialSlateData = slateData;
|
|
85
|
+
} else if (content) {
|
|
86
|
+
const parsedProtocol = parseSkillDocProtocol(content);
|
|
87
|
+
const normalizedContent = parsedProtocol.content;
|
|
88
|
+
pageMeta = parsedProtocol.meta;
|
|
89
|
+
// 将 Markdown 转换为 Slate
|
|
90
|
+
try {
|
|
91
|
+
const parsed = markdownToSlate(normalizedContent);
|
|
92
|
+
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
93
|
+
const split = splitSlateTitleAndBody(parsed, initialTitle);
|
|
94
|
+
title = initialTitle?.trim() || split.title || title;
|
|
95
|
+
initialSlateData = split.body;
|
|
96
|
+
} else {
|
|
97
|
+
initialSlateData = [
|
|
98
|
+
{ type: "paragraph", children: [{ text: normalizedContent }] },
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {
|
|
102
|
+
console.error("[createPageServer] markdownToSlate failed:", e);
|
|
103
|
+
initialSlateData = [{ type: "paragraph", children: [{ text: normalizedContent }] }];
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// 空白页面
|
|
107
|
+
initialSlateData = [createEmptyParagraph()];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// 归一化 categoryId
|
|
111
|
+
const safeCategoryId = normalizeCategoryId(categoryId);
|
|
112
|
+
|
|
113
|
+
// 构造 PageData
|
|
114
|
+
const pageData: PageData = {
|
|
115
|
+
dbKey,
|
|
116
|
+
id,
|
|
117
|
+
type: DataType.DOC,
|
|
118
|
+
title,
|
|
119
|
+
spaceId: spaceId || null,
|
|
120
|
+
slateData: initialSlateData,
|
|
121
|
+
// `content` 是只读展示缓存,不是文档真源。
|
|
122
|
+
content:
|
|
123
|
+
typeof content === "string"
|
|
124
|
+
? parseSkillDocProtocol(content, pageMeta).content ||
|
|
125
|
+
slateToRenderMarkdown(initialSlateData)
|
|
126
|
+
: slateToRenderMarkdown(initialSlateData),
|
|
127
|
+
tags,
|
|
128
|
+
created: now.toISOString(),
|
|
129
|
+
...(pageMeta ? { meta: pageMeta } : {}),
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// 写入数据库
|
|
133
|
+
await serverDb.put(dbKey, pageData);
|
|
134
|
+
|
|
135
|
+
// 如果有 spaceId,需要更新 Space 的 contents
|
|
136
|
+
// 注意:这里简化处理,只更新 Space 的 contents 索引
|
|
137
|
+
if (spaceId) {
|
|
138
|
+
try {
|
|
139
|
+
const spaceKey = `space-${spaceId}`;
|
|
140
|
+
const spaceData = await serverDb.get(spaceKey).catch(() => null);
|
|
141
|
+
|
|
142
|
+
if (spaceData && typeof spaceData === "object") {
|
|
143
|
+
const contents = spaceData.contents || {};
|
|
144
|
+
contents[dbKey] = {
|
|
145
|
+
title,
|
|
146
|
+
type: DataType.DOC,
|
|
147
|
+
contentKey: dbKey,
|
|
148
|
+
categoryId: safeCategoryId,
|
|
149
|
+
pinned: false,
|
|
150
|
+
createdAt: Date.now(),
|
|
151
|
+
updatedAt: Date.now(),
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
await serverDb.put(spaceKey, {
|
|
155
|
+
...spaceData,
|
|
156
|
+
contents,
|
|
157
|
+
updatedAt: Date.now(),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
} catch (e) {
|
|
161
|
+
// Space 更新失败不影响页面创建
|
|
162
|
+
console.warn("[createPageServer] Failed to update space contents:", e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
success: true,
|
|
168
|
+
dbKey,
|
|
169
|
+
id,
|
|
170
|
+
title,
|
|
171
|
+
spaceId: spaceId || null,
|
|
172
|
+
categoryId: safeCategoryId,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
// 文件路径: render/page/server/handleCreatePage.ts
|
|
2
|
+
// HTTP handler for createPage (Bun serve 兼容)
|
|
3
|
+
|
|
4
|
+
import { createPageServer } from "./createPage";
|
|
5
|
+
import { handleToken } from "auth/server/token";
|
|
6
|
+
import { logger } from "auth/server/shared";
|
|
7
|
+
|
|
8
|
+
const CORS_HEADERS = {
|
|
9
|
+
"Access-Control-Allow-Origin": "*",
|
|
10
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
11
|
+
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
12
|
+
"Content-Type": "application/json",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* POST /api/page/create
|
|
17
|
+
*
|
|
18
|
+
* Body:
|
|
19
|
+
* {
|
|
20
|
+
* title?: string,
|
|
21
|
+
* content?: string, // Markdown 格式
|
|
22
|
+
* slateData?: any, // 或直接传 Slate 数据
|
|
23
|
+
* spaceId?: string,
|
|
24
|
+
* categoryId?: string,
|
|
25
|
+
* tags?: string[]
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export const handleCreatePage = async (req: Request): Promise<Response> => {
|
|
29
|
+
try {
|
|
30
|
+
// 鉴权:创建一个简化的 req 对象用于 handleToken
|
|
31
|
+
const authReq: any = {
|
|
32
|
+
headers: {
|
|
33
|
+
get: (name: string) => req.headers.get(name),
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const authRes: any = {}; // handleToken 可能不需要 res
|
|
37
|
+
|
|
38
|
+
const user = await handleToken(authReq, authRes);
|
|
39
|
+
const userId = user?.userId;
|
|
40
|
+
|
|
41
|
+
if (!userId) {
|
|
42
|
+
return new Response(
|
|
43
|
+
JSON.stringify({
|
|
44
|
+
success: false,
|
|
45
|
+
message: "Unauthorized: userId not found",
|
|
46
|
+
}),
|
|
47
|
+
{ status: 401, headers: CORS_HEADERS }
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 解析请求体
|
|
52
|
+
const body: any = await req.json().catch(() => ({}));
|
|
53
|
+
const { title, content, slateData, spaceId, categoryId, tags } = body;
|
|
54
|
+
|
|
55
|
+
const result = await createPageServer({
|
|
56
|
+
userId,
|
|
57
|
+
title,
|
|
58
|
+
content,
|
|
59
|
+
slateData,
|
|
60
|
+
spaceId,
|
|
61
|
+
categoryId,
|
|
62
|
+
tags,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
logger.info({
|
|
66
|
+
event: "page_created",
|
|
67
|
+
userId,
|
|
68
|
+
dbKey: result.dbKey,
|
|
69
|
+
title: result.title,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
return new Response(JSON.stringify(result), {
|
|
73
|
+
status: 200,
|
|
74
|
+
headers: CORS_HEADERS,
|
|
75
|
+
});
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
logger.error({
|
|
78
|
+
event: "create_page_failed",
|
|
79
|
+
error: error.message,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
return new Response(
|
|
83
|
+
JSON.stringify({
|
|
84
|
+
success: false,
|
|
85
|
+
message: error.message || "Failed to create page",
|
|
86
|
+
}),
|
|
87
|
+
{ status: 500, headers: CORS_HEADERS }
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DataType } from "create/types"; // 确认路径
|
|
2
|
+
import type { PageSkillMetadata } from "ai/skills/skillDocProtocol";
|
|
3
|
+
|
|
4
|
+
export interface PageData {
|
|
5
|
+
id: string; // 通常与 pageKey 中的 id 部分相同
|
|
6
|
+
dbKey: string; // pageKey (例如 'page-userid-ulid')
|
|
7
|
+
type: DataType.DOC | DataType.FILE | DataType.IMAGE;
|
|
8
|
+
title: string;
|
|
9
|
+
content?: string | null; // 只读展示 markdown 缓存 / legacy bridge,不是真源
|
|
10
|
+
slateData?: any | null; // 文档真源
|
|
11
|
+
spaceId: string | null; // 页面所属的 spaceId, null 表示不在任何空间
|
|
12
|
+
tags?: string[]; // 页面的标签
|
|
13
|
+
created: string; // ISO 格式创建时间
|
|
14
|
+
updated_at?: string; // ISO 格式更新时间 (可选)
|
|
15
|
+
tools?: string[]; // 关联的工具列表
|
|
16
|
+
meta?: PageSkillMetadata;
|
|
17
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { useEffect, RefObject } from "react";
|
|
2
|
+
|
|
3
|
+
interface KeyboardSaveProps {
|
|
4
|
+
isReadOnly: boolean;
|
|
5
|
+
editorFocusedRef: RefObject<boolean>;
|
|
6
|
+
saveTimeoutRef: RefObject<ReturnType<typeof setTimeout> | null>;
|
|
7
|
+
onSave: () => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 处理键盘保存快捷键的自定义Hook
|
|
12
|
+
*/
|
|
13
|
+
const useKeyboardSave = ({
|
|
14
|
+
isReadOnly,
|
|
15
|
+
editorFocusedRef,
|
|
16
|
+
saveTimeoutRef,
|
|
17
|
+
onSave,
|
|
18
|
+
}: KeyboardSaveProps): void => {
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
21
|
+
// 只处理编辑模式
|
|
22
|
+
if (isReadOnly) return;
|
|
23
|
+
|
|
24
|
+
// Ctrl+S / Cmd+S 保存快捷键
|
|
25
|
+
if ((e.ctrlKey || e.metaKey) && e.key === "s") {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
|
|
28
|
+
// 取消计划中的自动保存
|
|
29
|
+
if (saveTimeoutRef.current) {
|
|
30
|
+
clearTimeout(saveTimeoutRef.current);
|
|
31
|
+
saveTimeoutRef.current = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 立即保存
|
|
35
|
+
onSave();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// 仅监听编辑器焦点或全局键盘事件
|
|
40
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
41
|
+
|
|
42
|
+
return () => {
|
|
43
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
44
|
+
};
|
|
45
|
+
}, [isReadOnly, onSave, saveTimeoutRef, editorFocusedRef]);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export default useKeyboardSave;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const zIndex = {
|
|
2
|
+
// --- 1. 基础布局层 ---
|
|
3
|
+
topbar: 100, // 必须高于页面内 sticky 元素(如 content-header z-index:20)
|
|
4
|
+
sidebarBackdrop: 5,
|
|
5
|
+
sidebar: 6,
|
|
6
|
+
// --- 3. 浮动组件层 ---
|
|
7
|
+
// 侧边栏下拉需覆盖 sticky 分类头/顶部控件,避免出现“被挡住”的回归。
|
|
8
|
+
dropdown: 1000,
|
|
9
|
+
|
|
10
|
+
// --- 4. 模态框与全局菜单层 ---
|
|
11
|
+
modalBackdrop: 1010,
|
|
12
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// WeatherIconStyles.js
|
|
2
|
+
|
|
3
|
+
// 用于指示风向和浪向的箭头图标的样式
|
|
4
|
+
export const iconContainerStyle = {
|
|
5
|
+
display: "inline-flex",
|
|
6
|
+
alignItems: "center",
|
|
7
|
+
justifyContent: "center",
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
// 图标外部容器的共通样式
|
|
11
|
+
export const outerDivStyle = {
|
|
12
|
+
color: "#6b7280",
|
|
13
|
+
textAlign: "center",
|
|
14
|
+
height: "1.5rem", // 相当于24px
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// 根据需要可以在这里添加更多样式...
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const modes = [
|
|
2
|
+
{ value: "sg", title: "自动" },
|
|
3
|
+
{ value: "noaa", title: "GFS" },
|
|
4
|
+
{ value: "meteo", title: "Meteo" },
|
|
5
|
+
{ value: "icon", title: "ICON" },
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
export const intervals = [
|
|
9
|
+
{ value: 3, title: "3H" },
|
|
10
|
+
{ value: 1, title: "1H" },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export const defaultDisplayConfig = [
|
|
14
|
+
{
|
|
15
|
+
key: "time",
|
|
16
|
+
enabled: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "swellDirection",
|
|
20
|
+
enabled: true,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
key: "windDirection",
|
|
24
|
+
enabled: true,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: "windSpeed",
|
|
28
|
+
enabled: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
key: "swellHeight",
|
|
32
|
+
enabled: true,
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: "swellPeriod",
|
|
36
|
+
enabled: true,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "gust",
|
|
40
|
+
enabled: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
key: "airTemperature",
|
|
44
|
+
enabled: true,
|
|
45
|
+
},
|
|
46
|
+
];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const CELL_HEIGHT = 26;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// ToggleButtonStyles.ts
|
|
2
|
+
export const baseButton: string =
|
|
3
|
+
"flex-1 py-1 bg-white border border-gray-300 text-gray-800 hover:bg-gray-100 ";
|
|
4
|
+
export const activeButton: string =
|
|
5
|
+
"flex-1 py-1 bg-[#0066FF] text-white border border-[#0066FF] hover:bg-[#0056E0] shadow-lg";
|
|
6
|
+
export const baseText: string =
|
|
7
|
+
"text-center text-xs font-semibold tracking-wide leading-relaxed text-gray-700";
|
|
8
|
+
export const activeText: string = "text-white";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { format, parseISO } from "date-fns";
|
|
2
|
+
import { utcToZonedTime } from "date-fns-tz";
|
|
3
|
+
import type { HourlyWeather } from "integrations/weather";
|
|
4
|
+
|
|
5
|
+
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
6
|
+
export const groupHourlyWeatherByLocalDate = (hours: HourlyWeather[]) => {
|
|
7
|
+
return hours.reduce((acc: Record<string, any[]>, hour) => {
|
|
8
|
+
const date = parseISO(hour.time); // 转换成 JS 日期对象
|
|
9
|
+
const zonedDate = utcToZonedTime(date, timeZone); // 转换到当地时区时间
|
|
10
|
+
const localDate = format(zonedDate, "yyyy-MM-dd", { timeZone }); // 按当地时区格式化日期
|
|
11
|
+
|
|
12
|
+
if (!acc[localDate]) {
|
|
13
|
+
acc[localDate] = [];
|
|
14
|
+
}
|
|
15
|
+
acc[localDate].push(hour);
|
|
16
|
+
|
|
17
|
+
return acc;
|
|
18
|
+
}, {});
|
|
19
|
+
};
|
|
20
|
+
export const extractTimeAndSwellHeight = (
|
|
21
|
+
hourlyWeatherArray: HourlyWeather[],
|
|
22
|
+
) => {
|
|
23
|
+
const times = [];
|
|
24
|
+
const SwellHeights = [];
|
|
25
|
+
|
|
26
|
+
for (const weather of hourlyWeatherArray) {
|
|
27
|
+
times.push(weather.time);
|
|
28
|
+
SwellHeights.push(weather.waveHeight?.sg || 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return { x: times, y: SwellHeights };
|
|
32
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import COLORS from "./color";
|
|
2
|
+
|
|
3
|
+
export const calculateAverage = (
|
|
4
|
+
hours: Array<any>,
|
|
5
|
+
field: string,
|
|
6
|
+
mode: string,
|
|
7
|
+
): string => {
|
|
8
|
+
let tempSum = 0;
|
|
9
|
+
let tempCount = 0;
|
|
10
|
+
|
|
11
|
+
for (const hour of hours) {
|
|
12
|
+
const tempValue = hour[field]?.[mode];
|
|
13
|
+
if (tempValue !== undefined) {
|
|
14
|
+
tempSum += parseFloat(tempValue);
|
|
15
|
+
tempCount++;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return tempCount > 0 ? (tempSum / tempCount).toFixed(1) : "-";
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const getQualityColor = (value: number, type: string): string => {
|
|
23
|
+
let quality = "average"; // 默认质量为'average'
|
|
24
|
+
let backgroundColor = COLORS.quality.average; // 默认背景颜色为淡黄色(average)
|
|
25
|
+
|
|
26
|
+
// 根据不同数据类型设置不同条件的阈值判断
|
|
27
|
+
switch (type) {
|
|
28
|
+
case "windSpeed":
|
|
29
|
+
if (value < 5) quality = "good";
|
|
30
|
+
else if (value > 8) quality = "bad";
|
|
31
|
+
break;
|
|
32
|
+
case "swellHeight":
|
|
33
|
+
if (value > 1.5) quality = "good";
|
|
34
|
+
else if (value < 0.5) quality = "bad";
|
|
35
|
+
break;
|
|
36
|
+
case "swellPeriod":
|
|
37
|
+
if (value > 12) quality = "good";
|
|
38
|
+
else if (value < 8) quality = "bad";
|
|
39
|
+
break;
|
|
40
|
+
default:
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 根据质量结果更新背景颜色
|
|
45
|
+
if (quality === "good")
|
|
46
|
+
backgroundColor = COLORS.quality.good; // Light green
|
|
47
|
+
else if (quality === "bad") backgroundColor = COLORS.quality.bad; // Light red
|
|
48
|
+
|
|
49
|
+
return backgroundColor;
|
|
50
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export const TABLE_ACTIVITY_COLUMNS = ["meta.latestActivityRef", "meta.activityRefs"] as const;
|
|
2
|
+
|
|
3
|
+
export function includeTableActivityColumns(columns?: string[]): string[] | undefined {
|
|
4
|
+
if (!Array.isArray(columns)) return [...TABLE_ACTIVITY_COLUMNS];
|
|
5
|
+
return Array.from(new Set([...columns, ...TABLE_ACTIVITY_COLUMNS]));
|
|
6
|
+
}
|