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

Expose icon SVG to theme CSS #6034

Merged
merged 85 commits into from Aug 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
7eab2e7
first working example of a correctly imported svg
telamonian Feb 24, 2019
d499b7b
added svg typings
telamonian Feb 24, 2019
8e6151f
made react-svg play nicely with the existing typestyle stuff
telamonian Feb 24, 2019
aab6485
added tweaks for svg imports to build system
telamonian Feb 25, 2019
856e7b6
svg theming is now working, needs a bunch of fixes
telamonian Feb 25, 2019
fcd93aa
All svg formatting is fixed and at parity with master
telamonian Feb 25, 2019
3dca6b0
progress
telamonian Apr 29, 2019
6713dfe
migrating icon stuff to ui-components, first working version
telamonian Apr 29, 2019
91352f5
progress on migration to ui-components
telamonian Apr 29, 2019
593124e
progress
telamonian Apr 30, 2019
3e54380
simplification
telamonian Apr 30, 2019
c11d151
fixed up handling of icon colors
telamonian Apr 30, 2019
9947194
progress, started work on non-react svg icons
telamonian Apr 30, 2019
27182d1
progress
telamonian Apr 30, 2019
69a6411
first working version of IconRegistry
telamonian May 2, 2019
d8ea0e5
webpack cleanup
telamonian May 2, 2019
2572625
pure js inline svgs now work in IconRegistry
telamonian May 2, 2019
dfb777f
css cleanup. The only non-theme svg left is jupyter-favicon.svg
telamonian May 3, 2019
429fbce
delinted
telamonian May 3, 2019
d8d3cd3
all non-theme icons in core have been converted to inline svgs
telamonian May 3, 2019
58e0163
unified icon handling in React and plain DOM
telamonian May 3, 2019
414ca89
got listing and tab svg icons working
telamonian Jun 4, 2019
ed3ddc0
all sidebar tab icons are now svg-ed
telamonian Jun 4, 2019
fde4939
cleanup
telamonian Jun 4, 2019
6bf50a9
fixed override method
telamonian Jun 4, 2019
f291e91
fixing gnarly file move conflict from rebase
telamonian Jun 4, 2019
6d52073
resolved conflict from rebase
telamonian Jun 4, 2019
96af4cb
fixed issues remaining from rebase
telamonian Jun 4, 2019
33fca86
categorized icons in ui-components
telamonian Jun 5, 2019
538e303
progress
telamonian Jun 5, 2019
6d434db
"fixed" doc build issue
telamonian Jun 5, 2019
bc8d92b
replaced sidebar hack with something better
telamonian Jun 5, 2019
f9cf2d0
splitting icon code into types and implementation
telamonian Jun 6, 2019
5836216
removed override hack, improved error handling
telamonian Jun 6, 2019
97c6cb6
finished replacing override with better fix
telamonian Jun 9, 2019
f9d0d69
cleanup, fixed vdom icon
telamonian Jun 9, 2019
1d384f5
added single debug flag to IconRegistry
telamonian Jun 13, 2019
7208024
removed redundant CSS styling/classes from inline svg icons
telamonian Jun 13, 2019
8629c1d
fixes following rebase
telamonian Jun 13, 2019
d249265
more rebase cleanup
telamonian Jun 13, 2019
a3214b0
even more cleanup
telamonian Jun 13, 2019
94af99d
fixed funky CSS bugs left over from CSS refactor
telamonian Jun 13, 2019
9c7bda9
fully automated import of svg icons
telamonian Jun 13, 2019
4dd3e1d
fixed typedoc issue, attempting to fix jest issue with a polyfill
telamonian Jun 13, 2019
3df4e9b
dynamic require breaks jest, disabling for now
telamonian Jun 14, 2019
f737a5a
rebase fix
telamonian Jun 14, 2019
d0ea82d
another attempt at dynamic svg imports
telamonian Jun 27, 2019
cd3d820
cleanup
telamonian Jun 27, 2019
3423f03
added `ui-components-extension` package
telamonian Jun 28, 2019
4471232
integrity
telamonian Jun 28, 2019
a335b11
post rebase delint/integrity/fixes
telamonian Jun 28, 2019
021ecd8
the iconRegistry plugin now works, is serving icons to filebrowser
telamonian Jun 28, 2019
c3af854
fixed examples, wrote docs
telamonian Jun 28, 2019
277b8eb
post rebase fix/integrity/delint
telamonian Jul 1, 2019
d941ce5
fixed @jupyterlab/test-filebrowser
telamonian Jul 1, 2019
fb3270b
fixing up colors
telamonian Jul 10, 2019
4843f18
added inline svg icons for many of the default filetypes
telamonian Jul 10, 2019
7a74ec0
fixed some colors and Listing's kernel running indicator (the green dot)
telamonian Jul 10, 2019
5ff8f75
finished converting all default filetype icons to inline svg
telamonian Jul 10, 2019
f5d9618
cleanup of code in listing and tabbarsvg
telamonian Jul 10, 2019
cf81b23
more cleanup
telamonian Jul 10, 2019
dd51b13
fixed up settingsEditor icons
telamonian Jul 10, 2019
b23d71b
restored home folder bread crumb icon
telamonian Jul 10, 2019
7b31d62
fixed up outdated icons
telamonian Jul 11, 2019
ae3a1e8
implemented `selectable` styles for inline svg icons
telamonian Jul 11, 2019
13f0230
fixed the icons in the tab manager in the sidebar
telamonian Jul 11, 2019
4dfeec7
fixed tabManager icon styling, and a couple of minor issues
telamonian Jul 11, 2019
144192c
fixed problem in test-filebrowser caused by change in bread crumb CSS…
telamonian Jul 11, 2019
21133fb
removed stray `debugger` line from test
telamonian Jul 11, 2019
2fcb22a
fixed launcher card icons
telamonian Jul 12, 2019
ae55a22
fixed launcher section icons
telamonian Jul 12, 2019
13f0178
switched icon svg imports to auto-generated explicit `import` statements
telamonian Jul 12, 2019
eb13110
fixed capitalization error, cleaned up some file names
telamonian Jul 12, 2019
fbecad1
fixed path issue in ensureUiComponents, more cleanup
telamonian Jul 12, 2019
9929092
added basic {{template}} handling to buildutils
telamonian Jul 17, 2019
7713f2a
added support for the (deprecated) icon CSS classes to ui-components
telamonian Jul 17, 2019
dac6b0d
deduped icon files in `ui-components` vs `theme-x-extension`
telamonian Jul 17, 2019
43c151d
moved "selected" icons to `deprecated` in `ui-components`
telamonian Jul 17, 2019
e154617
cleanup of splash icon handling
telamonian Jul 17, 2019
6886d0c
cleaned up/simplified tempalting code related to `ensureUiComponents`
telamonian Jul 17, 2019
b5a9441
fixed CI complaint
telamonian Jul 17, 2019
e03bf24
preemptively prettify code generated from templates
telamonian Jul 25, 2019
cdcdaba
integrity
telamonian Jul 25, 2019
180aee8
integrity
telamonian Jul 31, 2019
48d6cec
integrity/post rebase cleanup
telamonian Aug 14, 2019
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
2 changes: 2 additions & 0 deletions buildutils/package.json
Expand Up @@ -49,6 +49,7 @@
"mini-css-extract-plugin": "~0.6.0",
"package-json": "^6.3.0",
"path": "~0.12.7",
"prettier": "^1.18.2",
"semver": "^6.1.0",
"sort-package-json": "~1.22.1",
"typescript": "~3.5.1",
Expand All @@ -60,6 +61,7 @@
"@types/inquirer": "^6.0.3",
"@types/mini-css-extract-plugin": "^0.2.0",
"@types/node": "^12.0.2",
"@types/prettier": "^1.16.4",
"@types/webpack": "^4.4.32",
"rimraf": "~2.6.2"
}
Expand Down
5 changes: 1 addition & 4 deletions buildutils/src/build.ts
Expand Up @@ -183,10 +183,7 @@ export namespace Build {
},
{
test: /\.svg/,
use: [
{ loader: 'svg-url-loader', options: {} },
{ loader: 'svgo-loader', options: { plugins: [] } }
]
use: [{ loader: 'svg-url-loader', options: { encoding: 'none' } }]
},
{
test: /\.(png|jpg|gif|ttf|woff|woff2|eot)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
Expand Down
175 changes: 164 additions & 11 deletions buildutils/src/ensure-package.ts
Expand Up @@ -6,17 +6,48 @@
import * as fs from 'fs-extra';
import * as glob from 'glob';
import * as path from 'path';
import * as prettier from 'prettier';
import * as ts from 'typescript';
import { getDependency } from './get-dependency';
import * as utils from './utils';

const CSS_HEADER = `
const HEADER_TEMPLATE = `
/*-----------------------------------------------------------------------------
| Copyright (c) Jupyter Development Team.
| Distributed under the terms of the Modified BSD License.
|----------------------------------------------------------------------------*/

/* This file was auto-generated by ensurePackage() in @jupyterlab/buildutils */
/* This file was auto-generated by {{funcName}}() in @jupyterlab/buildutils */
`;

const ICON_IMPORTS_TEMPLATE = `
import { Icon } from './interfaces';

// icon svg import statements
{{iconImportStatements}}

// defaultIcons definition
export namespace IconImports {
export const defaultIcons: ReadonlyArray<Icon.IModel> = [
{{iconModelDeclarations}}
];
}
`;

const ICON_CSS_CLASSES_TEMPLATE = `
/**
* (DEPRECATED) Support for consuming icons as CSS background images
*/

/* Icons urls */

:root {
{{iconCSSUrls}}
}

/* Icon CSS class declarations */

{{iconCSSDeclarations}}
`;

/**
Expand Down Expand Up @@ -138,17 +169,20 @@ export async function ensurePackage(

// Template the CSS index file.
if (cssImports && fs.existsSync(path.join(pkgPath, 'style/base.css'))) {
let cssIndex = CSS_HEADER.trim();
const funcName = 'ensurePackage';
let cssIndexContents = utils.fromTemplate(
HEADER_TEMPLATE,
{ funcName },
{ end: '' }
);
cssImports.forEach(cssImport => {
cssIndex += `\n@import url('~${cssImport}');`;
cssIndexContents += `\n@import url('~${cssImport}');`;
});
cssIndex += "\n\n@import url('./base.css');\n";
const cssPath = path.join(pkgPath, 'style/index.css');
const prev = fs.readFileSync(cssPath, { encoding: 'utf8' });
if (prev !== cssIndex) {
messages.push(`Updated ./${data.style}`);
fs.writeFileSync(cssPath, cssIndex);
}
cssIndexContents += "\n\n@import url('./base.css');\n";

// write out cssIndexContents, if needed
const cssIndexPath = path.join(pkgPath, 'style/index.css');
messages.push(...ensureFile(cssIndexPath, cssIndexContents, false));
}

// Look for unused packages
Expand Down Expand Up @@ -290,6 +324,78 @@ export async function ensurePackage(
return messages;
}

/**
* An extra ensure function just for the @jupyterlab/ui-components package.
* Ensures that the icon svg import statements are synced with the contents
* of ui-components/style/icons.
*
* @param pkgPath - The path to the @jupyterlab/ui-components package.
*
* @returns A list of changes that were made to ensure the package.
*/
export async function ensureUiComponents(pkgPath: string): Promise<string[]> {
const funcName = 'ensureUiComponents';
let messages: string[] = [];

const svgs = glob.sync(path.join(pkgPath, 'style/icons', '**/*.svg'));

/* support for glob import of icon svgs */
const iconSrcDir = path.join(pkgPath, 'src/icon');

// build the per-icon import code
let _iconImportStatements: string[] = [];
let _iconModelDeclarations: string[] = [];
svgs.forEach(svg => {
const name = utils.stem(svg);
const nameCamel = utils.camelCase(name) + 'Svg';
_iconImportStatements.push(
`import ${nameCamel} from '${path.relative(iconSrcDir, svg)}';`
);
_iconModelDeclarations.push(`{ name: '${name}', svg: ${nameCamel} }`);
});
const iconImportStatements = _iconImportStatements.join('\n');
const iconModelDeclarations = _iconModelDeclarations.join(',\n');

// generate the actual contents of the iconImports file
const iconImportsPath = path.join(iconSrcDir, 'iconImports.ts');
const iconImportsContents = utils.fromTemplate(
HEADER_TEMPLATE + ICON_IMPORTS_TEMPLATE,
{ funcName, iconImportStatements, iconModelDeclarations }
);
messages.push(...ensureFile(iconImportsPath, iconImportsContents));

/* support for deprecated icon CSS classes */
const iconCSSDir = path.join(pkgPath, 'style');

// build the per-icon import code
let _iconCSSUrls: string[] = [];
let _iconCSSDeclarations: string[] = [];
svgs.forEach(svg => {
const name = utils.stem(svg);
const urlName = 'jp-icon-' + name;
const className = 'jp-' + utils.camelCase(name, true) + 'Icon';

_iconCSSUrls.push(
`--${urlName}: url('${path.relative(iconCSSDir, svg)}');`
);
_iconCSSDeclarations.push(
`.${className} {background-image: var(--${urlName})}`
);
});
const iconCSSUrls = _iconCSSUrls.join('\n');
const iconCSSDeclarations = _iconCSSDeclarations.join('\n');

// generate the actual contents of the iconCSSClasses file
const iconCSSClassesPath = path.join(iconCSSDir, 'deprecated.css');
const iconCSSClassesContent = utils.fromTemplate(
HEADER_TEMPLATE + ICON_CSS_CLASSES_TEMPLATE,
{ funcName, iconCSSUrls, iconCSSDeclarations }
);
messages.push(...ensureFile(iconCSSClassesPath, iconCSSClassesContent));

return messages;
}

/**
* The options used to ensure a package.
*/
Expand Down Expand Up @@ -340,6 +446,53 @@ export interface IEnsurePackageOptions {
differentVersions?: string[];
}

/**
* Ensure that contents of a file match a supplied string. If they do match,
* do nothing and return an empty array. If they don't match, overwrite the
* file and return an array with an update message.
*
* @param path: The path to the file being checked. The file must exist,
* or else this function does nothing.
*
* @param contents: The desired file contents.
*
* @param prettify: default = true. If true, format the contents with
* `prettier` before comparing/writing. Set to false only if you already
* know your code won't be modified later by the `prettier` git commit hook.
*
* @returns a string array with 0 or 1 messages.
*/
function ensureFile(
path: string,
contents: string,
prettify: boolean = true
): string[] {
let messages: string[] = [];

if (!fs.existsSync(path)) {
// bail
messages.push(
`Tried to ensure the contents of ./${path}, but the file does not exist`
);
return messages;
}

// run the newly generated contents through prettier before comparing
if (prettify) {
contents = prettier.format(contents, { filepath: path, singleQuote: true });
}

const prev = fs.readFileSync(path, {
encoding: 'utf8'
});
if (prev !== contents) {
fs.writeFileSync(path, contents);
messages.push(`Updated ./${path}`);
}

return messages;
}

/**
* Extract the module imports from a TypeScript source file.
*
Expand Down
16 changes: 15 additions & 1 deletion buildutils/src/ensure-repo.ts
Expand Up @@ -13,7 +13,11 @@
*/
import * as path from 'path';
import * as utils from './utils';
import { ensurePackage, IEnsurePackageOptions } from './ensure-package';
import {
ensurePackage,
ensureUiComponents,
IEnsurePackageOptions
} from './ensure-package';

type Dict<T> = { [key: string]: T };

Expand Down Expand Up @@ -341,6 +345,16 @@ export async function ensureIntegrity(): Promise<boolean> {
}
}

// ensure the icon svg imports
pkgMessages = await ensureUiComponents(pkgPaths['@jupyterlab/ui-components']);
if (pkgMessages.length > 0) {
let pkgName = '@jupyterlab/ui-components';
if (!messages[pkgName]) {
messages[pkgName] = [];
}
messages[pkgName] = messages[pkgName].concat(pkgMessages);
}

// Handle the top level package.
let corePath = path.resolve('.', 'package.json');
let coreData: any = utils.readJSONFile(corePath);
Expand Down
88 changes: 88 additions & 0 deletions buildutils/src/utils.ts
Expand Up @@ -114,6 +114,54 @@ export function writeJSONFile(filePath: string, data: any): boolean {
return false;
}

/**
* Simple template substitution for template vars of the form {{name}}
*
* @param templ: the template string.
* Ex: `This header generated by {{funcName}}`
*
* @param subs: an object in which the parameter keys are the template
* variables and the parameter values are the substitutions.
*
* @param options: function options.
*
* @param options.autoindent: default = true. If true, will try to match
* indentation level of {{var}} in substituted template.
*
* @param options.end: default = '\n'. Inserted at the end of
* a template post-substitution and post-trim.
*
* @returns the input template with all {{vars}} substituted, then `.trim`-ed.
*/
export function fromTemplate(
templ: string,
subs: Dict<string>,
options: { autoindent?: boolean; end?: string } = {}
) {
// default options values
const autoindent =
options.autoindent === undefined ? true : options.autoindent;
const end = options.end === undefined ? '\n' : options.end;

Object.keys(subs).forEach(key => {
const val = subs[key];

if (autoindent) {
// try to match the indentation level of the {{var}} in the input template.
templ = templ.split(`{{${key}}}`).reduce((acc, cur) => {
// Regex: 0 or more non-newline whitespaces followed by end of string
let indentRe = acc.match(/([^\S\r\n]*).*$/);
let indent = indentRe ? indentRe[1] : '';
return acc + val.split('\n').join('\n' + indent) + cur;
});
} else {
templ = templ.split(`{{${key}}}`).join(val);
}
});

return templ.trim() + end;
}

/**
*
* Call a command, checking its status.
Expand Down Expand Up @@ -286,3 +334,43 @@ export function ensureUnixPathSep(source: string) {
}
return source.replace(backSlash, '/');
}

/**
* Get the last portion of a path, without its extension (if any).
*
* @param path - The file path.
*
* @returns the last part of the path, sans extension.
*/
export function stem(path: string): string {
return path
.split('\\')
.pop()
.split('/')
.pop()
.split('.')
.shift();
}

/**
* Given a 'snake-case', 'snake_case', or 'snake case' string,
* will return the camel case version: 'snakeCase'.
*
* @param str: the snake-case input string.
*
* @param upper: default = false. If true, the first letter of the
* returned string will be capitalized.
*
* @returns the camel case version of the input string.
*/
export function camelCase(str: string, upper: boolean = false): string {
return str.replace(/(?:^\w|[A-Z]|\b\w|\s+|-+|_+)/g, function(match, index) {
if (+match === 0 || match[0] === '-') {
return '';
} else if (index === 0 && !upper) {
return match.toLowerCase();
} else {
return match.toUpperCase();
}
});
}
1 change: 1 addition & 0 deletions dev_mode/imports.css
Expand Up @@ -31,6 +31,7 @@
@import url('~@jupyterlab/tabmanager-extension/style/index.css');
@import url('~@jupyterlab/terminal-extension/style/index.css');
@import url('~@jupyterlab/tooltip-extension/style/index.css');
@import url('~@jupyterlab/ui-components-extension/style/index.css');
@import url('~@jupyterlab/vdom-extension/style/index.css');
@import url('~@jupyterlab/vega4-extension/style/index.css');
@import url('~@jupyterlab/vega5-extension/style/index.css');