11<script setup lang="ts">
2- import type { FetchError } from ' ofetch ' ;
2+ import { cloneDeep } from ' es-toolkit/compat ' ;
33import 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-
10143const 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