Skip to content

Commit 9e04283

Browse files
authored
Merge pull request #151 from os-checker/feat/replace-repo-page-by-FileTree2
feat(user/repo): redirect to /file-tree?user...repo...
2 parents b251fdf + 8060a52 commit 9e04283

File tree

4 files changed

+101
-365
lines changed

4 files changed

+101
-365
lines changed

os-checks/components/FileTree.vue

Lines changed: 90 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,22 @@
11
<script setup lang="ts">
2-
import type { FetchError } from 'ofetch';
2+
import { cloneDeep } from 'es-toolkit/compat';
33
import type { TreeNode } from 'primevue/treenode';
4-
import type { FileTree, Kinds } from '~/shared/file-tree';
5-
import { getEmpty } from '../shared/file-tree/utils';
4+
import type { FileTree } from '~/shared/file-tree';
5+
import { updateSelectedKey, type Get } from '~/shared/file-tree/utils';
66
7-
type Props = { fetch_path: (target: string) => string };
8-
const props = defineProps<Props>();
7+
type Props = { get: Get, count: number | null };
8+
const { get, count } = defineProps<Props>();
99
10-
highlightRust();
10+
const fullTabs = ref(cloneDeep(get.tabs));
11+
watch(() => get, g => fullTabs.value = cloneDeep(g.tabs));
1112
12-
const tabs = ref<CheckerResult[]>([]);
13-
const selectedTab = ref("");
14-
const fileTree = ref<FileTree>(getEmpty().fileTree);
13+
const filtered_fileTree = computed<FileTree>(() => get.fileTree);
1514
16-
const basic = useBasicStore();
17-
18-
basic.init_with_and_subscribe_to_current((target: string) => {
19-
const path = props.fetch_path(target);
20-
githubFetch<FileTree>({ path })
21-
.then((file_tree) => {
22-
// const file_tree: FileTree = JSON.parse(data as string);
23-
24-
// 首次打开页面加载数据后,从所有 packags 的原始输出填充到所有选项卡
25-
let kinds = {};
26-
for (const datum of file_tree.data) {
27-
for (const report of datum.raw_reports) {
28-
// for (const kind of Object.keys(report.kinds)) {
29-
// 对原始输出中的所有特殊符号转义,以后就不需要转义了
30-
// report.kinds[kind] = report.kinds[kind].map(domSanitize);
31-
// }
32-
mergeObjectsWithArrayConcat(kinds, report.kinds);
33-
}
34-
}
35-
tabs.value = checkerResult(kinds, file_tree.kinds_order);
36-
selectedTab.value = tabs.value[0]?.kind ?? "";
37-
fileTree.value = file_tree;
38-
}).catch((_: FetchError) => {
39-
// 不存在该文件:意味着该目标架构下的所有仓库没有检查出错误
40-
// 注意,由于使用 parseResponse,这个错误码并不为 404,而是 undefined,
41-
// 且错误原因为 SyntaxError: Unexpected non-whitespace character after JSON at position 3。
42-
// 这里 ofetch 没有正确处理错误(貌似也没人报告?),所以暂且认为出现任何网络或解析错误都视为无错误。
43-
// console.log(err, err.data, err.statusCode);
44-
45-
tabs.value = [{
46-
kind: "All good! 🥳", raw: ["该目标架构下的所有仓库没有检查出错误 🥳🥳🥳"],
47-
lang: "rust", severity: Severity.Info, disabled: false
48-
}];
49-
selectedTab.value = "All good! 🥳";
50-
fileTree.value = getEmpty().fileTree;
51-
52-
// tabs.value = [{
53-
// kind: "Not Exists!", raw: ["该目标架构下,无原始报告数据。"],
54-
// lang: "rust", severity: Severity.Danger, disabled: false
55-
// }];
56-
// selectedTab.value = "Not Exists!";
57-
// fileTree.value = { kinds_order: [], data: [] };
58-
});
59-
});
60-
61-
const nodes = ref<TreeNode[]>([]);
62-
watch(fileTree, (data) => {
63-
nodes.value = [];
15+
const nodes = computed<TreeNode[]>(() => {
16+
let nodes = [];
6417
6518
let key = 0;
66-
for (const datum of data.data) {
19+
for (const datum of filtered_fileTree.value.data) {
6720
let node: TreeNode = {
6821
key: (key++).toString(), label: `[${datum.count}] ${datum.repo} #${datum.pkg}`, children: [],
6922
};
@@ -82,132 +35,103 @@ watch(fileTree, (data) => {
8235
user: datum.user, repo: datum.repo, pkg: datum.pkg,
8336
total: datum.count, fmt: count_fmt, clippy_warn: count_clippy_warn, clippy_error: count_clippy_error
8437
};
85-
nodes.value.push(node);
38+
nodes.push(node);
8639
}
40+
return nodes;
8741
});
8842
89-
function mergeObjectsWithArrayConcat(result: Kinds, obj: Kinds) {
90-
for (const [key, value] of Object.entries(obj)) {
91-
if (result.hasOwnProperty(key)) {
92-
// 如果键已经存在,则合并数组
93-
result[key] = result[key].concat(value);
94-
} else {
95-
// 否则,添加新的键值对
96-
result[key] = value;
97-
}
98-
}
99-
}
100-
10143
const selectedKey = ref({});
102-
watch(selectedKey, (val) => {
103-
const key = Object.keys(val)[0];
104-
if (!key) { return; }
105-
const idx = parseInt(key);
106-
for (const node of nodes.value.slice().reverse()) {
107-
const nd = node.data;
108-
if (!(nd && nd.user && nd.repo && nd.pkg)) { return; }
109-
110-
// 查找是否点击了 package
111-
if (node.key === key) {
112-
// 更新 tabs 展示的数据
113-
const found_pkg = fileTree.value.data.find(datum => {
114-
return datum.user === nd.user && datum.repo === nd.repo && datum.pkg === nd.pkg;
115-
});
116-
let kinds = {};
117-
for (const report of found_pkg?.raw_reports ?? []) {
118-
mergeObjectsWithArrayConcat(kinds, report.kinds);
119-
}
120-
tabs.value = checkerResult(kinds, fileTree.value.kinds_order);
121-
return;
44+
watch(() => ({ key: selectedKey.value, n: nodes.value, ft: filtered_fileTree.value }),
45+
({ key, n, ft }) => {
46+
const val = updateSelectedKey(key, n, ft);
47+
if (val !== undefined) {
48+
get.tabs = val.results;
49+
get.selectedTab = val.selectedTab;
12250
} else {
123-
// 由于 key 是升序的,现在只要找第一个小于目标 key 的 package,那么这个文件就在那里
124-
if (idx > parseInt(node.key)) {
125-
for (const file of node.children ?? []) {
126-
if (file.key === key) {
127-
const filename = file.data;
128-
if (!filename) { return []; }
129-
const package_ = fileTree.value.data.find(datum => {
130-
return datum.user === nd.user && datum.repo === nd.repo && datum.pkg === nd.pkg;
131-
});
132-
const found_file = package_?.raw_reports.find(item => item.file === filename);
133-
if (found_file) {
134-
tabs.value = checkerResult(found_file.kinds, fileTree.value.kinds_order);
135-
return;
136-
}
137-
}
138-
}
139-
}
51+
// display full diagnostics if none is selected or something is not found
52+
get.tabs = cloneDeep(fullTabs.value);
14053
}
141-
}
142-
});
143-
144-
type CheckerResult = {
145-
kind: string,
146-
raw: string[],
147-
lang: string,
148-
severity: Severity,
149-
disabled: boolean, // 对于空数组,禁用选项卡
150-
};
54+
});
15155
152-
enum Severity {
153-
Danger = "danger",
154-
Warn = "warn",
155-
Info = "info",
156-
Disabled = "secondary",
56+
function resetSelectKey() {
57+
selectedKey.value = {};
58+
get.tabs = cloneDeep(fullTabs.value);
15759
}
15860
159-
// Kinds 可能不包含全部诊断类别,因此这里填充空数组,并按照顺序排列
160-
function checkerResult(kinds: Kinds, kinds_order: string[]): CheckerResult[] {
161-
let results = kinds_order.map<CheckerResult>(kind => {
162-
return { kind, raw: [], lang: "rust", severity: Severity.Disabled, disabled: true };
61+
// true means keeping file tree panel open (thus shows left arrow icon to indicate close)
62+
const displayFileTree = ref(true);
63+
const displayFileTreeIcon = computed<string>(() => displayFileTree.value ? "pi pi-angle-double-left" : "pi pi-angle-double-right");
64+
65+
// true means keeping filter panel open (thus shows up arrow icon to indicate close)
66+
const displayFilters = defineModel<boolean>("filters", { default: true });
67+
const displayFiltersIcon = computed<string>(() => displayFilters.value ? "pi pi-angle-double-up" : "pi pi-angle-double-down");
68+
69+
onMounted(() => {
70+
document.addEventListener("keydown", ({ code }: KeyboardEvent) => {
71+
if (code === "Space") displayFileTree.value = !displayFileTree.value;
72+
else if (code === "Escape") displayFilters.value = !displayFilters.value;
73+
else if (code === "ArrowLeft") displayFileTree.value = false;
74+
else if (code === "ArrowRight") displayFileTree.value = true;
75+
else if (code === "ArrowUp") displayFilters.value = false;
76+
else if (code === "ArrowDown") displayFilters.value = true;
16377
});
164-
for (const [kind, raw] of Object.entries(kinds)) {
165-
let lang = "rust";
166-
let severity = Severity.Info;
167-
switch (kind) {
168-
case "Cargo": severity = Severity.Danger; break;
169-
case "Clippy(Error)": severity = Severity.Danger; break;
170-
case "Lockbud(Probably)": severity = Severity.Danger; break;
171-
case "Clippy(Warn)": severity = Severity.Warn; break;
172-
case "Unformatted": lang = "diff"; break;
173-
default: ;
174-
}
175-
const pos = results.findIndex(r => r.kind === kind);
176-
if (pos !== -1) {
177-
// JSON 提供的诊断信息一定不是空数组
178-
results[pos] = { kind, raw, lang, severity, disabled: false };
179-
}
180-
}
181-
selectedTab.value = results.find(r => !r.disabled)?.kind ?? "";
182-
return results;
183-
}
78+
});
79+
80+
const { viewportHeight } = storeToRefs(useStyleStore());
81+
const heightCodePanel = computed(() => {
82+
const height = viewportHeight.value;
83+
// add more space to scroll codeblock panel to the bottom if filters exist
84+
const adjust = displayFilters.value ? 100 : 0;
85+
return `${height * 0.85 - adjust}px`;
86+
});
87+
88+
const lockURL = defineModel("lockURL", { default: false });
89+
const lockURLIcon = computed(() => lockURL.value ? "pi pi-lock" : "pi pi-lock-open");
18490
</script>
18591

18692
<template>
18793
<div class="fileViewPanel">
18894

189-
<div class="fileViewNavi">
190-
<ScrollPanel class="fileViewMenu">
191-
<PackageFileMenu style="padding-right: 0.8rem;" :nodes="nodes" :selectedKey="selectedKey"
192-
@update:selectedKey="selectedKey = $event" />
95+
<div class="fileViewNavi" v-if="displayFileTree">
96+
<div style="height: 3.2rem; display: flex; justify-content: space-between; align-items: center;">
97+
<div style="display: flex; justify-content: left; gap: 8px;">
98+
<div style="margin-left: 10px;">
99+
<Button class="btn" :icon="displayFileTreeIcon" severity="secondary" variant="text"
100+
@click="() => displayFileTree = !displayFileTree" />
101+
</div>
102+
<div>
103+
<Button class="btn" :icon="displayFiltersIcon" severity="secondary" variant="text"
104+
@click="() => displayFilters = !displayFilters" />
105+
</div>
106+
<div>
107+
<Button class="btn" :icon="lockURLIcon" severity="secondary" variant="text"
108+
@click="() => lockURL = !lockURL" />
109+
</div>
110+
</div>
111+
<div v-if="count" style="padding-right: 0.6rem;">
112+
<b style="margin-right: 6px;">Total Count:</b>
113+
<Button class="btn" severity="danger" @click="resetSelectKey"> {{ count }} </Button>
114+
</div>
115+
</div>
116+
117+
<ScrollPanel class="fileViewMenu" :style="{ height: heightCodePanel }">
118+
<PackageFileMenu :nodes="nodes" :selectedKey="selectedKey" @update:selectedKey="selectedKey = $event" />
193119
</ScrollPanel>
194120
</div>
195121

196122
<div class="fileViewResult">
197-
<Tabs :value="selectedTab" scrollable>
123+
<Tabs :value="get.selectedTab" scrollable>
198124
<TabList>
199-
<Tab v-for="tab in tabs" :value="tab.kind" :disabled="tab.disabled">
125+
<Tab v-for="tab in get.tabs" :value="tab.kind" :disabled="tab.disabled">
200126
{{ tab.kind }}
201127
<span class="tabBadge">
202128
<Badge :value="tab.raw.length" :severity="tab.severity" />
203129
</span>
204130
</Tab>
205131
</TabList>
206132
<TabPanels>
207-
<TabPanel v-for="tab in tabs" :value="tab.kind">
208-
<ScrollPanel class="fileViewScroll" :dt="{
209-
bar: { background: '{primary.color}' },
210-
}">
133+
<TabPanel v-for="tab in get.tabs" :value="tab.kind">
134+
<ScrollPanel :dt="{ bar: { background: '{primary.color}' } }" :style="{ height: heightCodePanel }">
211135
<CodeBlock :snippets="tab.raw" :lang="tab.lang" />
212136
</ScrollPanel>
213137
</TabPanel>
@@ -251,15 +175,18 @@ function checkerResult(kinds: Kinds, kinds_order: string[]): CheckerResult[] {
251175
.fileViewResult {
252176
flex: 1;
253177
overflow-x: auto;
254-
overflow-y: hidden;
255-
/* 控制代码块容器的 padding: 上、左、下、右 */
256-
--p-tabs-tabpanel-padding: 0.35rem 0.3rem 0 0;
178+
overflow-y: auto;
179+
padding: 0rem 0.5rem 0rem 1rem;
180+
/* 控制代码块容器的 padding: 上、右、下、左 */
181+
--p-tabs-tabpanel-padding: 0.35rem 0rem 0 0;
257182
/* 右边div占据剩余空间 */
258183
/* 可以省略flex-grow为1,因为默认值就是1 */
184+
185+
/* 选中标签页的底部块的高度 */
186+
--p-tabs-active-bar-height: 3.2px;
259187
}
260188
261-
.fileViewScroll {
262-
width: 100%;
263-
height: 86vh;
189+
.btn {
190+
height: 2.4rem;
264191
}
265192
</style>

0 commit comments

Comments
 (0)