1+ {% extends "scanpipe/base.html" %}
2+ {% load static humanize %}
3+ {% block title %}ScanCode.io: {{ project.name }} - Resource Tree{% endblock %}
4+
5+ {% block extrahead %}
6+ < style >
7+ .is-current {
8+ background : rgba (128 , 128 , 128 , 0.10 );
9+ border-radius : 6px ;
10+ }
11+ .chevron {
12+ transition : transform 0.2s ease;
13+ display : inline-block;
14+ }
15+ .chevron .rotated {
16+ transform : rotate (90deg );
17+ }
18+
19+ .resizable-container {
20+ display : flex;
21+ height : calc (100vh - 140px );
22+ margin : 0 ;
23+ }
24+
25+ .left-pane {
26+ min-width : 0 ;
27+ max-width : 100% ;
28+ border-right : 1px solid # ccc ;
29+ overflow-y : auto;
30+ overflow-x : hidden;
31+ flex-basis : 25% ;
32+ transition : opacity 0.2s ease;
33+ }
34+
35+ .left-pane .collapsed {
36+ opacity : 0 ;
37+ pointer-events : none;
38+ }
39+
40+ .resizer {
41+ width : 5px ;
42+ background : # ddd ;
43+ cursor : col-resize;
44+ position : relative;
45+ flex-shrink : 0 ;
46+ }
47+
48+ .resizer : hover {
49+ background : # bbb ;
50+ }
51+
52+ .resizer ::before {
53+ content : '' ;
54+ position : absolute;
55+ top : 50% ;
56+ left : 1px ;
57+ transform : translateY (-50% );
58+ width : 3px ;
59+ height : 30px ;
60+ background : # 999 ;
61+ border-radius : 2px ;
62+ }
63+
64+ .right-pane {
65+ flex : 1 ;
66+ overflow-y : auto;
67+ overflow-x : hidden;
68+ min-width : 0 ;
69+ transition : opacity 0.2s ease;
70+ }
71+
72+ .right-pane .collapsed {
73+ opacity : 0 ;
74+ pointer-events : none;
75+ }
76+ </ style >
77+ {% endblock %}
78+
79+ {% block content %}
80+ < div id ="content-header " class ="container is-max-widescreen mb-3 ">
81+ {% include 'scanpipe/includes/navbar_header.html' %}
82+ < section class ="mx-5 ">
83+ < div class ="is-flex is-justify-content-space-between ">
84+ {% include 'scanpipe/includes/breadcrumb.html' with linked_project=True current="Resource Tree" %}
85+ </ div >
86+ </ section >
87+ </ div >
88+
89+ < div class ="resizable-container ">
90+ < div class ="left-pane " id ="left-pane ">
91+ < div id ="resource-tree " class ="p-4 ">
92+ {% include "scanpipe/panels/codebase_tree_panel.html" with children=children path=path %}
93+ </ div >
94+ </ div >
95+ < div class ="resizer " id ="resizer "> </ div >
96+ < div class ="right-pane " id ="right-pane ">
97+ < div class ="p-4 ">
98+ {% include "scanpipe/panels/resource_table_panel.html" %}
99+ </ div >
100+ </ div >
101+ </ div >
102+ {% endblock %}
103+
104+ {% block scripts %}
105+ < script >
106+ // Tree functionality
107+ document . addEventListener ( "click" , async function ( e ) {
108+ const chevron = e . target . closest ( "[data-chevron]" ) ;
109+ if ( chevron ) {
110+ const folderNode = chevron . closest ( "[data-folder]" ) ;
111+ const targetId = folderNode . dataset . target ;
112+ const url = folderNode . dataset . url ;
113+ const target = document . getElementById ( "dir-" + targetId ) ;
114+
115+ if ( target . dataset . loaded === "true" ) {
116+ target . classList . toggle ( "is-hidden" ) ;
117+ } else {
118+ target . classList . remove ( "is-hidden" ) ;
119+ const response = await fetch ( url + "&tree_panel=true" ) ;
120+ target . innerHTML = await response . text ( ) ;
121+ target . dataset . loaded = "true" ;
122+ htmx . process ( target ) ;
123+ }
124+
125+ chevron . classList . toggle ( "rotated" ) ;
126+ e . stopPropagation ( ) ;
127+ return ;
128+ }
129+
130+ const folderMeta = e . target . closest ( ".folder-meta" ) ;
131+ if ( folderMeta ) {
132+ const folderNode = folderMeta . closest ( "[data-folder]" ) ;
133+ if ( folderNode && folderNode . dataset . target ) {
134+ document . querySelectorAll ( '.tree-node.is-current, .is-file.is-current' ) . forEach ( el => el . classList . remove ( 'is-current' ) ) ;
135+ folderNode . classList . add ( 'is-current' ) ;
136+ const chevron = folderNode . querySelector ( "[data-chevron]" ) ;
137+ const target = document . getElementById ( "dir-" + folderNode . dataset . target ) ;
138+
139+ if ( target . classList . contains ( "is-hidden" ) ) {
140+ target . classList . remove ( "is-hidden" ) ;
141+ chevron . classList . add ( "rotated" ) ;
142+ if ( target . dataset . loaded !== "true" ) {
143+ const response = await fetch ( folderNode . dataset . url + "&tree_panel=true" ) ;
144+ target . innerHTML = await response . text ( ) ;
145+ target . dataset . loaded = "true" ;
146+ htmx . process ( target ) ;
147+ }
148+ }
149+ }
150+ }
151+
152+ const fileNode = e . target . closest ( ".is-file[data-file]" ) ;
153+ if ( fileNode ) {
154+ document . querySelectorAll ( '.tree-node.is-current, .is-file.is-current' ) . forEach ( el => el . classList . remove ( 'is-current' ) ) ;
155+ fileNode . classList . add ( 'is-current' ) ;
156+ }
157+
158+ const expandLink = e . target . closest ( ".expand-in-tree" ) ;
159+ if ( expandLink ) {
160+ e . preventDefault ( ) ;
161+ const path = expandLink . getAttribute ( "data-path" ) ;
162+ const leftPane = document . getElementById ( "left-pane" ) ;
163+ if ( ! leftPane ) return ;
164+ let node = leftPane . querySelector ( `[data-folder][data-path="${ path } "], .is-file[data-file][data-path="${ path } "]` ) ;
165+ if ( node ) {
166+ document . querySelectorAll ( '.tree-node.is-current, .is-file.is-current' ) . forEach ( el => el . classList . remove ( 'is-current' ) ) ;
167+ node . classList . add ( 'is-current' ) ;
168+ const chevron = node . querySelector ( "[data-chevron]" ) ;
169+ if ( chevron && ! chevron . classList . contains ( "rotated" ) ) chevron . click ( ) ;
170+ node . scrollIntoView ( { behavior : "smooth" , block : "center" } ) ;
171+ }
172+ }
173+ } ) ;
174+
175+ document . addEventListener ( "DOMContentLoaded" , function ( ) {
176+ const resizer = document . getElementById ( 'resizer' ) ;
177+ const leftPane = document . getElementById ( 'left-pane' ) ;
178+ const rightPane = document . getElementById ( 'right-pane' ) ;
179+ let isResizing = false ;
180+
181+ resizer . addEventListener ( 'mousedown' , function ( e ) {
182+ isResizing = true ;
183+ document . body . style . cursor = 'col-resize' ;
184+ document . body . style . userSelect = 'none' ;
185+ e . preventDefault ( ) ;
186+ } ) ;
187+
188+ document . addEventListener ( 'mousemove' , function ( e ) {
189+ if ( ! isResizing ) return ;
190+
191+ const container = document . querySelector ( '.resizable-container' ) ;
192+ const containerRect = container . getBoundingClientRect ( ) ;
193+ let newLeftWidth = e . clientX - containerRect . left ;
194+ const containerWidth = containerRect . width ;
195+ const resizerWidth = 5 ;
196+ const minLeftWidth = 200 ;
197+ if ( newLeftWidth < minLeftWidth ) newLeftWidth = minLeftWidth ;
198+ if ( newLeftWidth > containerWidth - resizerWidth ) newLeftWidth = containerWidth - resizerWidth ;
199+
200+ const leftPercent = ( newLeftWidth / containerWidth ) * 100 ;
201+ const rightPercent = ( ( containerWidth - newLeftWidth - resizerWidth ) / containerWidth ) * 100 ;
202+
203+ leftPane . style . flexBasis = leftPercent + '%' ;
204+ rightPane . style . flexBasis = rightPercent + '%' ;
205+ } ) ;
206+
207+ document . addEventListener ( 'mouseup' , function ( ) {
208+ if ( isResizing ) {
209+ isResizing = false ;
210+ document . body . style . cursor = '' ;
211+ document . body . style . userSelect = '' ;
212+ }
213+ } ) ;
214+ } ) ;
215+ </ script >
216+ {% endblock %}
0 commit comments