Skip to content

Commit

Permalink
feat(dep-graph): add dark mode (#8712)
Browse files Browse the repository at this point in the history
* feat(dep-graph): add dark mode

Squashed commits:

feat(dep-graph): Updating tailwind config file.

As of version 3.0 higher tailwind jit mode is default. Instead of purge is now content and as dark mode will be added as class.

feat(dep-graph): Enabling toggable dark mode.

As darkmode (tailwind.congig.json) is set as class, we need to add the script to the head tag on index.html.
see: https://tailwindcss.com/docs/dark-mode

feat(dep-graph): Adding theme Initializer and creating support functions

All functions related to theme changing and initialization are located on the theme-resolver.tsx
The themeInit function will run when page is loaded and apply add class to the <html> tag as dark or light accordignly. Theme resolver applies the changes and keeps localstorage and <html class> in sync.

feat(dep-graph): Adding theme panel

The pannel allows users to switch themes. Currently it only changes the html class and localstorage.

feat(dep-graph): Creating tailwind dark colors pallete

The current colors remain as light. The dark colors are an attempt to contrast the light ones.

feat(dep-graph): Adding additional styles to sidebar

feat(dep-graph): Define styling presets for darkmode to allow consistent and concise classNames.

feat(dep-graph): Apply dark styling classes to sidebar

feat(dep-graph): Added dark mode styles to the debugger

feat(dep-graph): Added Color to tailwind config and adjusting imports.

feat(dep-graph): Created theme tracker

feat(dep-graph): Added dark classes to graph container

feat(dep-graph): Adjusted some edge styles for better UX. Added dynamic selection of colors according to the current theme.

feat(dep-graph):  Added transition when switching themes.

feat(dep-graph): Readded auto roation for implicit label and dynamic background.

feat(dep-graph): Assigned generic types to selectDynbamically, and added new color to the pallete.

feat(dep-graph): Added dynamic styles for theming.

feat(dep-graph): Added mock for matchMedia. Tests will fail otherwise.

feat(dep-graph): Added styles to tippy.

feat(dep-graph): Moved theme related functions to theme-resolver file

feat(dep-graph): Implement dark mode on tooltips.

feat(dep-graph): re-evaluate graph colors on theme change

cleanup(dep-graph): Removed duplicate style

chore(dep-graph): Testing theme preferences

Adding test cases for theme initialization, and the ability to change.

cleanup(dep-graph): removing repeated style classes

Fixed issue with webpack plugin (#8231)

feat(npm): resolved issue with live reload failing

Fixed the issue with live reload when adding scripts in project.json

Closes #8230

chore(repo): update nx to 13.10.0-beta.1 (#9407)

feat(dep-graph): re-evaluate graph colors on theme change

fix(dep-graph): use theme background color in image download

* fix(dep-graph): change dark mode styles

* cleanup(dep-graph): cleanup e2e tests and naming

Co-authored-by: Philip Fulcher <philip@nrwl.io>
  • Loading branch information
Lcarv20 and philipjfulcher committed Mar 31, 2022
1 parent 84dbd43 commit c86618e
Show file tree
Hide file tree
Showing 31 changed files with 528 additions and 111 deletions.
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
// 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
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
5 changes: 4 additions & 1 deletion dep-graph/client/src/app/sidebar/focused-project-panel.tsx
@@ -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

0 comments on commit c86618e

Please sign in to comment.