Skip to content

Commit 7c8c19b

Browse files
committed
Improving sidebar appearance in collapsed mode.
1 parent 04c4a55 commit 7c8c19b

File tree

10 files changed

+264
-80
lines changed

10 files changed

+264
-80
lines changed

bin/cssClasses.js

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Helper script that lists all CSS classes used in ReCodEx.
3+
*/
4+
5+
/* eslint no-console: "off" */
6+
import fs from 'node:fs';
7+
import fsp from 'node:fs/promises';
8+
import path from 'node:path';
9+
import { glob } from 'glob';
10+
import 'colors';
11+
12+
/**
13+
* Fix import path.
14+
* @param {string} path
15+
* @param {string} basePath to which the path is relative
16+
* @returns {string} fixed path
17+
*/
18+
function patchImportPath(path, basePath) {
19+
if (!path.startsWith('.')) {
20+
return path; // our paths (to be fixed) begins with . or ..
21+
}
22+
23+
const absPath = basePath + '/' + path;
24+
25+
if (fixMissingExtension && !fs.existsSync(absPath)) {
26+
const missingExt = '.js';
27+
if (
28+
!absPath.endsWith(missingExt) &&
29+
fs.existsSync(absPath + missingExt) &&
30+
!fs.lstatSync(absPath + missingExt).isDirectory()
31+
) {
32+
return path + missingExt;
33+
} else {
34+
console.log(`${path} ${basePath}`.red);
35+
}
36+
}
37+
38+
if (addIndex && fs.lstatSync(absPath).isDirectory()) {
39+
const indexJs = '/index.js';
40+
if (fs.existsSync(absPath + indexJs) && !fs.lstatSync(absPath + indexJs).isDirectory()) {
41+
return path + indexJs;
42+
} else {
43+
console.log(`${path} dir does not have 'index.js'`.red);
44+
}
45+
}
46+
47+
return path;
48+
}
49+
50+
function getClassNameContent(content, fromIndex) {
51+
let index = fromIndex;
52+
let level = 1;
53+
while (index < content.length) {
54+
if (content[index] === '{') {
55+
++level;
56+
} else if (content[index] === '}') {
57+
--level;
58+
}
59+
if (level === 0) {
60+
return content.substring(fromIndex, index);
61+
}
62+
++index;
63+
}
64+
return null;
65+
}
66+
67+
/**
68+
* Process a JS file and fix all import and direct export statements.
69+
* @param {string} file path to the file
70+
* @returns {Array} tuple [ imports/exports count, modified paths count ]
71+
*/
72+
async function processFile(file) {
73+
const content = await fsp.readFile(file, 'utf8');
74+
const res = [];
75+
76+
// regular string classes
77+
const regex = /className="([^"]+)"/g;
78+
let match;
79+
while ((match = regex.exec(content)) !== null) {
80+
match[1]
81+
.split(/\s+/)
82+
.filter(cls => cls)
83+
.forEach(cls => res.push([cls, `${file}:${match.index}`]));
84+
}
85+
86+
const regex2 = /className={/g;
87+
while ((match = regex2.exec(content)) !== null) {
88+
let classes = getClassNameContent(content, regex2.lastIndex);
89+
if (classes.startsWith('classnames(')) {
90+
// Try to extract as many names from classnames() dict keys
91+
const subrex = /'([^']+)':/g;
92+
let submatch;
93+
while ((submatch = subrex.exec(classes)) !== null) {
94+
if (/^[-_a-zA-Z0-9]+$/.test(submatch[1])) {
95+
res.push([submatch[1], `${file}:${match.index}`]);
96+
}
97+
}
98+
99+
const subrex2 = /\[`([^`]+)`\]:/g;
100+
while ((submatch = subrex2.exec(classes)) !== null) {
101+
const cls = submatch[1].replaceAll(/\$\{[^}]+\}/g, '*');
102+
if (/^[-_a-zA-Z0-9*]+$/.test(cls)) {
103+
res.push([cls, `${file}:${match.index}`]);
104+
}
105+
}
106+
} else if (classes.startsWith('`') && classes.endsWith('`')) {
107+
// handle `` strings, converting ${} into * as a wildcard replacement
108+
classes = classes.substring(1, classes.length - 1);
109+
classes = classes.replaceAll(/\$\{[^}]+\}/g, '*');
110+
classes
111+
.split(/\s+/)
112+
.filter(cls => cls && cls !== '*')
113+
.forEach(cls => res.push([cls, `${file}:${match.index}`]));
114+
} else {
115+
// handle all internal strings in '' as possible class names
116+
const subrex = /'\s*([^']+)\s*'/g;
117+
let submatch;
118+
while ((submatch = subrex.exec(classes)) !== null) {
119+
submatch[1]
120+
.split(/\s+/)
121+
.filter(cls => /^[-_a-zA-Z0-9]+$/.test(cls))
122+
.forEach(cls => res.push([cls, `${file}:${match.index}`]));
123+
}
124+
}
125+
}
126+
127+
return res;
128+
}
129+
130+
/**
131+
* Scan all JS files in given top-level directory.
132+
* @param {String} dir
133+
*/
134+
async function processDir(dir, res) {
135+
const searchPattern = `${dir}/**/*.js`;
136+
const files = await glob(searchPattern, { posix: true });
137+
const promises = {};
138+
139+
for (const file of files) {
140+
promises[file] = await processFile(file);
141+
}
142+
143+
for (const file in promises) {
144+
const classes = await promises[file];
145+
classes.forEach(([cls, location]) => (res[cls] = [...(res[cls] || []), location]));
146+
}
147+
}
148+
149+
/**
150+
* Main function that do all the stuff.
151+
*/
152+
async function main(args) {
153+
const classes = {};
154+
for (const arg of args) {
155+
await processDir(arg, classes);
156+
}
157+
158+
// const classNames = Object.keys(classes);
159+
// classNames.sort();
160+
161+
console.log(JSON.stringify(classes));
162+
}
163+
164+
const args = [...process.argv];
165+
args.shift(); // discard node
166+
args.shift(); // discard script name
167+
main(args).catch(e => console.error(e));

src/components/layout/Layout/Layout.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const Layout = ({
3232
className={classnames({
3333
'app-wrapper': true,
3434
'sidebar-expand-lg': true,
35+
'sidebar-mini': true,
3536
'sidebar-collapse': sidebarIsCollapsed,
3637
'sidebar-open': sidebarIsOpen,
3738
})}
@@ -53,7 +54,6 @@ const Layout = ({
5354
/>
5455
<SidebarContainer
5556
isCollapsed={sidebarIsCollapsed}
56-
small={!sidebarIsOpen && sidebarIsCollapsed} // does not always work, but is good enough
5757
currentUrl={currentUrl}
5858
pendingFetchOperations={pendingFetchOperations}
5959
/>

src/components/layout/Sidebar/Sidebar.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { getExternalIdForCAS } from '../../../helpers/cas.js';
1818
import { getConfigVar } from '../../../helpers/config.js';
1919
import Admin from './Admin.js';
2020

21-
import * as styles from './sidebar.less';
21+
import './sidebar.css';
2222

2323
const URL_PREFIX = getConfigVar('URL_PATH_PREFIX');
2424

@@ -30,7 +30,6 @@ const Sidebar = ({
3030
effectiveRole = null,
3131
currentUrl,
3232
instances,
33-
small = false,
3433
links: {
3534
HOME_URI,
3635
FAQ_URL,
@@ -47,7 +46,7 @@ const Sidebar = ({
4746
const user = getUserData(loggedInUser);
4847

4948
return (
50-
<aside className={`app-sidebar bg-body-secondary shadow ${styles.mainSidebar}`} data-bs-theme="dark">
49+
<aside className="app-sidebar bg-body-secondary shadow" data-bs-theme="dark">
5150
<div className="sidebar-brand">
5251
<Link to={HOME_URI} className="brand-link me-5">
5352
<>
@@ -58,7 +57,7 @@ const Sidebar = ({
5857
/>
5958
<span className="brand-text">
6059
{pendingFetchOperations && (
61-
<span className={styles.mainLoadingIcon}>
60+
<span className="brand-loading">
6261
<LoadingIcon gapRight={2} />
6362
</span>
6463
)}
@@ -69,7 +68,7 @@ const Sidebar = ({
6968
</div>
7069

7170
<div className="sticky-top shadow border-bottom bg-body-secondary py-2">
72-
<UserPanelContainer small={small} />
71+
<UserPanelContainer />
7372
</div>
7473

7574
<div className="sidebar-wrapper">
@@ -189,7 +188,6 @@ Sidebar.propTypes = {
189188
effectiveRole: PropTypes.string,
190189
currentUrl: PropTypes.string,
191190
instances: ImmutablePropTypes.list,
192-
small: PropTypes.bool,
193191
links: PropTypes.object,
194192
};
195193

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
.app-sidebar {
2+
height: 100%;
3+
overflow-y: auto;
4+
}
5+
6+
.app-sidebar .brand-loading {
7+
position: absolute;
8+
right: 1em;
9+
}
10+
11+
.sidebar-collapse .app-sidebar:not(:hover) .brand-link {
12+
margin-right: 0!important;
13+
}
14+
15+
.sidebar-collapse .app-sidebar:not(:hover) .brand-image {
16+
margin-right: 0!important;
17+
}
18+
19+
.sidebar-collapse .app-sidebar:not(:hover) .brand-text {
20+
display: none;
21+
}

src/components/layout/Sidebar/sidebar.less

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)