Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(dep-graph): add dark mode #8712

Merged
merged 3 commits into from Mar 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 44 additions & 0 deletions dep-graph/client-e2e/src/integration/app.spec.ts
Expand Up @@ -266,3 +266,47 @@ describe('loading dep-graph client with url params', () => {
getCheckedProjectItems().should('have.length', 53);
});
});

describe('theme preferences', () => {
let systemTheme: string;
beforeEach(() => {
cy.visit('/');
systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';
});
it('should initialize localstorage with default theme', () => {
expect(localStorage.getItem('nx-dep-graph-theme')).eq('system');
});

it('has system default theme', () => {
cy.log('system theme is:', systemTheme);
cy.get('html').should('have.class', systemTheme);
});

describe('dark theme is set as prefered', () => {
before(() => {
cy.get('[data-cy="theme-open-modal-button"]').click();
cy.get('[data-cy="dark-theme-button"]').click();
});

it('should set dark theme', () => {
cy.log('Localstorage is: ', localStorage.getItem('nx-dep-graph-theme'));
expect(localStorage.getItem('nx-dep-graph-theme')).eq('dark');
cy.get('html').should('have.class', 'dark');
});
});

describe('light theme is set as preferred', () => {
before(() => {
cy.get('[data-cy="theme-open-modal-button"]').click();
cy.get('[data-cy="light-theme-button"]').click();
});

it('should set light theme', () => {
cy.log('Localstorage is: ', localStorage.getItem('nx-dep-graph-theme'));
expect(localStorage.getItem('nx-dep-graph-theme')).eq('light');
cy.get('html').should('have.class', 'light');
});
});
});
5 changes: 5 additions & 0 deletions dep-graph/client/jest.config.js
Expand Up @@ -10,4 +10,9 @@ module.exports = {
},
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
coverageDirectory: '../../coverage/nx-dev/nx-dev',
// The mock for widnow.matchMedia has to be in a separete file and imported before the components to test
Lcarv20 marked this conversation as resolved.
Show resolved Hide resolved
// for more info check : // https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
modulePathIgnorePatterns: [
'/dep-graph/client/src/app/machines/match-media-mock.spec.ts',
],
};
41 changes: 24 additions & 17 deletions dep-graph/client/project.json
Expand Up @@ -15,20 +15,7 @@
"styles": ["dep-graph/client/src/styles.scss"],
"scripts": [],
"assets": [],
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
],

"webpackConfig": "dep-graph/client/webpack.config.js"
},
"configurations": {
Expand Down Expand Up @@ -57,15 +44,35 @@
"maximumError": "5mb"
}
]
},
"release": {
"optimization": true,
"outputHashing": "none",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
}
]
}
},
"defaultConfiguration": "release",
"outputs": ["{options.outputPath}"]
},
"serve-base": {
"executor": "@nrwl/web:dev-server",
"options": {
"buildTarget": "dep-graph-client:build-base:dev"
}
"configurations": {
"dev": {
"buildTarget": "dep-graph-client:build-base:dev"
}
},
"defaultConfiguration": "dev"
},
"lint": {
"executor": "@nrwl/linter:eslint",
Expand Down
3 changes: 3 additions & 0 deletions dep-graph/client/src/app/app.tsx
@@ -1,5 +1,8 @@
import { Shell } from './shell';
import { GlobalStateProvider } from './state.provider';
import { themeInit } from './theme-resolver';

themeInit();

export function App() {
return (
Expand Down
8 changes: 8 additions & 0 deletions dep-graph/client/src/app/dark-theme-styles.tsx
@@ -0,0 +1,8 @@
// Styling presets for dark mode, defined by agregating styling classes
Lcarv20 marked this conversation as resolved.
Show resolved Hide resolved
export const DarkClasses = {
button:
'dark:bg-sidebar-btn-dark dark:border-sidebar-border-dark hover:dark:bg-opacity-40 dark:text-sidebar-text-dark',
buttonAffected: 'dark:bg-sidebar-btn-dark hover:dark:bg-red-900/[.05]',
input:
'dark:text-sidebar-text-dark dark:bg-sidebar-btn-dark/[0.5] dark:border-sidebar-border-dark',
};
24 changes: 16 additions & 8 deletions dep-graph/client/src/app/debugger-panel.tsx
@@ -1,6 +1,7 @@
import { ProjectGraphList } from './interfaces';
import { GraphPerfReport } from './machines/interfaces';
import { memo } from 'react';
import { DarkClasses } from './dark-theme-styles';

export interface DebuggerPanelProps {
projectGraphs: ProjectGraphList[];
Expand All @@ -20,19 +21,26 @@ export const DebuggerPanel = memo(function ({
data-cy="debugger-panel"
className="
flex-column
flex-column
dark:bg-sidebar-dark
dark:border-sidebar-border-dark flex
flex
w-auto
items-center justify-items-center
w-auto items-center
items-center
justify-items-center
justify-items-center gap-4
gap-4
border-b border-gray-200
border-b
border-gray-200
bg-gray-50
p-4
text-gray-700
p-4 transition-all
"
>
<h4 className="mr-4 text-lg font-bold">Debugger</h4>
<h4 className="dark:text-sidebar-title-dark mr-4 text-lg font-bold">
Debugger
</h4>
<select
className="flex w-auto items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700"
className={`flex w-auto items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 ${DarkClasses.button}`}
data-cy="project-select"
onChange={(event) => projectGraphChange(event.target.value)}
value={selectedProjectGraph}
Expand All @@ -45,7 +53,7 @@ export const DebuggerPanel = memo(function ({
);
})}
</select>
<p className="text-sm">
<p className="dark:text-sidebar-subtitle-dark text-sm">
Last render took {lastPerfReport.renderTime}ms:{' '}
<b className="text-medium font-mono">{lastPerfReport.numNodes} nodes</b>{' '}
|{' '}
Expand Down
9 changes: 1 addition & 8 deletions dep-graph/client/src/app/experimental-feature.tsx
Expand Up @@ -5,14 +5,7 @@ function ExperimentalFeature(props) {
const showExperimentalFeatures =
environment.appConfig.showExperimentalFeatures;

return showExperimentalFeatures ? (
<div data-cy="experimental-features" className="bg-purple-200 pb-2">
<h3 className="mt-4 cursor-text px-4 py-2 text-sm font-semibold uppercase tracking-wide text-gray-900 lg:text-xs ">
Experimental Features
</h3>
{props.children}
</div>
) : null;
return showExperimentalFeatures ? props.children : null;
}

export default ExperimentalFeature;
12 changes: 8 additions & 4 deletions dep-graph/client/src/app/hooks/use-environment-config.ts
Expand Up @@ -12,15 +12,19 @@ export function useEnvironmentConfig(): {
appConfig: AppConfig;
useXstateInspect: boolean;
} {
const environmentConfig = useRef({
const environmentConfig = useRef(getEnvironmentConfig());

return environmentConfig.current;
}

export function getEnvironmentConfig() {
return {
exclude: window.exclude,
watch: window.watch,
localMode: window.localMode,
projectGraphResponse: window.projectGraphResponse,
environment: window.environment,
appConfig: window.appConfig,
useXstateInspect: window.useXstateInspect,
});

return environmentConfig.current;
};
}
8 changes: 3 additions & 5 deletions dep-graph/client/src/app/machines/dep-graph.spec.ts
@@ -1,12 +1,10 @@
// nx-ignore-next-line
import type {
ProjectGraphDependency,
ProjectGraphProjectNode,
} from '@nrwl/devkit';
import type { ProjectGraphDependency, ProjectGraphNode } from '@nrwl/devkit';
import './match-media-mock.spec';
import { depGraphMachine } from './dep-graph.machine';
import { interpret } from 'xstate';

export const mockProjects: ProjectGraphProjectNode[] = [
export const mockProjects: ProjectGraphNode[] = [
{
name: 'app1',
type: 'app',
Expand Down
13 changes: 12 additions & 1 deletion dep-graph/client/src/app/machines/graph.ts
Expand Up @@ -18,6 +18,7 @@ import {
ProjectNode,
} from '../util-cytoscape';
import { GraphRenderEvents, GraphPerfReport } from './interfaces';
import { selectValueByThemeStatic } from '../theme-resolver';

export class GraphService {
private traversalGraph: cy.Core;
Expand Down Expand Up @@ -557,6 +558,16 @@ export class GraphService {
}

getImage() {
return this.renderGraph.png({ bg: '#fff', full: true });
const bg = selectValueByThemeStatic('#262626', '#fff');
return this.renderGraph.png({ bg, full: true });
}

evaluateStyles() {
if (this.renderGraph) {
const container = this.renderGraph.container();
this.renderGraph.unmount();

this.renderGraph.mount(container);
}
}
}
14 changes: 14 additions & 0 deletions dep-graph/client/src/app/machines/match-media-mock.spec.ts
@@ -0,0 +1,14 @@
// https://jestjs.io/docs/manual-mocks#mocking-methods-which-are-not-implemented-in-jsdom
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
17 changes: 14 additions & 3 deletions dep-graph/client/src/app/shell.tsx
Expand Up @@ -14,6 +14,7 @@ import {
projectIsSelectedSelector,
} from './machines/selectors';
import Sidebar from './sidebar/sidebar';
import { selectValueByThemeStatic } from './theme-resolver';

export function Shell() {
const depGraphService = useDepGraphService();
Expand Down Expand Up @@ -97,7 +98,10 @@ export function Shell() {
return (
<>
<Sidebar></Sidebar>
<div id="main-content" className="flex-grow overflow-hidden">
<div
id="main-content"
className="dark:bg-graph-dark flex-grow overflow-hidden transition-all"
>
{environment.appConfig.showDebugger ? (
<DebuggerPanel
projectGraphs={environment.appConfig.projectGraphs}
Expand All @@ -108,7 +112,10 @@ export function Shell() {
) : null}

{!projectIsSelected ? (
<div id="no-projects-chosen" className="flex text-gray-700">
<div
id="no-projects-chosen"
className="dark:text-graph-text-dark flex text-gray-700"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="mr-4 h-6 w-6"
Expand All @@ -128,7 +135,11 @@ export function Shell() {
) : null}
<div id="graph-container">
<div id="cytoscape-graph"></div>
<Tippy content="Download Graph as PNG" placement="right" theme="nx">
<Tippy
content="Download Graph as PNG"
placement="right"
theme={selectValueByThemeStatic('dark-nx', 'nx')}
>
<button
type="button"
className={`
Expand Down
@@ -1,4 +1,5 @@
import { memo } from 'react';
import { DarkClasses } from '../dark-theme-styles';

export interface FocusedProjectPanelProps {
focusedProject: string;
Expand Down Expand Up @@ -31,7 +32,9 @@ export const FocusedProjectPanel = memo(
</svg>
<span id="focused-project-name">Focused on {focusedProject}</span>
</p>
<div className="absolute right-2 flex translate-x-32 items-center rounded-md bg-white pl-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-gray-500 transition-all transition duration-200 ease-in-out group-hover:translate-x-0">
<div
className={`absolute right-2 flex translate-x-32 items-center rounded-md bg-white pl-2 text-sm font-medium text-gray-700 shadow-sm ring-1 ring-gray-500 transition-all duration-200 ease-in-out group-hover:translate-x-0 ${DarkClasses.button}`}
>
Reset
<span className="rounded-md p-1">
<svg
Expand Down
4 changes: 2 additions & 2 deletions dep-graph/client/src/app/sidebar/group-by-folder-panel.tsx
Expand Up @@ -24,11 +24,11 @@ export const GroupByFolderPanel = memo(
<div className="ml-3 text-sm">
<label
htmlFor="displayOptions"
className="cursor-pointer font-medium text-gray-700"
className="dark:text-sidebar-subtitle-dark cursor-pointer font-medium text-gray-700"
>
Group by folder
</label>
<p className="text-gray-500">
<p className="dark:text-sidebar-text-dark text-gray-500">
Visually arrange libraries by folders with different colors.
</p>
</div>
Expand Down