From fb78bd2dd93a8ab3df808eeabd9ca4456927abbc Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Thu, 3 May 2018 20:08:24 -0700 Subject: [PATCH] initial work on mol-app --- package-lock.json | Bin 467895 -> 553844 bytes package.json | 35 +- src/apps/cif2bcif/converter.ts | 7 +- src/apps/cif2bcif/field-classifier.ts | 4 +- .../render-test/components/assemblies.tsx | 65 -- .../render-test/components/color-theme.tsx | 89 -- .../render-test/components/file-input.tsx | 62 -- src/apps/render-test/components/observer.tsx | 21 - .../render-test/components/sphere-detail.tsx | 60 -- src/apps/render-test/components/viewport.tsx | 28 - .../render-test/components/visibility.tsx | 70 -- src/apps/render-test/index.tsx | 13 - src/apps/render-test/state.ts | 226 ----- src/apps/render-test/ui.tsx | 103 --- src/apps/render-test/utils/index.ts | 58 -- src/apps/render-test/utils/mcubes.ts | 57 -- .../schema-generator/schema-from-mmcif-dic.ts | 4 +- src/apps/schema-generator/util/cif-dic.ts | 14 +- src/apps/viewer/index.html | 13 + src/apps/viewer/index.tsx | 88 ++ src/mol-app/context/context.ts | 61 ++ src/mol-app/controller/controller.ts | 49 ++ src/mol-app/controller/entity/tree.ts | 27 + src/mol-app/controller/layout.ts | 216 +++++ src/mol-app/controller/misc/jobs.ts | 77 ++ src/mol-app/controller/misc/log.ts | 22 + src/mol-app/controller/transform/list.ts | 27 + .../controller/visualization/viewport.ts | 27 + src/mol-app/event/basic.ts | 33 + src/mol-app/event/event.ts | 43 + src/mol-app/service/dispatcher.ts | 59 ++ src/mol-app/service/job.ts | 132 +++ src/mol-app/service/logger.ts | 51 ++ src/mol-app/skin/base.scss | 45 + src/mol-app/skin/bootstrap.scss | 25 + src/mol-app/skin/bootstrap/badges.scss | 68 ++ src/mol-app/skin/bootstrap/button-groups.scss | 244 ++++++ src/mol-app/skin/bootstrap/buttons.scss | 168 ++++ src/mol-app/skin/bootstrap/forms.scss | 617 +++++++++++++ src/mol-app/skin/bootstrap/input-groups.scss | 171 ++++ src/mol-app/skin/bootstrap/labels.scss | 66 ++ src/mol-app/skin/bootstrap/mixins.scss | 40 + .../bootstrap/mixins/background-variant.scss | 12 + .../skin/bootstrap/mixins/border-radius.scss | 18 + .../skin/bootstrap/mixins/buttons.scss | 65 ++ .../skin/bootstrap/mixins/clearfix.scss | 22 + src/mol-app/skin/bootstrap/mixins/forms.scss | 88 ++ src/mol-app/skin/bootstrap/mixins/grid.scss | 122 +++ src/mol-app/skin/bootstrap/mixins/image.scss | 33 + src/mol-app/skin/bootstrap/mixins/labels.scss | 12 + .../skin/bootstrap/mixins/opacity.scss | 8 + .../skin/bootstrap/mixins/tab-focus.scss | 9 + .../skin/bootstrap/mixins/text-emphasis.scss | 12 + .../skin/bootstrap/mixins/text-overflow.scss | 8 + .../bootstrap/mixins/vendor-prefixes.scss | 222 +++++ src/mol-app/skin/bootstrap/normalize.scss | 424 +++++++++ src/mol-app/skin/bootstrap/scaffolding.scss | 161 ++++ src/mol-app/skin/bootstrap/type.scss | 298 +++++++ src/mol-app/skin/bootstrap/variables.scss | 353 ++++++++ src/mol-app/skin/colors/blue.scss | 24 + src/mol-app/skin/colors/dark.scss | 22 + src/mol-app/skin/colors/light.scss | 30 + .../skin/components/controls-base.scss | 144 ++++ src/mol-app/skin/components/controls.scss | 197 +++++ src/mol-app/skin/components/entity.scss | 225 +++++ src/mol-app/skin/components/help.scss | 28 + src/mol-app/skin/components/jobs.scss | 131 +++ src/mol-app/skin/components/log.scss | 97 +++ src/mol-app/skin/components/misc.scss | 69 ++ src/mol-app/skin/components/panel.scss | 142 +++ src/mol-app/skin/components/slider.scss | 164 ++++ src/mol-app/skin/components/viewport.scss | 93 ++ src/mol-app/skin/fonts/fontello.eot | Bin 0 -> 49272 bytes src/mol-app/skin/fonts/fontello.svg | 442 ++++++++++ src/mol-app/skin/fonts/fontello.ttf | Bin 0 -> 49104 bytes src/mol-app/skin/fonts/fontello.woff | Bin 0 -> 29392 bytes src/mol-app/skin/fonts/fontello.woff2 | Bin 0 -> 23892 bytes src/mol-app/skin/icons.scss | 135 +++ src/mol-app/skin/layout.scss | 29 + src/mol-app/skin/layout/common.scss | 60 ++ src/mol-app/skin/layout/landscape.scss | 81 ++ src/mol-app/skin/layout/outside.scss | 89 ++ src/mol-app/skin/layout/portrait.scss | 99 +++ src/mol-app/skin/logo.scss | 47 + src/mol-app/skin/molstar-blue.scss | 2 + src/mol-app/skin/molstar-dark.scss | 2 + src/mol-app/skin/molstar-light.scss | 2 + src/mol-app/skin/ui.scss | 38 + src/mol-app/skin/variables.scss | 78 ++ src/mol-app/ui/controls/common.tsx | 167 ++++ src/mol-app/ui/controls/slider.tsx | 814 ++++++++++++++++++ src/mol-app/ui/entity/tree.tsx | 98 +++ src/mol-app/ui/layout.tsx | 89 ++ src/mol-app/ui/misc/jobs.tsx | 64 ++ src/mol-app/ui/misc/log.tsx | 69 ++ src/mol-app/ui/transform/file-loader.tsx | 29 + src/mol-app/ui/transform/list.tsx | 74 ++ src/mol-app/ui/transform/model.tsx | 67 ++ src/mol-app/ui/transform/spacefill.tsx | 143 +++ src/mol-app/ui/view.tsx | 93 ++ src/mol-app/ui/visualization/viewport.tsx | 141 +++ src/mol-io/reader/_spec/cif.spec.ts | 4 +- src/mol-io/reader/cif.ts | 12 +- src/mol-io/reader/cif/binary/field.ts | 10 +- src/mol-io/reader/cif/binary/parser.ts | 12 +- src/mol-io/reader/cif/data-model.ts | 34 +- src/mol-io/reader/cif/schema.ts | 22 +- src/mol-io/reader/cif/text/field.ts | 10 +- src/mol-io/reader/cif/text/parser.ts | 26 +- src/mol-io/reader/csv/data-model.ts | 20 +- src/mol-io/reader/csv/parser.ts | 12 +- src/mol-io/reader/gro/parser.ts | 14 +- src/mol-io/reader/gro/schema.d.ts | 14 +- src/mol-io/reader/mol2/parser.ts | 16 +- src/mol-io/reader/mol2/schema.d.ts | 18 +- src/mol-model/structure/model/format.ts | 2 +- src/mol-model/structure/model/formats/gro.ts | 8 +- src/mol-util/download.ts | 65 ++ src/mol-util/file-info.ts | 73 ++ src/mol-util/id-factory.ts | 4 +- src/mol-util/index.ts | 113 ++- src/mol-util/parse-unit.ts | 1 + src/mol-util/performance-monitor.ts | 65 ++ src/mol-util/read.ts | 44 + src/mol-view/stage.ts | 52 ++ src/mol-view/state/context.ts | 28 + src/mol-view/state/entity.ts | 120 +++ src/mol-view/state/transform.ts | 152 ++++ src/mol-view/util.ts | 25 + src/mol-view/viewer.ts | 20 +- tsconfig.json | 1 + web/render-test/index.html | 12 - webpack.config.js | 26 +- 133 files changed, 9488 insertions(+), 1017 deletions(-) delete mode 100644 src/apps/render-test/components/assemblies.tsx delete mode 100644 src/apps/render-test/components/color-theme.tsx delete mode 100644 src/apps/render-test/components/file-input.tsx delete mode 100644 src/apps/render-test/components/observer.tsx delete mode 100644 src/apps/render-test/components/sphere-detail.tsx delete mode 100644 src/apps/render-test/components/viewport.tsx delete mode 100644 src/apps/render-test/components/visibility.tsx delete mode 100644 src/apps/render-test/index.tsx delete mode 100644 src/apps/render-test/state.ts delete mode 100644 src/apps/render-test/ui.tsx delete mode 100644 src/apps/render-test/utils/index.ts delete mode 100644 src/apps/render-test/utils/mcubes.ts create mode 100644 src/apps/viewer/index.html create mode 100644 src/apps/viewer/index.tsx create mode 100644 src/mol-app/context/context.ts create mode 100644 src/mol-app/controller/controller.ts create mode 100644 src/mol-app/controller/entity/tree.ts create mode 100644 src/mol-app/controller/layout.ts create mode 100644 src/mol-app/controller/misc/jobs.ts create mode 100644 src/mol-app/controller/misc/log.ts create mode 100644 src/mol-app/controller/transform/list.ts create mode 100644 src/mol-app/controller/visualization/viewport.ts create mode 100644 src/mol-app/event/basic.ts create mode 100644 src/mol-app/event/event.ts create mode 100644 src/mol-app/service/dispatcher.ts create mode 100644 src/mol-app/service/job.ts create mode 100644 src/mol-app/service/logger.ts create mode 100644 src/mol-app/skin/base.scss create mode 100644 src/mol-app/skin/bootstrap.scss create mode 100644 src/mol-app/skin/bootstrap/badges.scss create mode 100644 src/mol-app/skin/bootstrap/button-groups.scss create mode 100644 src/mol-app/skin/bootstrap/buttons.scss create mode 100644 src/mol-app/skin/bootstrap/forms.scss create mode 100644 src/mol-app/skin/bootstrap/input-groups.scss create mode 100644 src/mol-app/skin/bootstrap/labels.scss create mode 100644 src/mol-app/skin/bootstrap/mixins.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/background-variant.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/border-radius.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/buttons.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/clearfix.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/forms.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/grid.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/image.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/labels.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/opacity.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/tab-focus.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/text-emphasis.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/text-overflow.scss create mode 100644 src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss create mode 100644 src/mol-app/skin/bootstrap/normalize.scss create mode 100644 src/mol-app/skin/bootstrap/scaffolding.scss create mode 100644 src/mol-app/skin/bootstrap/type.scss create mode 100644 src/mol-app/skin/bootstrap/variables.scss create mode 100644 src/mol-app/skin/colors/blue.scss create mode 100644 src/mol-app/skin/colors/dark.scss create mode 100644 src/mol-app/skin/colors/light.scss create mode 100644 src/mol-app/skin/components/controls-base.scss create mode 100644 src/mol-app/skin/components/controls.scss create mode 100644 src/mol-app/skin/components/entity.scss create mode 100644 src/mol-app/skin/components/help.scss create mode 100644 src/mol-app/skin/components/jobs.scss create mode 100644 src/mol-app/skin/components/log.scss create mode 100644 src/mol-app/skin/components/misc.scss create mode 100644 src/mol-app/skin/components/panel.scss create mode 100644 src/mol-app/skin/components/slider.scss create mode 100644 src/mol-app/skin/components/viewport.scss create mode 100644 src/mol-app/skin/fonts/fontello.eot create mode 100644 src/mol-app/skin/fonts/fontello.svg create mode 100644 src/mol-app/skin/fonts/fontello.ttf create mode 100644 src/mol-app/skin/fonts/fontello.woff create mode 100644 src/mol-app/skin/fonts/fontello.woff2 create mode 100644 src/mol-app/skin/icons.scss create mode 100644 src/mol-app/skin/layout.scss create mode 100644 src/mol-app/skin/layout/common.scss create mode 100644 src/mol-app/skin/layout/landscape.scss create mode 100644 src/mol-app/skin/layout/outside.scss create mode 100644 src/mol-app/skin/layout/portrait.scss create mode 100644 src/mol-app/skin/logo.scss create mode 100644 src/mol-app/skin/molstar-blue.scss create mode 100644 src/mol-app/skin/molstar-dark.scss create mode 100644 src/mol-app/skin/molstar-light.scss create mode 100644 src/mol-app/skin/ui.scss create mode 100644 src/mol-app/skin/variables.scss create mode 100644 src/mol-app/ui/controls/common.tsx create mode 100644 src/mol-app/ui/controls/slider.tsx create mode 100644 src/mol-app/ui/entity/tree.tsx create mode 100644 src/mol-app/ui/layout.tsx create mode 100644 src/mol-app/ui/misc/jobs.tsx create mode 100644 src/mol-app/ui/misc/log.tsx create mode 100644 src/mol-app/ui/transform/file-loader.tsx create mode 100644 src/mol-app/ui/transform/list.tsx create mode 100644 src/mol-app/ui/transform/model.tsx create mode 100644 src/mol-app/ui/transform/spacefill.tsx create mode 100644 src/mol-app/ui/view.tsx create mode 100644 src/mol-app/ui/visualization/viewport.tsx create mode 100644 src/mol-util/download.ts create mode 100644 src/mol-util/file-info.ts create mode 100644 src/mol-util/performance-monitor.ts create mode 100644 src/mol-util/read.ts create mode 100644 src/mol-view/stage.ts create mode 100644 src/mol-view/state/context.ts create mode 100644 src/mol-view/state/entity.ts create mode 100644 src/mol-view/state/transform.ts delete mode 100644 web/render-test/index.html diff --git a/package-lock.json b/package-lock.json index e8d174db64f94695fff7d142ab262682782e2d2e..7e574f630d3b78f7f2f79cd0b910a5f38116cbf4 100644 GIT binary patch delta 35972 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 delta 6818 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 H?8N^9lcXS- diff --git a/package.json b/package.json index 2a60147a8..4ec4c0c13 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,12 @@ }, "scripts": { "lint": "tslint src/**/*.ts", - "build": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ && tsc", + "build": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ && tsc", "watch": "tsc -watch", - "watch-shader": "cpx \"src/**/*.{vert,frag,glsl}\" build/node_modules/ --watch", + "watch-extra": "cpx \"src/**/*.{vert,frag,glsl,scss,woff,woff2,ttf,otf,eot,svg,html}\" build/node_modules/ --watch", "test": "jest", - "script": "node build/node_modules/script.js", - "app-render-test": "webpack build/node_modules/apps/render-test/index.js --mode development -o web/render-test/index.js", - "app-render-test-watch": "webpack build/node_modules/apps/render-test/index.js -w --mode development -o web/render-test/index.js" + "build-viewer": "webpack build/node_modules/apps/viewer/index.js --mode development -o build/viewer/index.js", + "watch-viewer": "webpack build/node_modules/apps/viewer/index.js -w --mode development -o build/viewer/index.js" }, "jest": { "moduleFileExtensions": [ @@ -34,6 +33,7 @@ "build/node_modules" ], "moduleNameMapper": { + "mol-app($|/.*)": "<rootDir>/src/mol-app$1", "mol-data($|/.*)": "<rootDir>/src/mol-data$1", "mol-geo($|/.*)": "<rootDir>/src/mol-geo$1", "mol-gl($|/.*)": "<rootDir>/src/mol-gl$1", @@ -60,35 +60,42 @@ "@types/benchmark": "^1.0.31", "@types/express": "^4.11.1", "@types/jest": "^22.2.3", - "@types/node": "^9.6.8", + "@types/node": "^9.6.16", "@types/node-fetch": "^1.6.9", - "@types/react": "^16.3.13", + "@types/react": "^16.3.14", "@types/react-dom": "^16.0.5", "benchmark": "^2.1.4", "copyfiles": "^2.0.0", "cpx": "^1.5.0", + "css-loader": "^0.28.11", "extra-watch-webpack-plugin": "^1.0.3", + "extract-text-webpack-plugin": "^4.0.0-beta.0", + "file-loader": "^1.1.11", "glslify-import": "^3.1.0", "glslify-loader": "^1.0.2", "jest": "^22.4.3", "jest-raw-loader": "^1.0.1", + "node-sass": "^4.9.0", "raw-loader": "^0.5.1", - "ts-jest": "^22.4.4", - "tslint": "^5.9.1", + "resolve-url-loader": "^2.3.0", + "sass-loader": "^7.0.1", + "style-loader": "^0.21.0", + "ts-jest": "^22.4.6", + "tslint": "^5.10.0", "typescript": "^2.8.3", - "uglify-js": "^3.3.23", + "uglify-js": "^3.3.25", "util.promisify": "^1.0.0", - "webpack": "^4.6.0", - "webpack-cli": "^2.1.2" + "webpack": "^4.8.3", + "webpack-cli": "^2.1.3" }, "dependencies": { "argparse": "^1.0.10", "express": "^4.16.3", "gl": "^4.0.4", - "material-ui": "^1.0.0-beta.43", + "immutable": "^4.0.0-rc.9", "node-fetch": "^2.1.2", "react": "^16.3.2", "react-dom": "^16.3.2", - "rxjs": "^6.0.0" + "rxjs": "^6.1.0" } } diff --git a/src/apps/cif2bcif/converter.ts b/src/apps/cif2bcif/converter.ts index 1a3a08221..8a0530bc5 100644 --- a/src/apps/cif2bcif/converter.ts +++ b/src/apps/cif2bcif/converter.ts @@ -5,7 +5,7 @@ */ import Iterator from 'mol-data/iterator' -import CIF, { Category } from 'mol-io/reader/cif' +import CIF, { CifCategory } from 'mol-io/reader/cif' import * as Encoder from 'mol-io/writer/cif' import * as fs from 'fs' import classify from './field-classifier' @@ -20,14 +20,14 @@ async function getCIF(path: string) { return parsed.result; } -function createDefinition(cat: Category): Encoder.CategoryDefinition { +function createDefinition(cat: CifCategory): Encoder.CategoryDefinition { return { name: cat.name, fields: cat.fieldNames.map(n => classify(n, cat.getField(n)!)) } } -function getCategoryInstanceProvider(cat: Category): Encoder.CategoryProvider { +function getCategoryInstanceProvider(cat: CifCategory): Encoder.CategoryProvider { return function (ctx: any) { return { data: cat, @@ -50,4 +50,3 @@ export default async function convert(path: string, asText = false) { } return encoder.getData(); } - diff --git a/src/apps/cif2bcif/field-classifier.ts b/src/apps/cif2bcif/field-classifier.ts index 9d8d322f6..d0725993f 100644 --- a/src/apps/cif2bcif/field-classifier.ts +++ b/src/apps/cif2bcif/field-classifier.ts @@ -5,7 +5,7 @@ */ import { Column } from 'mol-data/db' -import { Field } from 'mol-io/reader/cif/data-model' +import { CifField } from 'mol-io/reader/cif/data-model' import { FieldDefinition, FieldType } from 'mol-io/writer/cif/encoder' const intRegex = /^-?\d+$/ @@ -13,7 +13,7 @@ const floatRegex = /^-?(([0-9]+)[.]?|([0-9]*[.][0-9]+))([(][0-9]+[)])?([eE][+-]? // Classify a cif field as str, int or float based the data it contains. // To classify a field as int or float all items are checked. -function classify(name: string, field: Field): FieldDefinition { +function classify(name: string, field: CifField): FieldDefinition { let floatCount = 0, hasString = false; for (let i = 0, _i = field.rowCount; i < _i; i++) { const k = field.valueKind(i); diff --git a/src/apps/render-test/components/assemblies.tsx b/src/apps/render-test/components/assemblies.tsx deleted file mode 100644 index ab0bae997..000000000 --- a/src/apps/render-test/components/assemblies.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { WithStyles } from 'material-ui/styles'; -import { MenuItem } from 'material-ui/Menu'; -import { InputLabel } from 'material-ui/Input'; -import { FormControl } from 'material-ui/Form'; -import Select from 'material-ui/Select'; - -import State from '../state' -import Observer from './observer'; -import { Assembly } from 'mol-model/structure/model/properties/symmetry'; - -interface AssemblyState { - loading: boolean - assemblies: ReadonlyArray<Assembly> - value: string -} - -export default class Assemblies extends Observer<{ state: State } & WithStyles, AssemblyState> { - state: AssemblyState = { loading: false, assemblies: [], value: '' } - - componentDidMount() { - this.subscribe(this.props.state.loading, value => { - this.setState({ loading: value }); - }); - this.subscribe(this.props.state.model, value => { - this.setState({ assemblies: value ? value.symmetry.assemblies : [] }); - }); - this.subscribe(this.props.state.assembly, value => { - this.setState({ value }); - }); - } - - handleValueChange = (event: React.ChangeEvent<any>) => { - this.props.state.assembly.next(event.target.value) - } - - render() { - const { classes } = this.props; - - const items = this.state.assemblies.map((value, idx) => { - return <MenuItem key={idx} value={value.id}>{value.details}</MenuItem> - }) - - return <FormControl className={classes.formControl}> - <InputLabel htmlFor='assembly-value'>Assembly</InputLabel> - <Select - className={classes.selectField} - value={this.state.value} - onChange={this.handleValueChange} - inputProps={{ - name: 'value', - id: 'assembly-value', - }} - > - {items} - </Select> - </FormControl> - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/color-theme.tsx b/src/apps/render-test/components/color-theme.tsx deleted file mode 100644 index 701247c5d..000000000 --- a/src/apps/render-test/components/color-theme.tsx +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { WithStyles } from 'material-ui/styles'; -import { MenuItem } from 'material-ui/Menu'; -import { InputLabel } from 'material-ui/Input'; -import { FormControl } from 'material-ui/Form'; -import Select from 'material-ui/Select'; - -import State, { ColorTheme as _ColorTheme } from '../state' -import Observer from './observer'; -import { Color, ColorNames } from 'mol-util/color'; - -interface ColorThemeState { - loading: boolean - name: _ColorTheme - value: Color -} - -export default class ColorTheme extends Observer<{ state: State } & WithStyles, ColorThemeState> { - state: ColorThemeState = { loading: false, name: 'element-symbol' as _ColorTheme, value: 0xFF0000 } - - componentDidMount() { - this.subscribe(this.props.state.loading, value => { - this.setState({ loading: value }); - }); - this.subscribe(this.props.state.colorTheme, value => { - this.setState({ name: value }); - }); - this.subscribe(this.props.state.colorValue, value => { - this.setState({ value: value }); - }); - } - - handleNameChange = (event: React.ChangeEvent<any>) => { - this.props.state.colorTheme.next(event.target.value) - } - - handleValueChange = (event: React.ChangeEvent<any>) => { - this.props.state.colorValue.next(event.target.value) - } - - render() { - const { classes } = this.props; - - const colorThemeItems = Object.keys(_ColorTheme).map((name, idx) => { - return <MenuItem key={idx} value={name}>{name}</MenuItem> - }) - - const colorValueItems = Object.keys(ColorNames).map((name, idx) => { - return <MenuItem key={idx} value={(ColorNames as any)[name]}>{name}</MenuItem> - }) - - return <div> - <FormControl className={classes.formControl}> - <InputLabel htmlFor='color-theme-name'>Color Theme</InputLabel> - <Select - className={classes.selectField} - value={this.state.name} - onChange={this.handleNameChange} - inputProps={{ - name: 'name', - id: 'color-theme-name', - }} - > - {colorThemeItems} - </Select> - </FormControl> - <FormControl className={classes.formControl}> - <InputLabel htmlFor='uniform-color-value'>Color Value</InputLabel> - <Select - className={classes.selectField} - value={this.state.value} - onChange={this.handleValueChange} - inputProps={{ - name: 'value', - id: 'uniform-color-value', - }} - > - {colorValueItems} - </Select> - </FormControl> - </div> - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/file-input.tsx b/src/apps/render-test/components/file-input.tsx deleted file mode 100644 index 850ba452e..000000000 --- a/src/apps/render-test/components/file-input.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { WithStyles } from 'material-ui/styles'; -import TextField from 'material-ui/TextField'; -import Button from 'material-ui/Button'; - -import State from '../state' -import Observer from './observer'; - -export default class FileInput extends Observer<{ state: State } & WithStyles, { loading: boolean }> { - state = { loading: false } - - componentDidMount() { - this.subscribe(this.props.state.loading, value => { - this.setState({ loading: value }); - }); - } - - render() { - const { classes, state } = this.props; - - return <div> - <TextField - label='PDB ID' - className={classes.textField} - disabled={this.state.loading} - margin='normal' - onChange={(event) => { - state.pdbId = event.target.value - }} - onKeyPress={(event) => { - if (event.key === 'Enter') state.loadPdbId() - }} - /> - <input - accept='*.cif' - className={classes.input} - id='button-file' - type='file' - onChange={(event) => { - if (event.target.files) { - state.loadFile(event.target.files[0]) - } - }} - /> - <label htmlFor='button-file'> - <Button - variant='raised' - component='span' - className={classes.button} - > - Open CIF - </Button> - </label> - </div> - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/observer.tsx b/src/apps/render-test/components/observer.tsx deleted file mode 100644 index 67e48fb23..000000000 --- a/src/apps/render-test/components/observer.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { Observable, Subscription } from 'rxjs'; - -export default class Observer<S, P> extends React.Component<S, P> { - private _subs: Subscription[] = [] - - subscribe<T>(obs: Observable<T>, onNext: (v: T) => void) { - this._subs.push(obs.subscribe(onNext)); - } - - componentWillUnmount() { - for (const s of this._subs) s.unsubscribe(); - this._subs = []; - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/sphere-detail.tsx b/src/apps/render-test/components/sphere-detail.tsx deleted file mode 100644 index 1b19cca35..000000000 --- a/src/apps/render-test/components/sphere-detail.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { WithStyles } from 'material-ui/styles'; -import { MenuItem } from 'material-ui/Menu'; -import { InputLabel } from 'material-ui/Input'; -import { FormControl } from 'material-ui/Form'; -import Select from 'material-ui/Select'; - -import State from '../state' -import Observer from './observer'; - -interface SphereDetailState { - loading: boolean - value: number -} - -export default class SphereDetail extends Observer<{ state: State } & WithStyles, SphereDetailState> { - state: SphereDetailState = { loading: false, value: 2 } - - componentDidMount() { - this.subscribe(this.props.state.loading, value => { - this.setState({ loading: value }); - }); - this.subscribe(this.props.state.sphereDetail, value => { - this.setState({ value }); - }); - } - - handleValueChange = (event: React.ChangeEvent<any>) => { - this.props.state.sphereDetail.next(event.target.value) - } - - render() { - const { classes } = this.props; - - const items = [0, 1, 2, 3].map((value, idx) => { - return <MenuItem key={idx} value={value}>{value.toString()}</MenuItem> - }) - - return <FormControl className={classes.formControl}> - <InputLabel htmlFor='sphere-detail-value'>Sphere Detail</InputLabel> - <Select - className={classes.selectField} - value={this.state.value} - onChange={this.handleValueChange} - inputProps={{ - name: 'value', - id: 'sphere-detail-value', - }} - > - {items} - </Select> - </FormControl> - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/viewport.tsx b/src/apps/render-test/components/viewport.tsx deleted file mode 100644 index 27b0cd63c..000000000 --- a/src/apps/render-test/components/viewport.tsx +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import State from '../state' - -export default class Viewport extends React.Component<{ state: State }, { initialized: boolean }> { - private container: HTMLDivElement | null = null; - private canvas: HTMLCanvasElement | null = null; - state = { initialized: false } - - componentDidMount() { - if (this.container && this.canvas) { - this.props.state.initRenderer(this.canvas, this.container).then(() => { - this.setState({ initialized: true }) - }) - } - } - - render() { - return <div ref={elm => this.container = elm} style={{ height: '100%' }}> - <canvas ref={elm => this.canvas = elm}></canvas> - </div> - } -} \ No newline at end of file diff --git a/src/apps/render-test/components/visibility.tsx b/src/apps/render-test/components/visibility.tsx deleted file mode 100644 index a93a9b123..000000000 --- a/src/apps/render-test/components/visibility.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { WithStyles } from 'material-ui/styles'; -import { FormLabel, FormControl, FormGroup, FormControlLabel } from 'material-ui/Form'; -import Checkbox from 'material-ui/Checkbox'; - -import State from '../state' -import Observer from './observer'; - -interface VisibilityState { - loading: boolean - spacefill: boolean - point: boolean -} - -export default class Visibility extends Observer<{ state: State } & WithStyles, VisibilityState> { - state: VisibilityState = { loading: false, spacefill: true, point: true } - - componentDidMount() { - this.subscribe(this.props.state.loading, value => { - this.setState({ loading: value }); - }); - this.subscribe(this.props.state.spacefillVisibility, value => { - this.setState({ spacefill: value }); - }); - this.subscribe(this.props.state.pointVisibility, value => { - this.setState({ point: value }); - }); - } - - handleChange = (event: React.ChangeEvent<any>) => { - switch (event.target.name) { - case 'point': this.props.state.pointVisibility.next(event.target.checked); break; - case 'spacefill': this.props.state.spacefillVisibility.next(event.target.checked); break; - } - } - - render() { - const { classes } = this.props - - return <div className={classes.formControl}> - <FormControl component='fieldset'> - <FormLabel component='legend'>Visibility</FormLabel> - <FormGroup> - <FormControlLabel - control={<Checkbox - checked={this.state.point} - onChange={this.handleChange} - name='point' - />} - label='Point' - /> - <FormControlLabel - control={<Checkbox - checked={this.state.spacefill} - onChange={this.handleChange} - name='spacefill' - />} - label='Spacefill' - /> - </FormGroup> - </FormControl> - </div> - } -} \ No newline at end of file diff --git a/src/apps/render-test/index.tsx b/src/apps/render-test/index.tsx deleted file mode 100644 index 2e87d291f..000000000 --- a/src/apps/render-test/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import UI from './ui' -import State from './state' -import * as React from 'react' -import * as ReactDOM from 'react-dom' - -const state = new State() -ReactDOM.render(<UI state={ state } />, document.getElementById('app')); \ No newline at end of file diff --git a/src/apps/render-test/state.ts b/src/apps/render-test/state.ts deleted file mode 100644 index 1e284a0a1..000000000 --- a/src/apps/render-test/state.ts +++ /dev/null @@ -1,226 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import { BehaviorSubject } from 'rxjs'; - -// import { ValueCell } from 'mol-util/value-cell' - -// import { Vec3, Mat4 } from 'mol-math/linear-algebra' -import Viewer from 'mol-view/viewer' -// import { createColorTexture } from 'mol-gl/util'; -// import Icosahedron from 'mol-geo/primitive/icosahedron' -// import Box from 'mol-geo/primitive/box' -import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill' -import Point, { PointProps } from 'mol-geo/representation/structure/point' - -import { Run } from 'mol-task' -import { Symmetry, Structure, Model } from 'mol-model/structure' - -// import mcubes from './utils/mcubes' -import { getModelFromPdbId, getModelFromFile, log, Volume, getVolumeFromEmdId } from './utils' -import { StructureRepresentation } from 'mol-geo/representation/structure'; -import { Color } from 'mol-util/color'; -import Surface, { SurfaceProps } from 'mol-geo/representation/volume/surface'; -import { VolumeIsoValue } from 'mol-model/volume'; -import { VolumeRepresentation } from 'mol-geo/representation/volume'; -// import Cylinder from 'mol-geo/primitive/cylinder'; - - -export const ColorTheme = { - 'atom-index': {}, - 'chain-id': {}, - 'element-symbol': {}, - 'instance-index': {}, - 'uniform': {} -} -export type ColorTheme = keyof typeof ColorTheme - -export default class State { - viewer: Viewer - pdbId = '1crn' - // pdbId = '5ire' - emdId = '8116' - // pdbId = '6G1K' - // emdId = '4339' - // pdbId = '4cup' - // emdId = '' - model = new BehaviorSubject<Model | undefined>(undefined) - volume = new BehaviorSubject<Volume | undefined>(undefined) - initialized = new BehaviorSubject<boolean>(false) - loading = new BehaviorSubject<boolean>(false) - - colorTheme = new BehaviorSubject<ColorTheme>('element-symbol') - colorValue = new BehaviorSubject<Color>(0xFF4411) - sphereDetail = new BehaviorSubject<number>(0) - assembly = new BehaviorSubject<string>('') - - pointVisibility = new BehaviorSubject<boolean>(true) - spacefillVisibility = new BehaviorSubject<boolean>(true) - - pointRepr: StructureRepresentation<PointProps> - spacefillRepr: StructureRepresentation<SpacefillProps> - surfaceRepr: VolumeRepresentation<SurfaceProps> - - constructor() { - this.colorTheme.subscribe(() => this.update()) - this.colorValue.subscribe(() => this.update()) - this.sphereDetail.subscribe(() => this.update()) - this.assembly.subscribe(() => this.initStructure()) - - this.pointVisibility.subscribe(() => this.updateVisibility()) - this.spacefillVisibility.subscribe(() => this.updateVisibility()) - } - - getSpacefillProps (): SpacefillProps { - const colorThemeName = this.colorTheme.getValue() - return { - doubleSided: true, - detail: this.sphereDetail.getValue(), - colorTheme: colorThemeName === 'uniform' ? - { name: colorThemeName, value: this.colorValue.getValue() } : - { name: colorThemeName } - } - } - - getPointProps (): PointProps { - const colorThemeName = this.colorTheme.getValue() - return { - sizeTheme: { name: 'uniform', value: 0.1 }, - colorTheme: colorThemeName === 'uniform' ? - { name: colorThemeName, value: this.colorValue.getValue() } : - { name: colorThemeName } - } - } - - async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { - this.viewer = Viewer.create(canvas, container) - this.initialized.next(true) - this.loadPdbId() - this.loadEmdId() - this.viewer.animate() - } - - async getStructure () { - const model = this.model.getValue() - if (!model) return - const assembly = this.assembly.getValue() - let structure: Structure - const assemblies = model.symmetry.assemblies - if (assemblies.length) { - structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), log, 500) - } else { - structure = Structure.ofModel(model) - } - return structure - } - - async initStructure () { - const { viewer } = this - if (!viewer || !this.model.getValue()) return - - if (this.pointRepr) this.viewer.remove(this.pointRepr) - if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr) - - const structure = await this.getStructure() - if (!structure) return - - this.pointRepr = StructureRepresentation(Point) - await Run(this.pointRepr.create(structure, this.getPointProps()), log, 500) - viewer.add(this.pointRepr) - - this.spacefillRepr = StructureRepresentation(Spacefill) - await Run(this.spacefillRepr.create(structure, this.getSpacefillProps()), log, 500) - viewer.add(this.spacefillRepr) - - this.updateVisibility() - viewer.requestDraw() - console.log(viewer.stats) - } - - setModel(model: Model) { - this.model.next(model) - this.initStructure() - this.loading.next(false) - } - - async loadFile (file: File) { - this.viewer.clear() - this.loading.next(true) - this.setModel((await getModelFromFile(file))[0]) - } - - async initVolume () { - const { viewer } = this - const v = this.volume.getValue() - if (!viewer || !v) return - - if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr) - - this.surfaceRepr = VolumeRepresentation(Surface) - await Run(this.surfaceRepr.create(v.volume, { - isoValue: VolumeIsoValue.relative(v.volume.dataStats, 3.0), - alpha: 0.5, - flatShaded: false, - flipSided: true, - doubleSided: true - }), log, 500) - viewer.add(this.surfaceRepr) - - viewer.requestDraw() - console.log(viewer.stats) - } - - async loadPdbId () { - if (this.pointRepr) this.viewer.remove(this.pointRepr) - if (this.spacefillRepr) this.viewer.remove(this.spacefillRepr) - if (this.pdbId.length !== 4) return - this.loading.next(true) - this.setModel((await getModelFromPdbId(this.pdbId))[0]) - } - - setVolume(volume: Volume) { - this.volume.next(volume) - this.initVolume() - this.loading.next(false) - } - - async loadEmdId () { - if (this.surfaceRepr) this.viewer.remove(this.surfaceRepr) - if (this.emdId.length !== 4) return - this.loading.next(true) - this.setVolume(await getVolumeFromEmdId(this.emdId)) - } - - async update () { - if (!this.spacefillRepr) return - await Run(this.spacefillRepr.update(this.getSpacefillProps()), log, 500) - await Run(this.pointRepr.update(this.getPointProps()), log, 500) - this.viewer.add(this.spacefillRepr) - this.viewer.add(this.pointRepr) - this.viewer.update() - this.viewer.requestDraw() - console.log(this.viewer.stats) - } - - updateVisibility () { - if (!this.viewer) return - if (this.pointRepr) { - if (this.pointVisibility.getValue()) { - this.viewer.show(this.pointRepr) - } else { - this.viewer.hide(this.pointRepr) - } - } - if (this.spacefillRepr) { - if (this.spacefillVisibility.getValue()) { - this.viewer.show(this.spacefillRepr) - } else { - this.viewer.hide(this.spacefillRepr) - } - } - this.viewer.requestDraw() - } -} \ No newline at end of file diff --git a/src/apps/render-test/ui.tsx b/src/apps/render-test/ui.tsx deleted file mode 100644 index b302fe512..000000000 --- a/src/apps/render-test/ui.tsx +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import * as React from 'react' -import { withStyles, WithStyles, Theme, StyleRulesCallback } from 'material-ui/styles'; -import Typography from 'material-ui/Typography'; -import Toolbar from 'material-ui/Toolbar'; -import AppBar from 'material-ui/AppBar'; -import Drawer from 'material-ui/Drawer'; - -import State from './state' - -import Viewport from './components/viewport' -import FileInput from './components/file-input' -import ColorTheme from './components/color-theme' -import SphereDetail from './components/sphere-detail' -import Visibility from './components/visibility' -import Assemblies from './components/assemblies' - - -const styles: StyleRulesCallback = (theme: Theme) => ({ - root: { - flexGrow: 1, - height: 830, - zIndex: 1, - overflow: 'hidden', - position: 'relative', - display: 'flex', - }, - appBar: { - zIndex: theme.zIndex.drawer + 1, - }, - drawerPaper: { - position: 'relative', - width: 240, - }, - content: { - flexGrow: 1, - backgroundColor: theme.palette.background.default, - padding: theme.spacing.unit * 3, - minWidth: 0, // So the Typography noWrap works - }, - toolbar: theme.mixins.toolbar, - formControl: { - margin: theme.spacing.unit, - width: 200, - }, - textField: { - marginLeft: theme.spacing.unit, - marginRight: theme.spacing.unit, - width: 200, - }, - button: { - margin: theme.spacing.unit, - }, - input: { - display: 'none', - }, -} as any); - -const decorate = withStyles(styles); - -interface Props { - state: State; -}; - -class UI extends React.Component<{ state: State } & WithStyles, { }> { - render() { - const { classes, state } = this.props; - return ( - <div className={classes.root}> - <AppBar position='absolute' className={classes.appBar}> - <Toolbar> - <Typography variant='title' color='inherit' noWrap> - Mol* Render Test - </Typography> - </Toolbar> - </AppBar> - <Drawer variant='permanent' classes={{ paper: classes.drawerPaper, }}> - <div className={classes.toolbar} /> - <FileInput state={state} classes={classes}></FileInput> - <form className={classes.root} autoComplete='off'> - <div> - <Assemblies state={state} classes={classes}></Assemblies> - <ColorTheme state={state} classes={classes}></ColorTheme> - <SphereDetail state={state} classes={classes}></SphereDetail> - <Visibility state={state} classes={classes}></Visibility> - </div> - </form> - </Drawer> - <main className={classes.content}> - <div className={classes.toolbar} /> - <Viewport state={state}></Viewport> - </main> - </div> - ); - } -} - -export default decorate<Props>(UI) \ No newline at end of file diff --git a/src/apps/render-test/utils/index.ts b/src/apps/render-test/utils/index.ts deleted file mode 100644 index 54486ffb3..000000000 --- a/src/apps/render-test/utils/index.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author Alexander Rose <alexander.rose@weirdbyte.de> - */ - -import CIF from 'mol-io/reader/cif' -import { Run, Progress } from 'mol-task' -import { Model } from 'mol-model/structure' -import { VolumeData, parseDensityServerData } from 'mol-model/volume' -import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; - -export function log(progress: Progress) { - const p = progress.root.progress - console.log(`${p.message} ${(p.current/p.max*100).toFixed(2)}%`) -} - -export async function downloadCif(url: string, isBinary: boolean) { - const data = await fetch(url); - return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); -} - -export async function parseCif(data: string|Uint8Array) { - const comp = CIF.parse(data) - const parsed = await Run(comp, log, 500); - if (parsed.isError) throw parsed; - return parsed.result -} - -export async function getModelFromPdbId(pdbid: string) { - const cif = await downloadCif(`https://files.rcsb.org/download/${pdbid}.cif`, false) - return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) }) -} - -const readFileAsText = (file: File) => { - const fileReader = new FileReader() - return new Promise<string>((resolve, reject) => { - fileReader.onerror = () => { - fileReader.abort() - reject(new DOMException('Error parsing input file.')) - } - fileReader.onload = () => resolve(fileReader.result) - fileReader.readAsText(file) - }) -} - -export async function getModelFromFile(file: File) { - const cif = await parseCif(await readFileAsText(file)) - return Model.create({ kind: 'mmCIF', data: CIF.schema.mmCIF(cif.blocks[0]) }) -} - -export type Volume = { source: DensityServer_Data_Database, volume: VolumeData } - -export async function getVolumeFromEmdId(emdid: string): Promise<Volume> { - const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true) - const data = CIF.schema.densityServer(cif.blocks[1]) - return { source: data, volume: await Run(parseDensityServerData(data)) } -} \ No newline at end of file diff --git a/src/apps/render-test/utils/mcubes.ts b/src/apps/render-test/utils/mcubes.ts deleted file mode 100644 index 92905c7b6..000000000 --- a/src/apps/render-test/utils/mcubes.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import { Run } from 'mol-task' -import { computeMarchingCubes } from 'mol-geo/util/marching-cubes/algorithm' -import { Mesh } from 'mol-geo/shape/mesh' -import { Tensor, Mat4, Vec3 } from 'mol-math/linear-algebra' - -function fillField(tensor: Tensor, f: (x: number, y: number, z: number) => number, min: number[], max: number[]): Tensor { - const { space: { set, dimensions: [ii, jj, kk] }, data } = tensor; - - const dx = (max[0] - min[0]) / (ii - 1); - const dy = (max[1] - min[1]) / (jj - 1); - const dz = (max[2] - min[2]) / (kk - 1); - - for (let i = 0, x = min[0]; i < ii; i++, x += dx) { - for (let j = 0, y = min[1]; j < jj; j++, y += dy) { - for (let k = 0, z = min[2]; k < kk; k++, z += dz) { - set(data, i, j, k, f(x, y, z)); - } - } - } - - return tensor -} - -export default async function computeSurface(f: (x: number, y: number, z: number) => number, data?: { field: Tensor, surface: Mesh }) { - let field: Tensor; - if (data) field = data.field; - else { - const space = Tensor.Space([30, 30, 30], [0, 1, 2]); - field = Tensor.create(space, space.create(Float32Array)); - } - - const min = Vec3.create(-1.1, -1.1, -1.1), max = Vec3.create(1.1, 1.1, 1.1); - - fillField(field, f, min, max); - const surface = await Run(computeMarchingCubes({ - scalarField: field, - isoLevel: 0, - oldSurface: data ? data.surface : void 0 - })); - - const translation = Mat4.fromTranslation(Mat4.zero(), min); - const grid = Vec3.zero(); - Vec3.fromArray(grid, field.space.dimensions as any, 0); - const size = Vec3.sub(Vec3.zero(), max, min); - const scale = Mat4.fromScaling(Mat4.zero(), Vec3.create(size[0] / (grid[0] - 1), size[1] / (grid[1] - 1), size[2] / (grid[2] - 1))); - - const transform = Mat4.mul(Mat4.zero(), translation, scale); - Mesh.transformImmediate(surface, transform); - Mesh.computeNormalsImmediate(surface); - return { surface, field }; -} \ No newline at end of file diff --git a/src/apps/schema-generator/schema-from-mmcif-dic.ts b/src/apps/schema-generator/schema-from-mmcif-dic.ts index a6fc5fed2..eb1d7e74b 100644 --- a/src/apps/schema-generator/schema-from-mmcif-dic.ts +++ b/src/apps/schema-generator/schema-from-mmcif-dic.ts @@ -10,7 +10,7 @@ import * as fs from 'fs' import fetch from 'node-fetch' import Csv from 'mol-io/reader/csv/parser' -import CIF, { Frame } from 'mol-io/reader/cif' +import CIF, { CifFrame } from 'mol-io/reader/cif' import { generateSchema } from './util/cif-dic' import { generate } from './util/generate' import { Filter } from './util/json-schema' @@ -29,7 +29,7 @@ async function runGenerateSchema(name: string, fieldNamesPath?: string, typescri const ihmDicVersion = CIF.schema.dic(ihmDic.result.blocks[0]).dictionary.version.value(0) const version = `Dictionary versions: mmCIF ${mmcifDicVersion}, IHM ${ihmDicVersion}.` - const frames: Frame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames] + const frames: CifFrame[] = [...mmcifDic.result.blocks[0].saveFrames, ...ihmDic.result.blocks[0].saveFrames] const schema = generateSchema(frames) const filter = fieldNamesPath ? await getFieldNamesFilter(fieldNamesPath) : undefined diff --git a/src/apps/schema-generator/util/cif-dic.ts b/src/apps/schema-generator/util/cif-dic.ts index 5cc6016cf..718c93483 100644 --- a/src/apps/schema-generator/util/cif-dic.ts +++ b/src/apps/schema-generator/util/cif-dic.ts @@ -6,7 +6,7 @@ import { Database, ValueColumn, ListColumn } from './json-schema' import * as Data from 'mol-io/reader/cif/data-model' -import { Frame } from 'mol-io/reader/cif/data-model'; +import { CifFrame } from 'mol-io/reader/cif/data-model'; export function getFieldType (type: string, values?: string[]): ValueColumn|ListColumn { switch (type) { @@ -76,7 +76,7 @@ export function getFieldType (type: string, values?: string[]): ValueColumn|List return 'str' } -type FrameCategories = { [category: string]: Data.Frame } +type FrameCategories = { [category: string]: Data.CifFrame } type FrameLinks = { [k: string]: string } interface FrameData { @@ -85,7 +85,7 @@ interface FrameData { } // get field from given or linked category -function getField ( category: string, field: string, d: Data.Frame, ctx: FrameData): Data.Field|undefined { +function getField ( category: string, field: string, d: Data.CifFrame, ctx: FrameData): Data.CifField|undefined { const { categories, links } = ctx const cat = d.categories[category] @@ -105,7 +105,7 @@ function getField ( category: string, field: string, d: Data.Frame, ctx: FrameDa } } -function getEnums (d: Data.Frame, ctx: FrameData) { +function getEnums (d: Data.CifFrame, ctx: FrameData) { const value = getField('item_enumeration', 'value', d, ctx) const enums: string[] = [] if (value) { @@ -119,7 +119,7 @@ function getEnums (d: Data.Frame, ctx: FrameData) { } } -function getCode (d: Data.Frame, ctx: FrameData): [string, string[]|undefined]|undefined { +function getCode (d: Data.CifFrame, ctx: FrameData): [string, string[]|undefined]|undefined { const code = getField('item_type', 'code', d, ctx) if (code) { return [ code.str(0), getEnums(d, ctx) ] @@ -128,7 +128,7 @@ function getCode (d: Data.Frame, ctx: FrameData): [string, string[]|undefined]|u } } -function getSubCategory (d: Data.Frame, ctx: FrameData): string|undefined { +function getSubCategory (d: Data.CifFrame, ctx: FrameData): string|undefined { const value = getField('item_sub_category', 'id', d, ctx) if (value) { return value.str(0) @@ -178,7 +178,7 @@ const SPACE_SEPARATED_LIST_FIELDS = [ '_pdbx_soln_scatter.data_analysis_software_list', // SCTPL5 GNOM ]; -export function generateSchema (frames: Frame[]) { +export function generateSchema (frames: CifFrame[]) { const schema: Database = {} const categories: FrameCategories = {} diff --git a/src/apps/viewer/index.html b/src/apps/viewer/index.html new file mode 100644 index 000000000..f3faa04f9 --- /dev/null +++ b/src/apps/viewer/index.html @@ -0,0 +1,13 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> + <title>Mol* Render Test</title> + <link href='./app.css', rel="stylesheet"> + </head> + <body> + <div id="app"></div> + <script type="text/javascript" src="./index.js"></script> + </body> +</html> \ No newline at end of file diff --git a/src/apps/viewer/index.tsx b/src/apps/viewer/index.tsx new file mode 100644 index 000000000..f828e597a --- /dev/null +++ b/src/apps/viewer/index.tsx @@ -0,0 +1,88 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import * as ReactDOM from 'react-dom' + +import './index.html' +import 'mol-app/skin/molstar-light.scss' + +import { Context } from 'mol-app/context/context'; +import { Viewport } from 'mol-app/ui/visualization/viewport' +import { makeEmptyTargets, LayoutRegion } from 'mol-app/controller/layout'; +import { Layout } from 'mol-app/ui/layout'; +import { LogController } from 'mol-app/controller/misc/log'; +import { Log } from 'mol-app/ui/misc/log'; +import { JobsController } from 'mol-app/controller/misc/jobs'; +import { BackgroundJobs, Overlay } from 'mol-app/ui/misc/jobs'; +import { EntityTree } from 'mol-app/ui/entity/tree'; +import { EntityTreeController } from 'mol-app/controller/entity/tree'; +import { TransformListController } from 'mol-app/controller/transform/list'; +import { TransformList } from 'mol-app/ui/transform/list'; + +const elm = document.getElementById('app') +if (!elm) throw new Error('Can not find element with id "app".') + +const ctx = new Context() +const targets = makeEmptyTargets(); + +targets[LayoutRegion.Main].components.push({ + key: 'molstar-internal-viewport', + controller: ctx.viewport, + region: LayoutRegion.Main, + view: Viewport, + isStatic: true +}); + +targets[LayoutRegion.Bottom].components.push({ + key: 'molstar-log', + controller: new LogController(ctx), + region: LayoutRegion.Bottom, + view: Log, + isStatic: true +}); + +targets[LayoutRegion.Main].components.push({ + key: 'molstar-background-jobs', + controller: new JobsController(ctx, 'Background'), + region: LayoutRegion.Main, + view: BackgroundJobs, + isStatic: true +}); + +targets[LayoutRegion.Root].components.push({ + key: 'molstar-overlay', + controller: new JobsController(ctx, 'Normal'), + region: LayoutRegion.Root, + view: Overlay, + isStatic: true +}); + +targets[LayoutRegion.Right].components.push({ + key: 'molstar-transform-list', + controller: new TransformListController(ctx), + region: LayoutRegion.Right, + view: TransformList, + isStatic: false +}); + +targets[LayoutRegion.Left].components.push({ + key: 'molstar-entity-tree', + controller: new EntityTreeController(ctx), + region: LayoutRegion.Left, + view: EntityTree, + isStatic: true +}); + +ctx.createLayout(targets, elm) +ctx.layout.setState({ + isExpanded: true, + hideControls: false, + collapsedControlsLayout: 0 +}) +// ctx.viewport.setState() + +ReactDOM.render(React.createElement(Layout, { controller: ctx.layout }), elm); diff --git a/src/mol-app/context/context.ts b/src/mol-app/context/context.ts new file mode 100644 index 000000000..92a317aad --- /dev/null +++ b/src/mol-app/context/context.ts @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { UUID } from 'mol-util' +import { PerformanceMonitor } from 'mol-util/performance-monitor'; +import { Dispatcher } from '../service/dispatcher' +import { Logger } from '../service/logger' +import { LayoutTarget, LayoutController } from '../controller/layout'; +import { ViewportController } from '../controller/visualization/viewport'; +import { Stage } from 'mol-view/stage'; +import { AnyTransform } from 'mol-view/state/transform'; +import { BehaviorSubject } from 'rxjs'; +import { AnyEntity } from 'mol-view/state/entity'; + +export class Settings { + private settings = new Map<string, any>(); + + set(key: string, value: any) { + this.settings.set(key, value); + } + + get(key: string) { + return this.settings.get(key); + } +} + +export class Context { + id = UUID.create() + + dispatcher = new Dispatcher(); + logger = new Logger(this); + performance = new PerformanceMonitor(); + + stage = new Stage(); + viewport = new ViewportController(this); + layout: LayoutController; + settings = new Settings(); + + currentEntity = new BehaviorSubject(undefined) as BehaviorSubject<AnyEntity | undefined> + currentTransforms = new BehaviorSubject([] as AnyTransform[]) + + createLayout(targets: LayoutTarget[], target: HTMLElement) { + this.layout = new LayoutController(this, targets, target); + } + + initStage(canvas: HTMLCanvasElement, container: HTMLDivElement) { + this.stage.initRenderer(canvas, container) + return true + } + + destroy() { + if (this.stage) { + this.stage.dispose() + this.stage = null as any + } + } +} \ No newline at end of file diff --git a/src/mol-app/controller/controller.ts b/src/mol-app/controller/controller.ts new file mode 100644 index 000000000..bfd6127f6 --- /dev/null +++ b/src/mol-app/controller/controller.ts @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { BehaviorSubject } from 'rxjs'; +import { merge } from 'mol-util'; +import { Context } from '../context/context' +import { LayoutRegion } from './layout'; + +export class Controller<State> { + + private _state = new BehaviorSubject<State>(<any>void 0); + private _latestState: State = <any>void 0; + + get dispatcher() { + return this.context.dispatcher; + } + + setState(...states: Partial<State>[]) { + let s = merge(this._latestState, ...states); + if (s !== this._latestState) { + this._latestState = s; + this._state.next(s); + } + } + + get state() { + return this._state; + } + + get latestState() { + return this._latestState; + } + + constructor(public context: Context, initialState: State) { + this._latestState = initialState; + } +} + +export interface ControllerInfo { + key: string; + controller: Controller<any>; + view: any; + region: LayoutRegion; + isStatic?: boolean; +} \ No newline at end of file diff --git a/src/mol-app/controller/entity/tree.ts b/src/mol-app/controller/entity/tree.ts new file mode 100644 index 000000000..fdcb620e5 --- /dev/null +++ b/src/mol-app/controller/entity/tree.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Context } from '../../context/context' +import { Controller } from '../controller'; +import { AnyEntity } from 'mol-view/state/entity'; + +export interface EntityTreeState { + entities: Set<AnyEntity> +} + +export class EntityTreeController extends Controller<EntityTreeState> { + constructor(context: Context) { + super(context, { entities: new Set() }); + + context.stage.ctx.change.subscribe(() => { + if (context.stage.ctx) { + this.state.next({ entities: context.stage.ctx.entities }) // TODO + this.setState({ entities: context.stage.ctx.entities }) + } + }) + } +} \ No newline at end of file diff --git a/src/mol-app/controller/layout.ts b/src/mol-app/controller/layout.ts new file mode 100644 index 000000000..388f57788 --- /dev/null +++ b/src/mol-app/controller/layout.ts @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Context } from '../context/context' +import { Controller, ControllerInfo } from './controller' +import { CommonEvents, LayoutEvents } from '../event/basic'; + +export enum LayoutRegion { + Main = 0, + Top = 1, + Right = 2, + Bottom = 3, + Left = 4, + Root = 5 +} + +export enum CollapsedControlsLayout { + Outside = 0, + Landscape = 1, + Portrait = 2 +} + +export class LayoutTarget { + components: ControllerInfo[] = []; + constructor(public cssClass: string) { + } +} + +export function makeEmptyTargets() { + let ret: LayoutTarget[] = []; + for (let i = 0; i <= LayoutRegion.Root; i++) { + ret.push(new LayoutTarget(LayoutRegion[i].toLowerCase())); + } + return ret; +} + +export type RegionState = 'Hidden' | 'Sticky' | 'Default' + +export interface LayoutState { + isExpanded: boolean, + hideControls: boolean, + collapsedControlsLayout: CollapsedControlsLayout, + regionStates?: { [region: number]: RegionState } +} + +interface RootState { + top: string | null, + bottom: string | null, + left: string | null, + right: string | null, + + width: string | null; + height: string | null; + maxWidth: string | null; + maxHeight: string | null; + margin: string | null; + marginLeft: string | null; + marginRight: string | null; + marginTop: string | null; + marginBottom: string | null; + + scrollTop: number, + scrollLeft: number, + position: string | null, + overflow: string | null, + viewports: HTMLElement[], + zindex: string | null +} + +export class LayoutController extends Controller<LayoutState> { + + update(state: Partial<LayoutState>) { + let prevExpanded = !!this.latestState.isExpanded; + this.setState(state); + if (typeof state.isExpanded === 'boolean' && state.isExpanded !== prevExpanded) this.handleExpand(); + + this.dispatcher.schedule(() => CommonEvents.LayoutChanged.dispatch(this.context, {})); + } + + private rootState: RootState | undefined = void 0; + private expandedViewport: HTMLMetaElement; + + private getScrollElement() { + if ((document as any).scrollingElement) return (document as any).scrollingElement; + if (document.documentElement) return document.documentElement; + return document.body; + } + + private handleExpand() { + try { + let body = document.getElementsByTagName('body')[0]; + let head = document.getElementsByTagName('head')[0]; + + if (!body || !head) return; + + if (this.latestState.isExpanded) { + + let children = head.children; + let hasExp = false; + let viewports: HTMLElement[] = []; + for (let i = 0; i < children.length; i++) { + if (children[i] === this.expandedViewport) { + hasExp = true; + } else if (((children[i] as any).name || '').toLowerCase() === 'viewport') { + viewports.push(children[i] as any); + } + } + + for (let v of viewports) { + head.removeChild(v); + } + + if (!hasExp) head.appendChild(this.expandedViewport); + + + let s = body.style; + + let doc = this.getScrollElement(); + let scrollLeft = doc.scrollLeft; + let scrollTop = doc.scrollTop; + + this.rootState = { + top: s.top, bottom: s.bottom, right: s.right, left: s.left, scrollTop, scrollLeft, position: s.position, overflow: s.overflow, viewports, zindex: this.root.style.zIndex, + width: s.width, height: s.height, + maxWidth: s.maxWidth, maxHeight: s.maxHeight, + margin: s.margin, marginLeft: s.marginLeft, marginRight: s.marginRight, marginTop: s.marginTop, marginBottom: s.marginBottom + }; + + s.overflow = 'hidden'; + s.position = 'fixed'; + s.top = '0'; + s.bottom = '0'; + s.right = '0'; + s.left = '0'; + + s.width = '100%'; + s.height = '100%'; + s.maxWidth = '100%'; + s.maxHeight = '100%'; + s.margin = '0'; + s.marginLeft = '0'; + s.marginRight = '0'; + s.marginTop = '0'; + s.marginBottom = '0'; + + this.root.style.zIndex = '100000'; + } else { + // root.style.overflow = rootOverflow; + let children = head.children; + for (let i = 0; i < children.length; i++) { + if (children[i] === this.expandedViewport) { + head.removeChild(this.expandedViewport); + break; + } + } + + if (this.rootState) { + let s = body.style, t = this.rootState; + for (let v of t.viewports) { + head.appendChild(v); + } + s.top = t.top; + s.bottom = t.bottom; + s.left = t.left; + s.right = t.right; + + s.width = t.width; + s.height = t.height; + s.maxWidth = t.maxWidth; + s.maxHeight = t.maxHeight; + s.margin = t.margin; + s.marginLeft = t.marginLeft; + s.marginRight = t.marginRight; + s.marginTop = t.marginTop; + s.marginBottom = t.marginBottom; + + s.position = t.position; + s.overflow = t.overflow; + let doc = this.getScrollElement(); + doc.scrollTop = t.scrollTop; + doc.scrollLeft = t.scrollLeft; + this.rootState = void 0; + this.root.style.zIndex = t.zindex; + } + } + } catch (e) { + this.context.logger.error('Layout change error, you might have to reload the page.'); + console.log('Layout change error, you might have to reload the page.', e); + } + } + + updateTargets(targets: LayoutTarget[]) { + this.targets = targets; + this.dispatcher.schedule(() => CommonEvents.ComponentsChanged.dispatch(this.context, {})); + } + + constructor(context: Context, public targets: LayoutTarget[], private root: HTMLElement) { + super(context, { + isExpanded: false, + hideControls: false, + collapsedControlsLayout: CollapsedControlsLayout.Outside, + regionStates: { } + }); + + LayoutEvents.SetState.getStream(this.context).subscribe(e => this.update(e.data)); + + // <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" /> + this.expandedViewport = document.createElement('meta') as any; + this.expandedViewport.name = 'viewport'; + this.expandedViewport.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0'; + } +} \ No newline at end of file diff --git a/src/mol-app/controller/misc/jobs.ts b/src/mol-app/controller/misc/jobs.ts new file mode 100644 index 000000000..075c32e0f --- /dev/null +++ b/src/mol-app/controller/misc/jobs.ts @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Map } from 'immutable' +import { filter } from 'rxjs/operators'; + +import { Controller } from '../controller' +import { JobEvents } from '../../event/basic'; +import { Context } from '../../context/context'; +import { Job } from '../../service/job'; + + +export interface JobInfo { + name: string; + message: string; + abort?: () => void +} + +export interface JobsState { + jobs: Map<number, JobInfo> +} + +export class JobsController extends Controller<JobsState> { + private updated(state: Job.State) { + let isWatched = state.type === this.type; + let jobs = this.latestState.jobs!; + + if (!isWatched) { + if (jobs.has(state.jobId)) { + jobs = jobs.delete(state.jobId); + this.setState({ jobs }); + } + return; + } + + jobs = jobs.set(state.jobId, { + name: state.name, + message: state.message, + abort: state.abort + }); + this.setState({ jobs }); + } + + private started(job: Job.Info) { + this.setState({ + jobs: this.latestState.jobs!.set(job.id, { name: job.name, message: 'Running...' }) + }); + } + + private completed(taskId: number) { + if (!this.latestState.jobs!.has(taskId)) return; + + this.setState({ + jobs: this.latestState.jobs!.delete(taskId) + }); + } + + constructor(context: Context, private type: Job.Type) { + super(context, { + jobs: Map<number, JobInfo>() + }); + + JobEvents.StateUpdated.getStream(this.context) + .subscribe(e => this.updated(e.data)); + + JobEvents.Started.getStream(this.context).pipe( + filter(e => e.data.type === type)) + .subscribe(e => this.started(e.data)); + + JobEvents.Completed.getStream(this.context) + .subscribe(e => this.completed(e.data)); + } +} \ No newline at end of file diff --git a/src/mol-app/controller/misc/log.ts b/src/mol-app/controller/misc/log.ts new file mode 100644 index 000000000..c694612ec --- /dev/null +++ b/src/mol-app/controller/misc/log.ts @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { List } from 'immutable' + +import { Controller } from '../controller' +import { Context } from '../../context/context'; +import { LogEvent } from '../../event/basic'; +import { Logger } from '../../service/logger'; + +export class LogController extends Controller<{ entries: List<Logger.Entry> }> { + constructor(context: Context) { + super(context, { entries: List<Logger.Entry>() }); + + LogEvent.getStream(this.context) + .subscribe(e => this.setState({ entries: this.latestState.entries.push(e.data) })) + } +} \ No newline at end of file diff --git a/src/mol-app/controller/transform/list.ts b/src/mol-app/controller/transform/list.ts new file mode 100644 index 000000000..75ee2b5c2 --- /dev/null +++ b/src/mol-app/controller/transform/list.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Context } from '../../context/context' +import { Controller } from '../controller'; +import { AnyTransform } from 'mol-view/state/transform'; +import { AnyEntity } from 'mol-view/state/entity'; + +export interface TransformListState { + entity?: AnyEntity + transforms: AnyTransform[] +} + +export class TransformListController extends Controller<TransformListState> { + constructor(context: Context) { + super(context, { transforms: [], entity: undefined }); + + context.currentTransforms.subscribe((transforms) => { + this.state.next({ transforms, entity: context.currentEntity.getValue() }) // TODO + this.setState({ transforms, entity: context.currentEntity.getValue() }) + }) + } +} \ No newline at end of file diff --git a/src/mol-app/controller/visualization/viewport.ts b/src/mol-app/controller/visualization/viewport.ts new file mode 100644 index 000000000..40a4ac53a --- /dev/null +++ b/src/mol-app/controller/visualization/viewport.ts @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +// import { throttle } from 'rxjs/operators'; +// import { interval } from 'rxjs'; + +import { shallowClone } from 'mol-util'; +import { Context } from '../../context/context' +import { Controller } from '../controller'; + +export const DefaultViewportOptions = { + clearColor: { r: 1, g: 1, b: 1 }, + enableFog: true, + cameraFOV: 30, + cameraSpeed: 4 +} +export type ViewportOptions = typeof DefaultViewportOptions + +export class ViewportController extends Controller<ViewportOptions> { + constructor(context: Context) { + super(context, shallowClone(DefaultViewportOptions)); + } +} \ No newline at end of file diff --git a/src/mol-app/event/basic.ts b/src/mol-app/event/basic.ts new file mode 100644 index 000000000..99cf366f9 --- /dev/null +++ b/src/mol-app/event/basic.ts @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Event } from './event' +import { Logger } from '../service/logger'; +import { Dispatcher } from '../service/dispatcher' +import { LayoutState } from '../controller/layout'; +import { ViewportOptions } from '../controller/visualization/viewport'; +import { Job } from '../service/job'; + +const Lane = Dispatcher.Lane; + +export const LogEvent = Event.create<Logger.Entry>('bs.Log', Lane.Log); + +export namespace CommonEvents { + export const LayoutChanged = Event.create('bs.Common.LayoutChanged', Lane.Slow); + export const ComponentsChanged = Event.create('bs.Common.ComponentsChanged', Lane.Slow); +} + +export namespace JobEvents { + export const Started = Event.create<Job.Info>('bs.Jobs.Started', Lane.Job); + export const Completed = Event.create<number>('bs.Jobs.Completed', Lane.Job); + export const StateUpdated = Event.create<Job.State>('bs.Jobs.StateUpdated', Lane.Busy); +} + +export namespace LayoutEvents { + export const SetState = Event.create<Partial<LayoutState>>('lm.cmd.Layout.SetState', Lane.Slow); + export const SetViewportOptions = Event.create<ViewportOptions>('bs.cmd.Layout.SetViewportOptions', Lane.Slow); +} diff --git a/src/mol-app/event/event.ts b/src/mol-app/event/event.ts new file mode 100644 index 000000000..4e6affecc --- /dev/null +++ b/src/mol-app/event/event.ts @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Observable } from 'rxjs'; +import { Context } from '../context/context' +import { Dispatcher } from '../service/dispatcher' + +export interface Event<T> { + type: Event.Type<T>; + data: T; +} + +export namespace Event { + export type Stream<T> = Observable<Event<T>>; + + import Lane = Dispatcher.Lane + + export type Any = Event<any> + export type AnyType = Type<any> + + export interface Type<T> { + name: string, + lane: Lane, + dispatch(context: Context, data: T): void; + getStream(context: Context): Stream<T>; + } + + const EventPrototype = { + dispatch<T>(this: any, context: Context, data: T) { context.dispatcher.dispatch({ type: this, data }) }, + getStream(this: any, context: Context) { return context.dispatcher.getStream(this); } + } + + export function create<T>(name: string, lane: Dispatcher.Lane): Type<T> { + return Object.create(EventPrototype, { + name: { writable: false, configurable: false, value: name }, + lane: { writable: false, configurable: false, value: lane } + }); + } +} \ No newline at end of file diff --git a/src/mol-app/service/dispatcher.ts b/src/mol-app/service/dispatcher.ts new file mode 100644 index 000000000..58a6345c6 --- /dev/null +++ b/src/mol-app/service/dispatcher.ts @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Subject } from 'rxjs'; +import { filter } from 'rxjs/operators'; +import { Event } from '../event/event' + +export class Dispatcher { + LOG_DISPATCH_STREAM = false; + + private lanes: Subject<Event<any>>[] = []; + constructor() { + for (let i = 0; i <= Dispatcher.Lane.Job; i++) { + this.lanes.push(new Subject<Event<any>>()); + } + } + + dispatch<T>(event: Event<T>) { + if (this.LOG_DISPATCH_STREAM) console.log(event.type.name, Dispatcher.Lane[event.type.lane], event.data); + this.lanes[event.type.lane].next(event); + } + + schedule(action: () => void, onError?: (e: string) => void, timeout = 1000 / 31) { + return setTimeout(() => { + if (onError) { + try { + action.call(null) + } catch (e) { + onError.call(null, '' + e); + } + } else { + action.call(null); + } + }, timeout); + } + + getStream<T>(type: Event.Type<T>): Event.Stream<T> { + return this.lanes[type.lane].pipe(filter(e => e.type === type)); + } + + finished() { + this.lanes.forEach(l => l.complete()); + } +} + +export namespace Dispatcher { + export enum Lane { + Slow = 0, + Fast = 1, + Log = 2, + Busy = 3, + Transformer = 4, + Job = 5 + } +} \ No newline at end of file diff --git a/src/mol-app/service/job.ts b/src/mol-app/service/job.ts new file mode 100644 index 000000000..ed2ec6a5d --- /dev/null +++ b/src/mol-app/service/job.ts @@ -0,0 +1,132 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { Context } from '../context/context' +import { JobEvents } from '../event/basic'; +import { PerformanceMonitor } from 'mol-util/performance-monitor'; +import { formatProgress } from 'mol-util'; +import { Progress, Task, Run } from 'mol-task'; + +export class Job<T> { + private info: Job.Info; + get id() { return this.info.id; } + get reportTime() { return this.info.reportTime; } + + run(context: Context) { + return this.runWithContext(context).result; + } + + runWithContext(context: Context): Job.Running<T> { + return new Job.Running(context, this.task, this.info); + } + + setReportTime(report: boolean) { + this.info.reportTime = report; + return this; + } + + constructor(public name: string, public type: Job.Type, private task: Task<T>) { + this.info = { + id: serialJobId++, + name, + type, + reportTime: false + }; + } +} + +let serialJobId = 0; +export namespace Job { + export let __DEBUG_MODE__ = false; + + export type Type = 'Normal' | 'Background' | 'Silent'; + + export interface Info { + id: number, + type: Type, + name: string, + reportTime: boolean + } + + export class Running<T> { + result: Promise<T>; + + private progressUpdated(progress: Progress) { + JobEvents.StateUpdated.dispatch(this.context, { + jobId: this.info.id, + type: this.info.type, + name: this.info.name, + message: formatProgress(progress), + abort: progress.requestAbort + }); + } + + private resolved() { + try { + this.context.performance.end('job' + this.info.id); + if (this.info.reportTime) { + let time = this.context.performance.time('job' + this.info.id); + if (this.info.type !== 'Silent') this.context.logger.info(`${this.info.name} finished in ${PerformanceMonitor.format(time)}.`) + } + } finally { + JobEvents.Completed.dispatch(this.context, this.info.id); + } + } + + private rejected(err: any) { + this.context.performance.end('job' + this.info.id); + this.context.performance.formatTime('job' + this.info.id); + + if (__DEBUG_MODE__) { + console.error(err); + } + + try { + if (this.info.type === 'Silent') { + if (err.warn) this.context.logger.warning(`Warning (${this.info.name}): ${err.message}`); + else console.error(`Error (${this.info.name})`, err); + } else { + if (err.warn) { + this.context.logger.warning(`Warning (${this.info.name}): ${err.message}`); + } else { + let e = '' + err; + if (e.indexOf('Aborted') >= 0) this.context.logger.info(`${this.info.name}: Aborted.`); + else this.context.logger.error(`Error (${this.info.name}): ${err}`); + } + } + } catch (e) { + console.error(e); + } finally { + JobEvents.Completed.dispatch(this.context, this.info.id); + } + } + + private run() { + JobEvents.Started.dispatch(this.context, this.info); + this.context.performance.start('job' + this.info.id); + + this.result = Run(this.task, (p: Progress) => this.progressUpdated(p), 250) + this.result.then(() => this.resolved()).catch(e => this.rejected(e)); + } + + constructor(private context: Context, private task: Task<T>, private info: Info) { + this.run(); + } + } + + export interface State { + jobId: number, + type: Type, + name: string, + message: string, + abort?: () => void + } + + export function create<T>(name: string, type: Type, task: Task<T>) { + return new Job<T>(name, type, task); + } +} \ No newline at end of file diff --git a/src/mol-app/service/logger.ts b/src/mol-app/service/logger.ts new file mode 100644 index 000000000..901596719 --- /dev/null +++ b/src/mol-app/service/logger.ts @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { LogEvent } from '../event/basic' +import { Context } from '../context/context' + +export class Logger { + + private log(e: Logger.Entry) { + LogEvent.dispatch(this.context, e); + } + + message(m: string) { + this.log({ type: Logger.EntryType.Message, timestamp: new Date(), message: m }); + } + + error(m: string) { + this.log({ type: Logger.EntryType.Error, timestamp: new Date(), message: m }); + } + + warning(m: string) { + this.log({ type: Logger.EntryType.Warning, timestamp: new Date(), message: m }); + } + + info(m: string) { + this.log({ type: Logger.EntryType.Info, timestamp: new Date(), message: m }); + } + + constructor(private context: Context) { + + } +} + +export namespace Logger { + export enum EntryType { + Message, + Error, + Warning, + Info + } + + export interface Entry { + type: EntryType; + timestamp: Date; + message: any + } +} \ No newline at end of file diff --git a/src/mol-app/skin/base.scss b/src/mol-app/skin/base.scss new file mode 100644 index 000000000..355f0ecca --- /dev/null +++ b/src/mol-app/skin/base.scss @@ -0,0 +1,45 @@ + +@font-face { + font-family: 'fontello'; + src: url('./fonts/fontello.eot'); + src: url('./fonts/fontello.eot#iefix') format('embedded-opentype'), + url('./fonts/fontello.woff2') format('woff2'), + url('./fonts/fontello.woff') format('woff'), + url('./fonts/fontello.ttf') format('truetype'), + url('./fonts/fontello.svg#fontello') format('svg'); + font-weight: normal; + font-style: normal; +} + +@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400italic,700); + +.molstar-plugin { + font-family: "Helvetica Neue", "Source Sans Pro", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + @import 'variables'; + + // for bootstrap + $border-radius-base: 0; + $border-radius-large: 0; + $border-radius-small: 0; + + @import 'bootstrap'; + + @import 'icons'; + @import 'layout'; + @import 'ui'; + @import 'logo'; + + .molstar-plugin-content { + color: $font-color; + } + + background: $default-background; +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap.scss b/src/mol-app/skin/bootstrap.scss new file mode 100644 index 000000000..f26fdba91 --- /dev/null +++ b/src/mol-app/skin/bootstrap.scss @@ -0,0 +1,25 @@ +/*! + * Bootstrap v3.3.6 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +// Core variables and mixins +@import "bootstrap/variables"; +@import "bootstrap/mixins"; + +// Reset and dependencies +@import "bootstrap/normalize"; + +// Core CSS +@import "bootstrap/scaffolding"; +@import "bootstrap/type"; +@import "bootstrap/forms"; +@import "bootstrap/buttons"; + +// Components +@import "bootstrap/button-groups"; +@import "bootstrap/input-groups"; + +@import "bootstrap/labels"; +@import "bootstrap/badges"; diff --git a/src/mol-app/skin/bootstrap/badges.scss b/src/mol-app/skin/bootstrap/badges.scss new file mode 100644 index 000000000..b03cc5562 --- /dev/null +++ b/src/mol-app/skin/bootstrap/badges.scss @@ -0,0 +1,68 @@ +// +// Badges +// -------------------------------------------------- + + +// Base class +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: $font-size-small; + font-weight: $badge-font-weight; + color: $badge-color; + line-height: $badge-line-height; + vertical-align: middle; + white-space: nowrap; + text-align: center; + background-color: $badge-bg; + border-radius: $badge-border-radius; + + // Empty badges collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for badges in buttons + .molstar-btn & { + position: relative; + top: -1px; + } + + .molstar-btn-xs &, + .molstar-btn-group-xs > .molstar-btn & { + top: 0; + padding: 1px 5px; + } + + // [converter] extracted a& to a.badge + + // Account for badges in navs + .list-group-item.active > &, + .nav-pills > .active > a > & { + color: $badge-active-color; + background-color: $badge-active-bg; + } + + .list-group-item > & { + float: right; + } + + .list-group-item > & + & { + margin-right: 5px; + } + + .nav-pills > li > a > & { + margin-left: 3px; + } + } + + // Hover state, but only for links + a.badge { + &:hover, + &:focus { + color: $badge-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/button-groups.scss b/src/mol-app/skin/bootstrap/button-groups.scss new file mode 100644 index 000000000..3fd6d085e --- /dev/null +++ b/src/mol-app/skin/bootstrap/button-groups.scss @@ -0,0 +1,244 @@ +// +// Button groups +// -------------------------------------------------- + +// Make the div behave like a button +.molstar-btn-group, +.molstar-btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; // match .molstar-btn alignment given font-size hack above + > .molstar-btn { + position: relative; + float: left; + // Bring the "active" button to the front + &:hover, + &:focus, + &:active, + &.active { + z-index: 2; + } + } +} + +// Prevent double borders when buttons are next to each other +.molstar-btn-group { + .molstar-btn + .molstar-btn, + .molstar-btn + .molstar-btn-group, + .molstar-btn-group + .molstar-btn, + .molstar-btn-group + .molstar-btn-group { + margin-left: -1px; + } +} + +// Optional: Group multiple button groups together for a toolbar +.molstar-btn-toolbar { + margin-left: -5px; // Offset the first child's margin + @include clearfix; + + .molstar-btn, + .molstar-btn-group, + .input-group { + float: left; + } + > .molstar-btn, + > .molstar-btn-group, + > .input-group { + margin-left: 5px; + } +} + +.molstar-btn-group > .molstar-btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} + +// Set corners individual because sometimes a single button can be in a .molstar-btn-group and we need :first-child and :last-child to both match +.molstar-btn-group > .molstar-btn:first-child { + margin-left: 0; + &:not(:last-child):not(.dropdown-toggle) { + @include border-right-radius(0); + } +} +// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it +.molstar-btn-group > .molstar-btn:last-child:not(:first-child), +.molstar-btn-group > .dropdown-toggle:not(:first-child) { + @include border-left-radius(0); +} + +// Custom edits for including molstar-btn-groups within molstar-btn-groups (useful for including dropdown buttons within a molstar-btn-group) +.molstar-btn-group > .molstar-btn-group { + float: left; +} +.molstar-btn-group > .molstar-btn-group:not(:first-child):not(:last-child) > .molstar-btn { + border-radius: 0; +} +.molstar-btn-group > .molstar-btn-group:first-child:not(:last-child) { + > .molstar-btn:last-child, + > .dropdown-toggle { + @include border-right-radius(0); + } +} +.molstar-btn-group > .molstar-btn-group:last-child:not(:first-child) > .molstar-btn:first-child { + @include border-left-radius(0); +} + +// On active and open, don't show outline +.molstar-btn-group .dropdown-toggle:active, +.molstar-btn-group.open .dropdown-toggle { + outline: 0; +} + + +// Sizing +// +// Remix the default button sizing classes into new ones for easier manipulation. + +.molstar-btn-group-xs > .molstar-btn { @extend .molstar-btn-xs; } +.molstar-btn-group-sm > .molstar-btn { @extend .molstar-btn-sm; } +.molstar-btn-group-lg > .molstar-btn { @extend .molstar-btn-lg; } + + +// Split button dropdowns +// ---------------------- + +// Give the line between buttons some depth +.molstar-btn-group > .molstar-btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; +} +.molstar-btn-group > .molstar-btn-lg + .dropdown-toggle { + padding-left: 12px; + padding-right: 12px; +} + +// The clickable button for toggling the menu +// Remove the gradient and set the same inset shadow as the :active state +.molstar-btn-group.open .dropdown-toggle { + @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + + // Show no shadow for `.molstar-btn-link` since it has no other button styles. + &.molstar-btn-link { + @include box-shadow(none); + } +} + + +// Reposition the caret +.molstar-btn .caret { + margin-left: 0; +} +// Carets in other button sizes +.molstar-btn-lg .caret { + border-width: $caret-width-large $caret-width-large 0; + border-bottom-width: 0; +} +// Upside down carets for .dropup +.dropup .molstar-btn-lg .caret { + border-width: 0 $caret-width-large $caret-width-large; +} + + +// Vertical button groups +// ---------------------- + +.molstar-btn-group-vertical { + > .molstar-btn, + > .molstar-btn-group, + > .molstar-btn-group > .molstar-btn { + display: block; + float: none; + width: 100%; + max-width: 100%; + } + + // Clear floats so dropdown menus can be properly placed + > .molstar-btn-group { + @include clearfix; + > .molstar-btn { + float: none; + } + } + + > .molstar-btn + .molstar-btn, + > .molstar-btn + .molstar-btn-group, + > .molstar-btn-group + .molstar-btn, + > .molstar-btn-group + .molstar-btn-group { + margin-top: -1px; + margin-left: 0; + } +} + +.molstar-btn-group-vertical > .molstar-btn { + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + &:first-child:not(:last-child) { + @include border-top-radius($molstar-btn-border-radius-base); + @include border-bottom-radius(0); + } + &:last-child:not(:first-child) { + @include border-top-radius(0); + @include border-bottom-radius($molstar-btn-border-radius-base); + } +} +.molstar-btn-group-vertical > .molstar-btn-group:not(:first-child):not(:last-child) > .molstar-btn { + border-radius: 0; +} +.molstar-btn-group-vertical > .molstar-btn-group:first-child:not(:last-child) { + > .molstar-btn:last-child, + > .dropdown-toggle { + @include border-bottom-radius(0); + } +} +.molstar-btn-group-vertical > .molstar-btn-group:last-child:not(:first-child) > .molstar-btn:first-child { + @include border-top-radius(0); +} + + +// Justified button groups +// ---------------------- + +.molstar-btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; + > .molstar-btn, + > .molstar-btn-group { + float: none; + display: table-cell; + width: 1%; + } + > .molstar-btn-group .molstar-btn { + width: 100%; + } + + > .molstar-btn-group .dropdown-menu { + left: auto; + } +} + + +// Checkbox and radio options +// +// In order to support the browser's form validation feedback, powered by the +// `required` attribute, we have to "hide" the inputs via `clip`. We cannot use +// `display: none;` or `visibility: hidden;` as that also hides the popover. +// Simply visually hiding the inputs via `opacity` would leave them clickable in +// certain cases which is prevented by using `clip` and `pointer-events`. +// This way, we ensure a DOM element is visible to position the popover from. +// +// See https://github.com/twbs/bootstrap/pull/12794 and +// https://github.com/twbs/bootstrap/pull/14559 for more information. + +[data-toggle="buttons"] { + > .molstar-btn, + > .molstar-btn-group > .molstar-btn { + input[type="radio"], + input[type="checkbox"] { + position: absolute; + clip: rect(0,0,0,0); + pointer-events: none; + } + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/buttons.scss b/src/mol-app/skin/bootstrap/buttons.scss new file mode 100644 index 000000000..f775ee279 --- /dev/null +++ b/src/mol-app/skin/bootstrap/buttons.scss @@ -0,0 +1,168 @@ +// +// Buttons +// -------------------------------------------------- + + +// Base styles +// -------------------------------------------------- + +.molstar-btn { + display: inline-block; + margin-bottom: 0; // For input.molstar-btn + font-weight: $molstar-btn-font-weight; + text-align: center; + vertical-align: middle; + touch-action: manipulation; + cursor: pointer; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid transparent; + white-space: nowrap; + @include button-size($padding-base-vertical, $padding-base-horizontal, $font-size-base, $line-height-base, $molstar-btn-border-radius-base); + @include user-select(none); + + &, + &:active, + &.active { + &:focus, + &.focus { + @include tab-focus; + } + } + + &:hover, + &:focus, + &.focus { + color: $molstar-btn-default-color; + text-decoration: none; + } + + &:active, + &.active { + outline: 0; + background-image: none; + @include box-shadow(inset 0 3px 5px rgba(0,0,0,.125)); + } + + &.disabled, + &[disabled], + fieldset[disabled] & { + cursor: $cursor-disabled; + @include opacity(.65); + @include box-shadow(none); + } + + // [converter] extracted a& to a.molstar-btn + } + + a.molstar-btn { + &.disabled, + fieldset[disabled] & { + pointer-events: none; // Future-proof disabling of clicks on `<a>` elements + } + } + + + // Alternate buttons + // -------------------------------------------------- + + .molstar-btn-default { + @include button-variant($molstar-btn-default-color, $molstar-btn-default-bg, $molstar-btn-default-border); + } + .molstar-btn-primary { + @include button-variant($molstar-btn-primary-color, $molstar-btn-primary-bg, $molstar-btn-primary-border); + } + // Success appears as green + .molstar-btn-success { + @include button-variant($molstar-btn-success-color, $molstar-btn-success-bg, $molstar-btn-success-border); + } + // Info appears as blue-green + .molstar-btn-info { + @include button-variant($molstar-btn-info-color, $molstar-btn-info-bg, $molstar-btn-info-border); + } + // Warning appears as orange + .molstar-btn-warning { + @include button-variant($molstar-btn-warning-color, $molstar-btn-warning-bg, $molstar-btn-warning-border); + } + // Danger and error appear as red + .molstar-btn-danger { + @include button-variant($molstar-btn-danger-color, $molstar-btn-danger-bg, $molstar-btn-danger-border); + } + + + // Link buttons + // ------------------------- + + // Make a button look and behave like a link + .molstar-btn-link { + color: $link-color; + font-weight: normal; + border-radius: 0; + + &, + &:active, + &.active, + &[disabled], + fieldset[disabled] & { + background-color: transparent; + @include box-shadow(none); + } + &, + &:hover, + &:focus, + &:active { + border-color: transparent; + } + &:hover, + &:focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + background-color: transparent; + } + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus { + color: $molstar-btn-link-disabled-color; + text-decoration: none; + } + } + } + + + // Button Sizes + // -------------------------------------------------- + + .molstar-btn-lg { + // line-height: ensure even-numbered height of button next to large input + @include button-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $molstar-btn-border-radius-large); + } + .molstar-btn-sm { + // line-height: ensure proper height of button next to small input + @include button-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $molstar-btn-border-radius-small); + } + .molstar-btn-xs { + @include button-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $line-height-small, $molstar-btn-border-radius-small); + } + + + // Block button + // -------------------------------------------------- + + .molstar-btn-block { + display: block; + width: 100%; + } + + // Vertically space out multiple block buttons + .molstar-btn-block + .molstar-btn-block { + margin-top: 5px; + } + + // Specificity overrides + input[type="submit"], + input[type="reset"], + input[type="button"] { + &.molstar-btn-block { + width: 100%; + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/forms.scss b/src/mol-app/skin/bootstrap/forms.scss new file mode 100644 index 000000000..b51eb372b --- /dev/null +++ b/src/mol-app/skin/bootstrap/forms.scss @@ -0,0 +1,617 @@ +// +// Forms +// -------------------------------------------------- + + +// Normalize non-controls +// +// Restyle and baseline non-control form elements. + +fieldset { + padding: 0; + margin: 0; + border: 0; + // Chrome and Firefox set a `min-width: min-content;` on fieldsets, + // so we reset that to ensure it behaves more like a standard block element. + // See https://github.com/twbs/bootstrap/issues/12359. + min-width: 0; + } + + legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: $line-height-computed; + font-size: ($font-size-base * 1.5); + line-height: inherit; + color: $legend-color; + border: 0; + border-bottom: 1px solid $legend-border-color; + } + + label { + display: inline-block; + max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141) + margin-bottom: 5px; + font-weight: bold; + } + + + // Normalize form controls + // + // While most of our form styles require extra classes, some basic normalization + // is required to ensure optimum display with or without those classes to better + // address browser inconsistencies. + + // Override content-box in Normalize (* isn't specific enough) + input[type="search"] { + @include box-sizing(border-box); + } + + // Position radios and checkboxes better + input[type="radio"], + input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; // IE8-9 + line-height: normal; + } + + input[type="file"] { + display: block; + } + + // Make range inputs behave like textual form controls + input[type="range"] { + display: block; + width: 100%; + } + + // Make multiple select elements height not fixed + select[multiple], + select[size] { + height: auto; + } + + // Focus for file, radio, and checkbox + input[type="file"]:focus, + input[type="radio"]:focus, + input[type="checkbox"]:focus { + @include tab-focus; + } + + // Adjust output element + output { + display: block; + padding-top: ($padding-base-vertical + 1); + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; + } + + + // Common form controls + // + // Shared size and type resets for form controls. Apply `.molstar-form-control` to any + // of the following form controls: + // + // select + // textarea + // input[type="text"] + // input[type="password"] + // input[type="datetime"] + // input[type="datetime-local"] + // input[type="date"] + // input[type="month"] + // input[type="time"] + // input[type="week"] + // input[type="number"] + // input[type="email"] + // input[type="url"] + // input[type="search"] + // input[type="tel"] + // input[type="color"] + + .molstar-form-control { + display: block; + width: 100%; + height: $input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border) + padding: $padding-base-vertical $padding-base-horizontal; + font-size: $font-size-base; + line-height: $line-height-base; + color: $input-color; + background-color: $input-bg; + background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214 + border: 1px solid $input-border; + border-radius: $input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS. + //@include box-shadow(none);//inset 0 1px 1px rgba(0,0,0,.075)); + //@include transition(border-color ease-in-out .15s, box-shadow ease-in-out .15s); + + // Customize the `:focus` state to imitate native WebKit styles. + @include molstar-form-control-focus; + + // Placeholder + @include placeholder; + + // Unstyle the caret on `<select>`s in IE10+. + &::-ms-expand { + border: 0; + background-color: transparent; + } + + // Disabled and read-only inputs + // + // HTML5 says that controls under a fieldset > legend:first-child won't be + // disabled if the fieldset is disabled. Due to implementation difficulty, we + // don't honor that edge case; we style them as disabled anyway. + &[disabled], + &[readonly], + fieldset[disabled] & { + background-color: $input-bg-disabled; + opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655 + } + + &[disabled], + fieldset[disabled] & { + cursor: $cursor-disabled; + } + + // [converter] extracted textarea& to textarea.molstar-form-control + } + + // Reset height for `textarea`s + textarea.molstar-form-control { + height: auto; + } + + + // Search inputs in iOS + // + // This overrides the extra rounded corners on search inputs in iOS so that our + // `.molstar-form-control` class can properly style them. Note that this cannot simply + // be added to `.molstar-form-control` as it's not specific enough. For details, see + // https://github.com/twbs/bootstrap/issues/11586. + + input[type="search"] { + -webkit-appearance: none; + } + + + // Special styles for iOS temporal inputs + // + // In Mobile Safari, setting `display: block` on temporal inputs causes the + // text within the input to become vertically misaligned. As a workaround, we + // set a pixel line-height that matches the given height of the input, but only + // for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848 + // + // Note that as of 8.3, iOS doesn't support `datetime` or `week`. + + @media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"], + input[type="time"], + input[type="datetime-local"], + input[type="month"] { + &.molstar-form-control { + line-height: $input-height-base; + } + + &.input-sm, + .input-group-sm & { + line-height: $input-height-small; + } + + &.input-lg, + .input-group-lg & { + line-height: $input-height-large; + } + } + } + + + // Form groups + // + // Designed to help with the organization and spacing of vertical forms. For + // horizontal forms, use the predefined grid classes. + + .form-group { + margin-bottom: $form-group-margin-bottom; + } + + + // Checkboxes and radios + // + // Indent the labels to position radios/checkboxes as hanging controls. + + .radio, + .checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; + + label { + min-height: $line-height-computed; // Ensure the input doesn't jump when there is no text + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; + } + } + .radio input[type="radio"], + .radio-inline input[type="radio"], + .checkbox input[type="checkbox"], + .checkbox-inline input[type="checkbox"] { + position: absolute; + margin-left: -20px; + margin-top: 4px \9; + } + + .radio + .radio, + .checkbox + .checkbox { + margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing + } + + // Radios and checkboxes on same line + .radio-inline, + .checkbox-inline { + position: relative; + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; + } + .radio-inline + .radio-inline, + .checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; // space out consecutive inline controls + } + + // Apply same disabled cursor tweak as for inputs + // Some special care is needed because <label>s don't inherit their parent's `cursor`. + // + // Note: Neither radios nor checkboxes can be readonly. + input[type="radio"], + input[type="checkbox"] { + &[disabled], + &.disabled, + fieldset[disabled] & { + cursor: $cursor-disabled; + } + } + // These classes are used directly on <label>s + .radio-inline, + .checkbox-inline { + &.disabled, + fieldset[disabled] & { + cursor: $cursor-disabled; + } + } + // These classes are used on elements with <label> descendants + .radio, + .checkbox { + &.disabled, + fieldset[disabled] & { + label { + cursor: $cursor-disabled; + } + } + } + + + // Static form control text + // + // Apply class to a `p` element to make any string of text align with labels in + // a horizontal form layout. + + .molstar-form-control-static { + // Size it appropriately next to real form controls + padding-top: ($padding-base-vertical + 1); + padding-bottom: ($padding-base-vertical + 1); + // Remove default margin from `p` + margin-bottom: 0; + min-height: ($line-height-computed + $font-size-base); + + &.input-lg, + &.input-sm { + padding-left: 0; + padding-right: 0; + } + } + + + // Form control sizing + // + // Build on `.molstar-form-control` with modifier classes to decrease or increase the + // height and font-size of form controls. + // + // The `.form-group-* molstar-form-control` variations are sadly duplicated to avoid the + // issue documented in https://github.com/twbs/bootstrap/issues/15074. + + @include input-size('.input-sm', $input-height-small, $padding-small-vertical, $padding-small-horizontal, $font-size-small, $line-height-small, $input-border-radius-small); + .form-group-sm { + .molstar-form-control { + height: $input-height-small; + padding: $padding-small-vertical $padding-small-horizontal; + font-size: $font-size-small; + line-height: $line-height-small; + border-radius: $input-border-radius-small; + } + select.molstar-form-control { + height: $input-height-small; + line-height: $input-height-small; + } + textarea.molstar-form-control, + select[multiple].molstar-form-control { + height: auto; + } + .molstar-form-control-static { + height: $input-height-small; + min-height: ($line-height-computed + $font-size-small); + padding: ($padding-small-vertical + 1) $padding-small-horizontal; + font-size: $font-size-small; + line-height: $line-height-small; + } + } + + @include input-size('.input-lg', $input-height-large, $padding-large-vertical, $padding-large-horizontal, $font-size-large, $line-height-large, $input-border-radius-large); + .form-group-lg { + .molstar-form-control { + height: $input-height-large; + padding: $padding-large-vertical $padding-large-horizontal; + font-size: $font-size-large; + line-height: $line-height-large; + border-radius: $input-border-radius-large; + } + select.molstar-form-control { + height: $input-height-large; + line-height: $input-height-large; + } + textarea.molstar-form-control, + select[multiple].molstar-form-control { + height: auto; + } + .molstar-form-control-static { + height: $input-height-large; + min-height: ($line-height-computed + $font-size-large); + padding: ($padding-large-vertical + 1) $padding-large-horizontal; + font-size: $font-size-large; + line-height: $line-height-large; + } + } + + + // Form control feedback states + // + // Apply contextual and semantic states to individual form controls. + + .has-feedback { + // Enable absolute positioning + position: relative; + + // Ensure icons don't overlap text + .molstar-form-control { + padding-right: ($input-height-base * 1.25); + } + } + // Feedback icon (requires .glyphicon classes) + .molstar-form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; // Ensure icon is above input groups + display: block; + width: $input-height-base; + height: $input-height-base; + line-height: $input-height-base; + text-align: center; + pointer-events: none; + } + .input-lg + .molstar-form-control-feedback, + .input-group-lg + .molstar-form-control-feedback, + .form-group-lg .molstar-form-control + .molstar-form-control-feedback { + width: $input-height-large; + height: $input-height-large; + line-height: $input-height-large; + } + .input-sm + .molstar-form-control-feedback, + .input-group-sm + .molstar-form-control-feedback, + .form-group-sm .molstar-form-control + .molstar-form-control-feedback { + width: $input-height-small; + height: $input-height-small; + line-height: $input-height-small; + } + + // Feedback states + .has-success { + @include molstar-form-control-validation($state-success-text, $state-success-text, $state-success-bg); + } + .has-warning { + @include molstar-form-control-validation($state-warning-text, $state-warning-text, $state-warning-bg); + } + .has-error { + @include molstar-form-control-validation($state-danger-text, $state-danger-text, $state-danger-bg); + } + + // Reposition feedback icon if input has visible label above + .has-feedback label { + + & ~ .molstar-form-control-feedback { + top: ($line-height-computed + 5); // Height of the `label` and its margin + } + &.sr-only ~ .molstar-form-control-feedback { + top: 0; + } + } + + + // Help text + // + // Apply to any element you wish to create light text for placement immediately + // below a form control. Use for general help, formatting, or instructional text. + + .help-block { + display: block; // account for any element using help-block + margin-top: 5px; + margin-bottom: 10px; + color: lighten($text-color, 25%); // lighten the text some for contrast + } + + + // Inline forms + // + // Make forms appear inline(-block) by adding the `.form-inline` class. Inline + // forms begin stacked on extra small (mobile) devices and then go inline when + // viewports reach <768px. + // + // Requires wrapping inputs and labels with `.form-group` for proper display of + // default HTML form controls and our custom form controls (e.g., input groups). + // + // Heads up! This is mixin-ed into `.navbar-form` in navbars.less. + + // [converter] extracted from `.form-inline` for libsass compatibility + @mixin form-inline { + + // Kick in the inline + @media (min-width: $screen-sm-min) { + // Inline-block all the things for "inline" + .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + + // In navbar-form, allow folks to *not* use `.form-group` + .molstar-form-control { + display: inline-block; + width: auto; // Prevent labels from stacking above inputs in `.form-group` + vertical-align: middle; + } + + // Make static controls behave like regular ones + .molstar-form-control-static { + display: inline-block; + } + + .input-group { + display: inline-table; + vertical-align: middle; + + .input-group-addon, + .input-group-molstar-btn, + .molstar-form-control { + width: auto; + } + } + + // Input groups need that 100% width though + .input-group > .molstar-form-control { + width: 100%; + } + + .control-label { + margin-bottom: 0; + vertical-align: middle; + } + + // Remove default margin on radios/checkboxes that were used for stacking, and + // then undo the floating of radios and checkboxes to match. + .radio, + .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + + label { + padding-left: 0; + } + } + .radio input[type="radio"], + .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + + // Re-override the feedback icon. + .has-feedback .molstar-form-control-feedback { + top: 0; + } + } + } + // [converter] extracted as `@mixin form-inline` for libsass compatibility + .form-inline { + @include form-inline; + } + + + + // Horizontal forms + // + // Horizontal forms are built on grid classes and allow you to create forms with + // labels on the left and inputs on the right. + + .form-horizontal { + + // Consistent vertical alignment of radios and checkboxes + // + // Labels also get some reset styles, but that is scoped to a media query below. + .radio, + .checkbox, + .radio-inline, + .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: ($padding-base-vertical + 1); // Default padding plus a border + } + // Account for padding we're adding to ensure the alignment and of help text + // and other content below items + .radio, + .checkbox { + min-height: ($line-height-computed + ($padding-base-vertical + 1)); + } + + // Make form groups behave like rows + .form-group { + @include make-row; + } + + // Reset spacing and right align labels, but scope to media queries so that + // labels on narrow viewports stack the same as a default form example. + @media (min-width: $screen-sm-min) { + .control-label { + text-align: right; + margin-bottom: 0; + padding-top: ($padding-base-vertical + 1); // Default padding plus a border + } + } + + // Validation states + // + // Reposition the icon because it's now within a grid column and columns have + // `position: relative;` on them. Also accounts for the grid gutter padding. + .has-feedback .molstar-form-control-feedback { + right: floor(($grid-gutter-width / 2)); + } + + // Form group sizes + // + // Quick utility class for applying `.input-lg` and `.input-sm` styles to the + // inputs and labels within a `.form-group`. + .form-group-lg { + @media (min-width: $screen-sm-min) { + .control-label { + padding-top: ($padding-large-vertical + 1); + font-size: $font-size-large; + } + } + } + .form-group-sm { + @media (min-width: $screen-sm-min) { + .control-label { + padding-top: ($padding-small-vertical + 1); + font-size: $font-size-small; + } + } + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/input-groups.scss b/src/mol-app/skin/bootstrap/input-groups.scss new file mode 100644 index 000000000..5f57fc3df --- /dev/null +++ b/src/mol-app/skin/bootstrap/input-groups.scss @@ -0,0 +1,171 @@ +// +// Input groups +// -------------------------------------------------- + +// Base styles +// ------------------------- +.input-group { + position: relative; // For dropdowns + display: table; + border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table + + // Undo padding and float of grid classes + &[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; + } + + .molstar-form-control { + // Ensure that the input is always above the *appended* addon button for + // proper border colors. + position: relative; + z-index: 2; + + // IE9 fubars the placeholder attribute in text inputs and the arrows on + // select elements in input groups. To fix it, we float the input. Details: + // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855 + float: left; + + width: 100%; + margin-bottom: 0; + + &:focus { + z-index: 3; + } + } + } + + // Sizing options + // + // Remix the default form control sizing classes into new ones for easier + // manipulation. + + .input-group-lg > .molstar-form-control, + .input-group-lg > .input-group-addon, + .input-group-lg > .input-group-molstar-btn > .molstar-btn { + @extend .input-lg; + } + .input-group-sm > .molstar-form-control, + .input-group-sm > .input-group-addon, + .input-group-sm > .input-group-molstar-btn > .molstar-btn { + @extend .input-sm; + } + + + // Display as table-cell + // ------------------------- + .input-group-addon, + .input-group-molstar-btn, + .input-group .molstar-form-control { + display: table-cell; + + &:not(:first-child):not(:last-child) { + border-radius: 0; + } + } + // Addon and addon wrapper for buttons + .input-group-addon, + .input-group-molstar-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; // Match the inputs + } + + // Text input groups + // ------------------------- + .input-group-addon { + padding: $padding-base-vertical $padding-base-horizontal; + font-size: $font-size-base; + font-weight: normal; + line-height: 1; + color: $input-color; + text-align: center; + background-color: $input-group-addon-bg; + border: 1px solid $input-group-addon-border-color; + border-radius: $input-border-radius; + + // Sizing + &.input-sm { + padding: $padding-small-vertical $padding-small-horizontal; + font-size: $font-size-small; + border-radius: $input-border-radius-small; + } + &.input-lg { + padding: $padding-large-vertical $padding-large-horizontal; + font-size: $font-size-large; + border-radius: $input-border-radius-large; + } + + // Nuke default margins from checkboxes and radios to vertically center within. + input[type="radio"], + input[type="checkbox"] { + margin-top: 0; + } + } + + // Reset rounded corners + .input-group .molstar-form-control:first-child, + .input-group-addon:first-child, + .input-group-molstar-btn:first-child > .molstar-btn, + .input-group-molstar-btn:first-child > .molstar-btn-group > .molstar-btn, + .input-group-molstar-btn:first-child > .dropdown-toggle, + .input-group-molstar-btn:last-child > .molstar-btn:not(:last-child):not(.dropdown-toggle), + .input-group-molstar-btn:last-child > .molstar-btn-group:not(:last-child) > .molstar-btn { + @include border-right-radius(0); + } + .input-group-addon:first-child { + border-right: 0; + } + .input-group .molstar-form-control:last-child, + .input-group-addon:last-child, + .input-group-molstar-btn:last-child > .molstar-btn, + .input-group-molstar-btn:last-child > .molstar-btn-group > .molstar-btn, + .input-group-molstar-btn:last-child > .dropdown-toggle, + .input-group-molstar-btn:first-child > .molstar-btn:not(:first-child), + .input-group-molstar-btn:first-child > .molstar-btn-group:not(:first-child) > .molstar-btn { + @include border-left-radius(0); + } + .input-group-addon:last-child { + border-left: 0; + } + + // Button input groups + // ------------------------- + .input-group-molstar-btn { + position: relative; + // Jankily prevent input button groups from wrapping with `white-space` and + // `font-size` in combination with `inline-block` on buttons. + font-size: 0; + white-space: nowrap; + + // Negative margin for spacing, position for bringing hovered/focused/actived + // element above the siblings. + > .molstar-btn { + position: relative; + + .molstar-btn { + margin-left: -1px; + } + // Bring the "active" button to the front + &:hover, + &:focus, + &:active { + z-index: 2; + } + } + + // Negative margin to only have a 1px border between the two + &:first-child { + > .molstar-btn, + > .molstar-btn-group { + margin-right: -1px; + } + } + &:last-child { + > .molstar-btn, + > .molstar-btn-group { + z-index: 2; + margin-left: -1px; + } + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/labels.scss b/src/mol-app/skin/bootstrap/labels.scss new file mode 100644 index 000000000..c453cd417 --- /dev/null +++ b/src/mol-app/skin/bootstrap/labels.scss @@ -0,0 +1,66 @@ +// +// Labels +// -------------------------------------------------- + +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: $label-color; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; + + // [converter] extracted a& to a.label + + // Empty labels collapse automatically (not available in IE8) + &:empty { + display: none; + } + + // Quick fix for labels in buttons + .molstar-btn & { + position: relative; + top: -1px; + } + } + + // Add hover effects, but only for links + a.label { + &:hover, + &:focus { + color: $label-link-hover-color; + text-decoration: none; + cursor: pointer; + } + } + + // Colors + // Contextual variations (linked labels get darker on :hover) + + .label-default { + @include label-variant($label-default-bg); + } + + .label-primary { + @include label-variant($label-primary-bg); + } + + .label-success { + @include label-variant($label-success-bg); + } + + .label-info { + @include label-variant($label-info-bg); + } + + .label-warning { + @include label-variant($label-warning-bg); + } + + .label-danger { + @include label-variant($label-danger-bg); + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins.scss b/src/mol-app/skin/bootstrap/mixins.scss new file mode 100644 index 000000000..94b12fed2 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins.scss @@ -0,0 +1,40 @@ +// Mixins +// -------------------------------------------------- + +// Utilities +// @import "mixins/hide-text"; +@import "mixins/opacity"; +@import "mixins/image"; +@import "mixins/labels"; +// @import "mixins/reset-filter"; +// @import "mixins/resize"; +// @import "mixins/responsive-visibility"; +// @import "mixins/size"; +@import "mixins/tab-focus"; +// @import "mixins/reset-text"; +@import "mixins/text-emphasis"; +@import "mixins/text-overflow"; +@import "mixins/vendor-prefixes"; + +// Components +// @import "mixins/alerts"; +@import "mixins/buttons"; +// @import "mixins/panels"; +// @import "mixins/pagination"; +// @import "mixins/list-group"; +// @import "mixins/nav-divider"; +@import "mixins/forms"; +// @import "mixins/progress-bar"; +// @import "mixins/table-row"; + +// Skins +@import "mixins/background-variant"; +@import "mixins/border-radius"; +// @import "mixins/gradients"; + +// Layout +@import "mixins/clearfix"; +// @import "mixins/center-block"; +// @import "mixins/nav-vertical-align"; +// @import "mixins/grid-framework"; +@import "mixins/grid"; \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/background-variant.scss b/src/mol-app/skin/bootstrap/mixins/background-variant.scss new file mode 100644 index 000000000..a3044fff2 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/background-variant.scss @@ -0,0 +1,12 @@ +// Contextual backgrounds + +// [converter] $parent hack +@mixin bg-variant($parent, $color) { + #{$parent} { + background-color: $color; + } + a#{$parent}:hover, + a#{$parent}:focus { + background-color: darken($color, 10%); + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/border-radius.scss b/src/mol-app/skin/bootstrap/mixins/border-radius.scss new file mode 100644 index 000000000..2ba5d06e8 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/border-radius.scss @@ -0,0 +1,18 @@ +// Single side border-radius + +@mixin border-top-radius($radius) { + border-top-right-radius: $radius; + border-top-left-radius: $radius; +} +@mixin border-right-radius($radius) { + border-bottom-right-radius: $radius; + border-top-right-radius: $radius; +} +@mixin border-bottom-radius($radius) { + border-bottom-right-radius: $radius; + border-bottom-left-radius: $radius; +} +@mixin border-left-radius($radius) { + border-bottom-left-radius: $radius; + border-top-left-radius: $radius; +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/buttons.scss b/src/mol-app/skin/bootstrap/mixins/buttons.scss new file mode 100644 index 000000000..c7be1237a --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/buttons.scss @@ -0,0 +1,65 @@ +// Button variants +// +// Easily pump out default styles, as well as :hover, :focus, :active, +// and disabled options for all buttons + +@mixin button-variant($color, $background, $border) { + color: $color; + background-color: $background; + border-color: $border; + + &:focus, + &.focus { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 25%); + } + &:hover { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 12%); + } + &:active, + &.active, + .open > &.dropdown-toggle { + color: $color; + background-color: darken($background, 10%); + border-color: darken($border, 12%); + + &:hover, + &:focus, + &.focus { + color: $color; + background-color: darken($background, 17%); + border-color: darken($border, 25%); + } + } + &:active, + &.active, + .open > &.dropdown-toggle { + background-image: none; + } + &.disabled, + &[disabled], + fieldset[disabled] & { + &:hover, + &:focus, + &.focus { + background-color: $background; + border-color: $border; + } + } + + .badge { + color: $background; + background-color: $color; + } + } + + // Button sizes + @mixin button-size($padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + border-radius: $border-radius; + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/clearfix.scss b/src/mol-app/skin/bootstrap/mixins/clearfix.scss new file mode 100644 index 000000000..12d42afa7 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/clearfix.scss @@ -0,0 +1,22 @@ +// Clearfix +// +// For modern browsers +// 1. The space content is one way to avoid an Opera bug when the +// contenteditable attribute is included anywhere else in the document. +// Otherwise it causes space to appear at the top and bottom of elements +// that are clearfixed. +// 2. The use of `table` rather than `block` is only necessary if using +// `:before` to contain the top-margins of child elements. +// +// Source: http://nicolasgallagher.com/micro-clearfix-hack/ + +@mixin clearfix() { + &:before, + &:after { + content: " "; // 1 + display: table; // 2 + } + &:after { + clear: both; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/forms.scss b/src/mol-app/skin/bootstrap/mixins/forms.scss new file mode 100644 index 000000000..2bfe4f00a --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/forms.scss @@ -0,0 +1,88 @@ +// Form validation states +// +// Used in forms.less to generate the form validation CSS for warnings, errors, +// and successes. + +@mixin molstar-form-control-validation($text-color: #555, $border-color: #ccc, $background-color: #f5f5f5) { + // Color the label and help text + .help-block, + .control-label, + .radio, + .checkbox, + .radio-inline, + .checkbox-inline, + &.radio label, + &.checkbox label, + &.radio-inline label, + &.checkbox-inline label { + color: $text-color; + } + // Set the border and box shadow on specific inputs to match + .molstar-form-control { + border-color: $border-color; + @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work + &:focus { + border-color: darken($border-color, 10%); + $shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten($border-color, 20%); + @include box-shadow($shadow); + } + } + // Set validation states also for addons + .input-group-addon { + color: $text-color; + border-color: $border-color; + background-color: $background-color; + } + // Optional feedback icon + .molstar-form-control-feedback { + color: $text-color; + } + } + + + // Form control focus state + // + // Generate a customized focus state and for any input with the specified color, + // which defaults to the `$input-border-focus` variable. + // + // We highly encourage you to not customize the default value, but instead use + // this to tweak colors on an as-needed basis. This aesthetic change is based on + // WebKit's default styles, but applicable to a wider range of browsers. Its + // usability and accessibility should be taken into account with any change. + // + // Example usage: change the default blue border and shadow to white for better + // contrast against a dark gray background. + @mixin molstar-form-control-focus($color: $input-border-focus) { + $color-rgba: rgba(red($color), green($color), blue($color), .6); + &:focus { + border-color: $color; + outline: 0; + @include box-shadow(inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px $color-rgba); + } + } + + // Form control sizing + // + // Relative text size, padding, and border-radii changes for form controls. For + // horizontal sizing, wrap controls in the predefined grid classes. `<select>` + // element gets special love because it's special, and that's a fact! + // [converter] $parent hack + @mixin input-size($parent, $input-height, $padding-vertical, $padding-horizontal, $font-size, $line-height, $border-radius) { + #{$parent} { + height: $input-height; + padding: $padding-vertical $padding-horizontal; + font-size: $font-size; + line-height: $line-height; + border-radius: $border-radius; + } + + select#{$parent} { + height: $input-height; + line-height: $input-height; + } + + textarea#{$parent}, + select[multiple]#{$parent} { + height: auto; + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/grid.scss b/src/mol-app/skin/bootstrap/mixins/grid.scss new file mode 100644 index 000000000..7457a0aba --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/grid.scss @@ -0,0 +1,122 @@ +// Grid system +// +// Generate semantic grid columns with these mixins. + +// Centered container element +@mixin container-fixed($gutter: $grid-gutter-width) { + margin-right: auto; + margin-left: auto; + padding-left: floor(($gutter / 2)); + padding-right: ceil(($gutter / 2)); + @include clearfix; + } + + // Creates a wrapper for a series of columns + @mixin make-row($gutter: $grid-gutter-width) { + margin-left: ceil(($gutter / -2)); + margin-right: floor(($gutter / -2)); + @include clearfix; + } + + // Generate the extra small columns + @mixin make-xs-column($columns, $gutter: $grid-gutter-width) { + position: relative; + float: left; + width: percentage(($columns / $grid-columns)); + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + } + @mixin make-xs-column-offset($columns) { + margin-left: percentage(($columns / $grid-columns)); + } + @mixin make-xs-column-push($columns) { + left: percentage(($columns / $grid-columns)); + } + @mixin make-xs-column-pull($columns) { + right: percentage(($columns / $grid-columns)); + } + + // Generate the small columns + @mixin make-sm-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-sm-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } + } + @mixin make-sm-column-offset($columns) { + @media (min-width: $screen-sm-min) { + margin-left: percentage(($columns / $grid-columns)); + } + } + @mixin make-sm-column-push($columns) { + @media (min-width: $screen-sm-min) { + left: percentage(($columns / $grid-columns)); + } + } + @mixin make-sm-column-pull($columns) { + @media (min-width: $screen-sm-min) { + right: percentage(($columns / $grid-columns)); + } + } + + // Generate the medium columns + @mixin make-md-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-md-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } + } + @mixin make-md-column-offset($columns) { + @media (min-width: $screen-md-min) { + margin-left: percentage(($columns / $grid-columns)); + } + } + @mixin make-md-column-push($columns) { + @media (min-width: $screen-md-min) { + left: percentage(($columns / $grid-columns)); + } + } + @mixin make-md-column-pull($columns) { + @media (min-width: $screen-md-min) { + right: percentage(($columns / $grid-columns)); + } + } + + // Generate the large columns + @mixin make-lg-column($columns, $gutter: $grid-gutter-width) { + position: relative; + min-height: 1px; + padding-left: ($gutter / 2); + padding-right: ($gutter / 2); + + @media (min-width: $screen-lg-min) { + float: left; + width: percentage(($columns / $grid-columns)); + } + } + @mixin make-lg-column-offset($columns) { + @media (min-width: $screen-lg-min) { + margin-left: percentage(($columns / $grid-columns)); + } + } + @mixin make-lg-column-push($columns) { + @media (min-width: $screen-lg-min) { + left: percentage(($columns / $grid-columns)); + } + } + @mixin make-lg-column-pull($columns) { + @media (min-width: $screen-lg-min) { + right: percentage(($columns / $grid-columns)); + } + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/image.scss b/src/mol-app/skin/bootstrap/mixins/image.scss new file mode 100644 index 000000000..608cad5a5 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/image.scss @@ -0,0 +1,33 @@ +// Image Mixins +// - Responsive image +// - Retina image + + +// Responsive image +// +// Keep images from scaling beyond the width of their parents. +@mixin img-responsive($display: block) { + display: $display; + max-width: 100%; // Part 1: Set a maximum relative to the parent + height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching +} + + +// Retina image +// +// Short retina mixin for setting background-image and -size. Note that the +// spelling of `min--moz-device-pixel-ratio` is intentional. +@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-1x}"), "#{$file-1x}")); + + @media + only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and ( min--moz-device-pixel-ratio: 2), + only screen and ( -o-min-device-pixel-ratio: 2/1), + only screen and ( min-device-pixel-ratio: 2), + only screen and ( min-resolution: 192dpi), + only screen and ( min-resolution: 2dppx) { + background-image: url(if($bootstrap-sass-asset-helper, twbs-image-path("#{$file-2x}"), "#{$file-2x}")); + background-size: $width-1x $height-1x; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/labels.scss b/src/mol-app/skin/bootstrap/mixins/labels.scss new file mode 100644 index 000000000..1353b098d --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/labels.scss @@ -0,0 +1,12 @@ +// Labels + +@mixin label-variant($color) { + background-color: $color; + + &[href] { + &:hover, + &:focus { + background-color: darken($color, 10%); + } + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/opacity.scss b/src/mol-app/skin/bootstrap/mixins/opacity.scss new file mode 100644 index 000000000..f04356c6b --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/opacity.scss @@ -0,0 +1,8 @@ +// Opacity + +@mixin opacity($opacity) { + opacity: $opacity; + // IE8 filter + $opacity-ie: ($opacity * 100); + filter: alpha(opacity=$opacity-ie); +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/tab-focus.scss b/src/mol-app/skin/bootstrap/mixins/tab-focus.scss new file mode 100644 index 000000000..414babf68 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/tab-focus.scss @@ -0,0 +1,9 @@ +// WebKit-style focus + +@mixin tab-focus() { + // Default + outline: thin dotted; + // WebKit + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss b/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss new file mode 100644 index 000000000..dddb9ae25 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/text-emphasis.scss @@ -0,0 +1,12 @@ +// Typography + +// [converter] $parent hack +@mixin text-emphasis-variant($parent, $color) { + #{$parent} { + color: $color; + } + a#{$parent}:hover, + a#{$parent}:focus { + color: darken($color, 10%); + } +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/text-overflow.scss b/src/mol-app/skin/bootstrap/mixins/text-overflow.scss new file mode 100644 index 000000000..b6ac5f707 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/text-overflow.scss @@ -0,0 +1,8 @@ +// Text overflow +// Requires inline-block or block for proper styling + +@mixin text-overflow() { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss b/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss new file mode 100644 index 000000000..b657e7a26 --- /dev/null +++ b/src/mol-app/skin/bootstrap/mixins/vendor-prefixes.scss @@ -0,0 +1,222 @@ +// Vendor Prefixes +// +// All vendor mixins are deprecated as of v3.2.0 due to the introduction of +// Autoprefixer in our Gruntfile. They have been removed in v4. + +// - Animations +// - Backface visibility +// - Box shadow +// - Box sizing +// - Content columns +// - Hyphens +// - Placeholder text +// - Transformations +// - Transitions +// - User Select + + +// Animations +@mixin animation($animation) { + -webkit-animation: $animation; + -o-animation: $animation; + animation: $animation; + } + @mixin animation-name($name) { + -webkit-animation-name: $name; + animation-name: $name; + } + @mixin animation-duration($duration) { + -webkit-animation-duration: $duration; + animation-duration: $duration; + } + @mixin animation-timing-function($timing-function) { + -webkit-animation-timing-function: $timing-function; + animation-timing-function: $timing-function; + } + @mixin animation-delay($delay) { + -webkit-animation-delay: $delay; + animation-delay: $delay; + } + @mixin animation-iteration-count($iteration-count) { + -webkit-animation-iteration-count: $iteration-count; + animation-iteration-count: $iteration-count; + } + @mixin animation-direction($direction) { + -webkit-animation-direction: $direction; + animation-direction: $direction; + } + @mixin animation-fill-mode($fill-mode) { + -webkit-animation-fill-mode: $fill-mode; + animation-fill-mode: $fill-mode; + } + + // Backface visibility + // Prevent browsers from flickering when using CSS 3D transforms. + // Default value is `visible`, but can be changed to `hidden` + + @mixin backface-visibility($visibility) { + -webkit-backface-visibility: $visibility; + -moz-backface-visibility: $visibility; + backface-visibility: $visibility; + } + + // Drop shadows + // + // Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's + // supported browsers that have box shadow capabilities now support it. + + @mixin box-shadow($shadow...) { + -webkit-box-shadow: $shadow; // iOS <4.3 & Android <4.1 + box-shadow: $shadow; + } + + // Box sizing + @mixin box-sizing($boxmodel) { + -webkit-box-sizing: $boxmodel; + -moz-box-sizing: $boxmodel; + box-sizing: $boxmodel; + } + + // CSS3 Content Columns + @mixin content-columns($column-count, $column-gap: $grid-gutter-width) { + -webkit-column-count: $column-count; + -moz-column-count: $column-count; + column-count: $column-count; + -webkit-column-gap: $column-gap; + -moz-column-gap: $column-gap; + column-gap: $column-gap; + } + + // Optional hyphenation + @mixin hyphens($mode: auto) { + word-wrap: break-word; + -webkit-hyphens: $mode; + -moz-hyphens: $mode; + -ms-hyphens: $mode; // IE10+ + -o-hyphens: $mode; + hyphens: $mode; + } + + // Placeholder text + @mixin placeholder($color: $input-color-placeholder) { + // Firefox + &::-moz-placeholder { + color: $color; + opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526 + } + &:-ms-input-placeholder { color: $color; } // Internet Explorer 10+ + &::-webkit-input-placeholder { color: $color; } // Safari and Chrome + } + + // Transformations + @mixin scale($ratio...) { + -webkit-transform: scale($ratio); + -ms-transform: scale($ratio); // IE9 only + -o-transform: scale($ratio); + transform: scale($ratio); + } + + @mixin scaleX($ratio) { + -webkit-transform: scaleX($ratio); + -ms-transform: scaleX($ratio); // IE9 only + -o-transform: scaleX($ratio); + transform: scaleX($ratio); + } + @mixin scaleY($ratio) { + -webkit-transform: scaleY($ratio); + -ms-transform: scaleY($ratio); // IE9 only + -o-transform: scaleY($ratio); + transform: scaleY($ratio); + } + @mixin skew($x, $y) { + -webkit-transform: skewX($x) skewY($y); + -ms-transform: skewX($x) skewY($y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+ + -o-transform: skewX($x) skewY($y); + transform: skewX($x) skewY($y); + } + @mixin translate($x, $y) { + -webkit-transform: translate($x, $y); + -ms-transform: translate($x, $y); // IE9 only + -o-transform: translate($x, $y); + transform: translate($x, $y); + } + @mixin translate3d($x, $y, $z) { + -webkit-transform: translate3d($x, $y, $z); + transform: translate3d($x, $y, $z); + } + @mixin rotate($degrees) { + -webkit-transform: rotate($degrees); + -ms-transform: rotate($degrees); // IE9 only + -o-transform: rotate($degrees); + transform: rotate($degrees); + } + @mixin rotateX($degrees) { + -webkit-transform: rotateX($degrees); + -ms-transform: rotateX($degrees); // IE9 only + -o-transform: rotateX($degrees); + transform: rotateX($degrees); + } + @mixin rotateY($degrees) { + -webkit-transform: rotateY($degrees); + -ms-transform: rotateY($degrees); // IE9 only + -o-transform: rotateY($degrees); + transform: rotateY($degrees); + } + @mixin perspective($perspective) { + -webkit-perspective: $perspective; + -moz-perspective: $perspective; + perspective: $perspective; + } + @mixin perspective-origin($perspective) { + -webkit-perspective-origin: $perspective; + -moz-perspective-origin: $perspective; + perspective-origin: $perspective; + } + @mixin transform-origin($origin) { + -webkit-transform-origin: $origin; + -moz-transform-origin: $origin; + -ms-transform-origin: $origin; // IE9 only + transform-origin: $origin; + } + + + // Transitions + + @mixin transition($transition...) { + -webkit-transition: $transition; + -o-transition: $transition; + transition: $transition; + } + @mixin transition-property($transition-property...) { + -webkit-transition-property: $transition-property; + transition-property: $transition-property; + } + @mixin transition-delay($transition-delay) { + -webkit-transition-delay: $transition-delay; + transition-delay: $transition-delay; + } + @mixin transition-duration($transition-duration...) { + -webkit-transition-duration: $transition-duration; + transition-duration: $transition-duration; + } + @mixin transition-timing-function($timing-function) { + -webkit-transition-timing-function: $timing-function; + transition-timing-function: $timing-function; + } + @mixin transition-transform($transition...) { + -webkit-transition: -webkit-transform $transition; + -moz-transition: -moz-transform $transition; + -o-transition: -o-transform $transition; + transition: transform $transition; + } + + + // User select + // For selecting text on the page + + @mixin user-select($select) { + -webkit-user-select: $select; + -moz-user-select: $select; + -ms-user-select: $select; // IE10+ + user-select: $select; + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/normalize.scss b/src/mol-app/skin/bootstrap/normalize.scss new file mode 100644 index 000000000..7caf32b8c --- /dev/null +++ b/src/mol-app/skin/bootstrap/normalize.scss @@ -0,0 +1,424 @@ +/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ + +// +// 1. Set default font family to sans-serif. +// 2. Prevent iOS and IE text size adjust after device orientation change, +// without disabling user zoom. +// + +html { + font-family: sans-serif; // 1 + -ms-text-size-adjust: 100%; // 2 + -webkit-text-size-adjust: 100%; // 2 + } + + // + // Remove default margin. + // + + body { + margin: 0; + } + + // HTML5 display definitions + // ========================================================================== + + // + // Correct `block` display not defined for any HTML5 element in IE 8/9. + // Correct `block` display not defined for `details` or `summary` in IE 10/11 + // and Firefox. + // Correct `block` display not defined for `main` in IE 11. + // + + article, + aside, + details, + figcaption, + figure, + footer, + header, + hgroup, + main, + menu, + nav, + section, + summary { + display: block; + } + + // + // 1. Correct `inline-block` display not defined in IE 8/9. + // 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + // + + audio, + canvas, + progress, + video { + display: inline-block; // 1 + vertical-align: baseline; // 2 + } + + // + // Prevent modern browsers from displaying `audio` without controls. + // Remove excess height in iOS 5 devices. + // + + audio:not([controls]) { + display: none; + height: 0; + } + + // + // Address `[hidden]` styling not present in IE 8/9/10. + // Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22. + // + + [hidden], + template { + display: none; + } + + // Links + // ========================================================================== + + // + // Remove the gray background color from active links in IE 10. + // + + a { + background-color: transparent; + } + + // + // Improve readability of focused elements when they are also in an + // active/hover state. + // + + a:active, + a:hover { + outline: 0; + } + + // Text-level semantics + // ========================================================================== + + // + // Address styling not present in IE 8/9/10/11, Safari, and Chrome. + // + + abbr[title] { + border-bottom: 1px dotted; + } + + // + // Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + // + + b, + strong { + font-weight: bold; + } + + // + // Address styling not present in Safari and Chrome. + // + + dfn { + font-style: italic; + } + + // + // Address variable `h1` font-size and margin within `section` and `article` + // contexts in Firefox 4+, Safari, and Chrome. + // + + h1 { + font-size: 2em; + margin: 0.67em 0; + } + + // + // Address styling not present in IE 8/9. + // + + mark { + background: #ff0; + color: #000; + } + + // + // Address inconsistent and variable font size in all browsers. + // + + small { + font-size: 80%; + } + + // + // Prevent `sub` and `sup` affecting `line-height` in all browsers. + // + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sup { + top: -0.5em; + } + + sub { + bottom: -0.25em; + } + + // Embedded content + // ========================================================================== + + // + // Remove border when inside `a` element in IE 8/9/10. + // + + img { + border: 0; + } + + // + // Correct overflow not hidden in IE 9/10/11. + // + + svg:not(:root) { + overflow: hidden; + } + + // Grouping content + // ========================================================================== + + // + // Address margin not present in IE 8/9 and Safari. + // + + figure { + margin: 1em 40px; + } + + // + // Address differences between Firefox and other browsers. + // + + hr { + box-sizing: content-box; + height: 0; + } + + // + // Contain overflow in all browsers. + // + + pre { + overflow: auto; + } + + // + // Address odd `em`-unit font size rendering in all browsers. + // + + code, + kbd, + pre, + samp { + font-family: monospace, monospace; + font-size: 1em; + } + + // Forms + // ========================================================================== + + // + // Known limitation: by default, Chrome and Safari on OS X allow very limited + // styling of `select`, unless a `border` property is set. + // + + // + // 1. Correct color not being inherited. + // Known issue: affects color of disabled elements. + // 2. Correct font properties not being inherited. + // 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + // + + button, + input, + optgroup, + select, + textarea { + color: inherit; // 1 + font: inherit; // 2 + margin: 0; // 3 + } + + // + // Address `overflow` set to `hidden` in IE 8/9/10/11. + // + + button { + overflow: visible; + } + + // + // Address inconsistent `text-transform` inheritance for `button` and `select`. + // All other form control elements do not inherit `text-transform` values. + // Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + // Correct `select` style inheritance in Firefox. + // + + button, + select { + text-transform: none; + } + + // + // 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + // and `video` controls. + // 2. Correct inability to style clickable `input` types in iOS. + // 3. Improve usability and consistency of cursor style between image-type + // `input` and others. + // + + button, + html input[type="button"], // 1 + input[type="reset"], + input[type="submit"] { + -webkit-appearance: button; // 2 + cursor: pointer; // 3 + } + + // + // Re-set default cursor for disabled elements. + // + + button[disabled], + html input[disabled] { + cursor: default; + } + + // + // Remove inner padding and border in Firefox 4+. + // + + button::-moz-focus-inner, + input::-moz-focus-inner { + border: 0; + padding: 0; + } + + // + // Address Firefox 4+ setting `line-height` on `input` using `!important` in + // the UA stylesheet. + // + + input { + line-height: normal; + } + + // + // It's recommended that you don't attempt to style these elements. + // Firefox's implementation doesn't respect box-sizing, padding, or width. + // + // 1. Address box sizing set to `content-box` in IE 8/9/10. + // 2. Remove excess padding in IE 8/9/10. + // + + input[type="checkbox"], + input[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 + } + + // + // Fix the cursor style for Chrome's increment/decrement buttons. For certain + // `font-size` values of the `input`, it causes the cursor style of the + // decrement button to change from `default` to `text`. + // + + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + height: auto; + } + + // + // 1. Address `appearance` set to `searchfield` in Safari and Chrome. + // 2. Address `box-sizing` set to `border-box` in Safari and Chrome. + // + + input[type="search"] { + -webkit-appearance: textfield; // 1 + box-sizing: content-box; //2 + } + + // + // Remove inner padding and search cancel button in Safari and Chrome on OS X. + // Safari (but not Chrome) clips the cancel button when the search input has + // padding (and `textfield` appearance). + // + + input[type="search"]::-webkit-search-cancel-button, + input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + } + + // + // Define consistent border, margin, and padding. + // + + fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; + } + + // + // 1. Correct `color` not being inherited in IE 8/9/10/11. + // 2. Remove padding so people aren't caught out if they zero out fieldsets. + // + + legend { + border: 0; // 1 + padding: 0; // 2 + } + + // + // Remove default vertical scrollbar in IE 8/9/10/11. + // + + textarea { + overflow: auto; + } + + // + // Don't inherit the `font-weight` (applied by a rule above). + // NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + // + + optgroup { + font-weight: bold; + } + + // Tables + // ========================================================================== + + // + // Remove most spacing between table cells. + // + + table { + border-collapse: collapse; + border-spacing: 0; + } + + td, + th { + padding: 0; + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/scaffolding.scss b/src/mol-app/skin/bootstrap/scaffolding.scss new file mode 100644 index 000000000..556b6acdf --- /dev/null +++ b/src/mol-app/skin/bootstrap/scaffolding.scss @@ -0,0 +1,161 @@ +// +// Scaffolding +// -------------------------------------------------- + + +// Reset the box-sizing +// +// Heads up! This reset may cause conflicts with some third-party widgets. +// For recommendations on resolving such conflicts, see +// http://getbootstrap.com/getting-started/#third-box-sizing +* { + @include box-sizing(border-box); + } + *:before, + *:after { + @include box-sizing(border-box); + } + + + // Body reset + + html { + font-size: 10px; + -webkit-tap-highlight-color: rgba(0,0,0,0); + } + + body { + font-family: $font-family-base; + font-size: $font-size-base; + line-height: $line-height-base; + color: $text-color; + background-color: $body-bg; + } + + // Reset fonts for relevant elements + input, + button, + select, + textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; + } + + + // Links + + a { + color: $link-color; + text-decoration: none; + + &:hover, + &:focus { + color: $link-hover-color; + text-decoration: $link-hover-decoration; + } + + &:focus { + @include tab-focus; + } + } + + + // Figures + // + // We reset this here because previously Normalize had no `figure` margins. This + // ensures we don't break anyone's use of the element. + + figure { + margin: 0; + } + + + // Images + + img { + vertical-align: middle; + } + + // Responsive images (ensure images don't scale beyond their parents) + .img-responsive { + @include img-responsive; + } + + // Rounded corners + .img-rounded { + border-radius: $border-radius-large; + } + + // Image thumbnails + // + // Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`. + .img-thumbnail { + padding: $thumbnail-padding; + line-height: $line-height-base; + background-color: $thumbnail-bg; + border: 1px solid $thumbnail-border; + border-radius: $thumbnail-border-radius; + @include transition(all .2s ease-in-out); + + // Keep them at most 100% wide + @include img-responsive(inline-block); + } + + // Perfect circle + .img-circle { + border-radius: 50%; // set radius in percents + } + + + // Horizontal rules + + hr { + margin-top: $line-height-computed; + margin-bottom: $line-height-computed; + border: 0; + border-top: 1px solid $hr-border; + } + + + // Only display content to screen readers + // + // See: http://a11yproject.com/posts/how-to-hide-content/ + + .sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; + } + + // Use in conjunction with .sr-only to only display content when it's focused. + // Useful for "Skip to main content" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1 + // Credit: HTML5 Boilerplate + + .sr-only-focusable { + &:active, + &:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; + } + } + + + // iOS "clickable elements" fix for role="button" + // + // Fixes "clickability" issue (and more generally, the firing of events such as focus as well) + // for traditionally non-focusable elements with role="button" + // see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile + + [role="button"] { + cursor: pointer; + } \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/type.scss b/src/mol-app/skin/bootstrap/type.scss new file mode 100644 index 000000000..92c0b63ca --- /dev/null +++ b/src/mol-app/skin/bootstrap/type.scss @@ -0,0 +1,298 @@ +// +// Typography +// -------------------------------------------------- + + +// Headings +// ------------------------- + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + font-family: $headings-font-family; + font-weight: $headings-font-weight; + line-height: $headings-line-height; + color: $headings-color; + + small, + .small { + font-weight: normal; + line-height: 1; + color: $headings-small-color; + } +} + +h1, .h1, +h2, .h2, +h3, .h3 { + margin-top: $line-height-computed; + margin-bottom: ($line-height-computed / 2); + + small, + .small { + font-size: 65%; + } +} +h4, .h4, +h5, .h5, +h6, .h6 { + margin-top: ($line-height-computed / 2); + margin-bottom: ($line-height-computed / 2); + + small, + .small { + font-size: 75%; + } +} + +h1, .h1 { font-size: $font-size-h1; } +h2, .h2 { font-size: $font-size-h2; } +h3, .h3 { font-size: $font-size-h3; } +h4, .h4 { font-size: $font-size-h4; } +h5, .h5 { font-size: $font-size-h5; } +h6, .h6 { font-size: $font-size-h6; } + + +// Body text +// ------------------------- + +p { + margin: 0 0 ($line-height-computed / 2); +} + +.lead { + margin-bottom: $line-height-computed; + font-size: floor(($font-size-base * 1.15)); + font-weight: 300; + line-height: 1.4; + + @media (min-width: $screen-sm-min) { + font-size: ($font-size-base * 1.5); + } +} + + +// Emphasis & misc +// ------------------------- + +// Ex: (12px small font / 14px base font) * 100% = about 85% +small, +.small { + font-size: floor((100% * $font-size-small / $font-size-base)); +} + +mark, +.mark { + background-color: $state-warning-bg; + padding: .2em; +} + +// Alignment +.text-left { text-align: left; } +.text-right { text-align: right; } +.text-center { text-align: center; } +.text-justify { text-align: justify; } +.text-nowrap { white-space: nowrap; } + +// Transformation +.text-lowercase { text-transform: lowercase; } +.text-uppercase { text-transform: uppercase; } +.text-capitalize { text-transform: capitalize; } + +// Contextual colors +.text-muted { + color: $text-muted; +} + +@include text-emphasis-variant('.text-primary', $brand-primary); + +@include text-emphasis-variant('.text-success', $state-success-text); + +@include text-emphasis-variant('.text-info', $state-info-text); + +@include text-emphasis-variant('.text-warning', $state-warning-text); + +@include text-emphasis-variant('.text-danger', $state-danger-text); + +// Contextual backgrounds +// For now we'll leave these alongside the text classes until v4 when we can +// safely shift things around (per SemVer rules). +.bg-primary { + // Given the contrast here, this is the only class to have its color inverted + // automatically. + color: #fff; +} +@include bg-variant('.bg-primary', $brand-primary); + +@include bg-variant('.bg-success', $state-success-bg); + +@include bg-variant('.bg-info', $state-info-bg); + +@include bg-variant('.bg-warning', $state-warning-bg); + +@include bg-variant('.bg-danger', $state-danger-bg); + + +// Page header +// ------------------------- + +.page-header { + padding-bottom: (($line-height-computed / 2) - 1); + margin: ($line-height-computed * 2) 0 $line-height-computed; + border-bottom: 1px solid $page-header-border-color; +} + + +// Lists +// ------------------------- + +// Unordered and Ordered lists +ul, +ol { + margin-top: 0; + margin-bottom: ($line-height-computed / 2); + ul, + ol { + margin-bottom: 0; + } +} + +// List options + +// [converter] extracted from `.lm-list-unstyled` for libsass compatibility +@mixin lm-list-unstyled { + padding-left: 0; + list-style: none; +} +// [converter] extracted as `@mixin lm-list-unstyled` for libsass compatibility +.lm-list-unstyled { + @include lm-list-unstyled; +} + + +// Inline turns list items into inline-block +.list-inline { + @include lm-list-unstyled; + margin-left: -5px; + + > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; + } +} + +// Description Lists +dl { + margin-top: 0; // Remove browser default + margin-bottom: $line-height-computed; +} +dt, +dd { + line-height: $line-height-base; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; // Undo browser default +} + +// Horizontal description lists +// +// Defaults to being stacked without any of the below styles applied, until the +// grid breakpoint is reached (default of ~768px). + +.dl-horizontal { + dd { + @include clearfix; // Clear the floated `dt` if an empty `dd` is present + } + + @media (min-width: $dl-horizontal-breakpoint) { + dt { + float: left; + width: ($dl-horizontal-offset - 20); + clear: left; + text-align: right; + @include text-overflow; + } + dd { + margin-left: $dl-horizontal-offset; + } + } +} + + +// Misc +// ------------------------- + +// Abbreviations and acronyms +abbr[title], +// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257 +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted $abbr-border-color; +} +.initialism { + font-size: 90%; + @extend .text-uppercase; +} + +// Blockquotes +blockquote { + padding: ($line-height-computed / 2) $line-height-computed; + margin: 0 0 $line-height-computed; + font-size: $blockquote-font-size; + border-left: 5px solid $blockquote-border-color; + + p, + ul, + ol { + &:last-child { + margin-bottom: 0; + } + } + + // Note: Deprecated small and .small as of v3.1.0 + // Context: https://github.com/twbs/bootstrap/issues/11660 + footer, + small, + .small { + display: block; + font-size: 80%; // back to default font-size + line-height: $line-height-base; + color: $blockquote-small-color; + + &:before { + content: '\2014 \00A0'; // em dash, nbsp + } + } +} + +// Opposite alignment of blockquote +// +// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0. +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid $blockquote-border-color; + border-left: 0; + text-align: right; + + // Account for citation + footer, + small, + .small { + &:before { content: ''; } + &:after { + content: '\00A0 \2014'; // nbsp, em dash + } + } +} + +// Addresses +address { + margin-bottom: $line-height-computed; + font-style: normal; + line-height: $line-height-base; +} \ No newline at end of file diff --git a/src/mol-app/skin/bootstrap/variables.scss b/src/mol-app/skin/bootstrap/variables.scss new file mode 100644 index 000000000..ead0db3c4 --- /dev/null +++ b/src/mol-app/skin/bootstrap/variables.scss @@ -0,0 +1,353 @@ +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +$gray-base: #000 !default; +$gray-darker: lighten($gray-base, 13.5%) !default; // #222 +$gray-dark: lighten($gray-base, 20%) !default; // #333 +$gray: lighten($gray-base, 33.5%) !default; // #555 +$gray-light: lighten($gray-base, 46.7%) !default; // #777 +$gray-lighter: lighten($gray-base, 93.5%) !default; // #eee + +$brand-primary: darken(#428bca, 6.5%) !default; // #337ab7 +$brand-success: #5cb85c !default; +$brand-info: #5bc0de !default; +$brand-warning: #f0ad4e !default; +$brand-danger: #d9534f !default; + + +//== Scaffolding +// +//## Settings for some of the most global styles. + +//** Background color for `<body>`. +$body-bg: #fff !default; +//** Global text color on `<body>`. +$text-color: $gray-dark !default; + +//** Global textual link color. +$link-color: $brand-primary !default; +//** Link hover color set via `darken()` function. +$link-hover-color: darken($link-color, 15%) !default; +//** Link hover decoration. +$link-hover-decoration: underline !default; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +$font-family-sans-serif: "Helvetica Neue", Helvetica, Arial, sans-serif !default; +$font-family-serif: Georgia, "Times New Roman", Times, serif !default; +//** Default monospace fonts for `<code>`, `<kbd>`, and `<pre>`. +$font-family-monospace: Menlo, Monaco, Consolas, "Courier New", monospace !default; +$font-family-base: $font-family-sans-serif !default; + +$font-size-base: 14px !default; +$font-size-large: ceil(($font-size-base * 1.25)) !default; // ~18px +$font-size-small: ceil(($font-size-base * 0.85)) !default; // ~12px + +$font-size-h1: floor(($font-size-base * 2.6)) !default; // ~36px +$font-size-h2: floor(($font-size-base * 2.15)) !default; // ~30px +$font-size-h3: ceil(($font-size-base * 1.7)) !default; // ~24px +$font-size-h4: ceil(($font-size-base * 1.25)) !default; // ~18px +$font-size-h5: $font-size-base !default; +$font-size-h6: ceil(($font-size-base * 0.85)) !default; // ~12px + +//** Unit-less `line-height` for use in components like buttons. +$line-height-base: 1.428571429 !default; // 20/14 +//** Computed "line-height" (`font-size` * `line-height`) for use with `margin`, `padding`, etc. +$line-height-computed: floor(($font-size-base * $line-height-base)) !default; // ~20px + +//** By default, this inherits from the `<body>`. +$headings-font-family: inherit !default; +$headings-font-weight: 500 !default; +$headings-line-height: 1.1 !default; +$headings-color: inherit !default; + + +//== Components +// +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). + +$padding-base-vertical: 6px !default; +$padding-base-horizontal: 12px !default; + +$padding-large-vertical: 10px !default; +$padding-large-horizontal: 16px !default; + +$padding-small-vertical: 5px !default; +$padding-small-horizontal: 10px !default; + +$padding-xs-vertical: 1px !default; +$padding-xs-horizontal: 5px !default; + +$line-height-large: 1.3333333 !default; // extra decimals for Win 8.1 Chrome +$line-height-small: 1.5 !default; + +$border-radius-base: 4px !default; +$border-radius-large: 6px !default; +$border-radius-small: 3px !default; + +//** Global color for active items (e.g., navs or dropdowns). +$component-active-color: #fff !default; +//** Global background color for active items (e.g., navs or dropdowns). +$component-active-bg: $brand-primary !default; + +//** Width of the `border` for generating carets that indicator dropdowns. +$caret-width-base: 4px !default; +//** Carets increase slightly in size for larger components. +$caret-width-large: 5px !default; + + +//== Buttons +// +//## For each of Bootstrap's buttons, define text, background and border color. + +$molstar-btn-font-weight: normal !default; + +$molstar-btn-default-color: #333 !default; +$molstar-btn-default-bg: #fff !default; +$molstar-btn-default-border: #ccc !default; + +$molstar-btn-primary-color: #fff !default; +$molstar-btn-primary-bg: $brand-primary !default; +$molstar-btn-primary-border: darken($molstar-btn-primary-bg, 5%) !default; + +$molstar-btn-success-color: #fff !default; +$molstar-btn-success-bg: $brand-success !default; +$molstar-btn-success-border: darken($molstar-btn-success-bg, 5%) !default; + +$molstar-btn-info-color: #fff !default; +$molstar-btn-info-bg: $brand-info !default; +$molstar-btn-info-border: darken($molstar-btn-info-bg, 5%) !default; + +$molstar-btn-warning-color: #fff !default; +$molstar-btn-warning-bg: $brand-warning !default; +$molstar-btn-warning-border: darken($molstar-btn-warning-bg, 5%) !default; + +$molstar-btn-danger-color: #fff !default; +$molstar-btn-danger-bg: $brand-danger !default; +$molstar-btn-danger-border: darken($molstar-btn-danger-bg, 5%) !default; + +$molstar-btn-link-disabled-color: $gray-light !default; + +// Allows for customizing button radius independently from global border radius +$molstar-btn-border-radius-base: $border-radius-base !default; +$molstar-btn-border-radius-large: $border-radius-large !default; +$molstar-btn-border-radius-small: $border-radius-small !default; + + +//== Media queries breakpoints +// +//## Define the breakpoints at which your layout will change, adapting to different screen sizes. + +// Extra small screen / phone +//** Deprecated `$screen-xs` as of v3.0.1 +$screen-xs: 480px !default; +//** Deprecated `$screen-xs-min` as of v3.2.0 +$screen-xs-min: $screen-xs !default; +//** Deprecated `$screen-phone` as of v3.0.1 +$screen-phone: $screen-xs-min !default; + +// Small screen / tablet +//** Deprecated `$screen-sm` as of v3.0.1 +$screen-sm: 768px !default; +$screen-sm-min: $screen-sm !default; +//** Deprecated `$screen-tablet` as of v3.0.1 +$screen-tablet: $screen-sm-min !default; + +// Medium screen / desktop +//** Deprecated `$screen-md` as of v3.0.1 +$screen-md: 992px !default; +$screen-md-min: $screen-md !default; +//** Deprecated `$screen-desktop` as of v3.0.1 +$screen-desktop: $screen-md-min !default; + +// Large screen / wide desktop +//** Deprecated `$screen-lg` as of v3.0.1 +$screen-lg: 1200px !default; +$screen-lg-min: $screen-lg !default; +//** Deprecated `$screen-lg-desktop` as of v3.0.1 +$screen-lg-desktop: $screen-lg-min !default; + +// So media queries don't overlap when required, provide a maximum +$screen-xs-max: ($screen-sm-min - 1) !default; +$screen-sm-max: ($screen-md-min - 1) !default; +$screen-md-max: ($screen-lg-min - 1) !default; + + +//== Grid system +// +//## Define your custom responsive grid. + +//** Number of columns in the grid. +$grid-columns: 12 !default; +//** Padding between columns. Gets divided in half for the left and right. +$grid-gutter-width: 30px !default; +// Navbar collapse +//** Point at which the navbar becomes uncollapsed. +$grid-float-breakpoint: $screen-sm-min !default; +//** Point at which the navbar begins collapsing. +$grid-float-breakpoint-max: ($grid-float-breakpoint - 1) !default; + + +//== Forms +// +//## + +//** `<input>` background color +$input-bg: #fff !default; +//** `<input disabled>` background color +$input-bg-disabled: $gray-lighter !default; + +//** Text color for `<input>`s +$input-color: $gray !default; +//** `<input>` border color +$input-border: #ccc !default; + +// TODO: Rename `$input-border-radius` to `$input-border-radius-base` in v4 +//** Default `.lm-form-control` border radius +// This has no effect on `<select>`s in some browsers, due to the limited stylability of `<select>`s in CSS. +$input-border-radius: $border-radius-base !default; +//** Large `.lm-form-control` border radius +$input-border-radius-large: $border-radius-large !default; +//** Small `.lm-form-control` border radius +$input-border-radius-small: $border-radius-small !default; + +//** Border color for inputs on focus +$input-border-focus: #66afe9 !default; + +//** Placeholder text color +$input-color-placeholder: #999 !default; + +//** Default `.lm-form-control` height +$input-height-base: ($line-height-computed + ($padding-base-vertical * 2) + 2) !default; +//** Large `.lm-form-control` height +$input-height-large: (ceil($font-size-large * $line-height-large) + ($padding-large-vertical * 2) + 2) !default; +//** Small `.lm-form-control` height +$input-height-small: (floor($font-size-small * $line-height-small) + ($padding-small-vertical * 2) + 2) !default; + +//** `.form-group` margin +$form-group-margin-bottom: 15px !default; + +$legend-color: $gray-dark !default; +$legend-border-color: #e5e5e5 !default; + +//** Background color for textual input addons +$input-group-addon-bg: $gray-lighter !default; +//** Border color for textual input addons +$input-group-addon-border-color: $input-border !default; + +//** Disabled cursor for form controls and buttons. +$cursor-disabled: not-allowed !default; + + +//== Thumbnails +// +//## + +//** Padding around the thumbnail image +$thumbnail-padding: 4px !default; +//** Thumbnail background color +$thumbnail-bg: $body-bg !default; +//** Thumbnail border color +$thumbnail-border: #ddd !default; +//** Thumbnail border radius +$thumbnail-border-radius: $border-radius-base !default; + +//** Custom text color for thumbnail captions +$thumbnail-caption-color: $text-color !default; +//** Padding around the thumbnail caption +$thumbnail-caption-padding: 9px !default; + + +//== Type +// +//## + +//** Horizontal offset for forms and lists. +$component-offset-horizontal: 180px !default; +//** Text muted color +$text-muted: $gray-light !default; +//** Abbreviations and acronyms border color +$abbr-border-color: $gray-light !default; +//** Headings small color +$headings-small-color: $gray-light !default; +//** Blockquote small color +$blockquote-small-color: $gray-light !default; +//** Blockquote font size +$blockquote-font-size: ($font-size-base * 1.25) !default; +//** Blockquote border color +$blockquote-border-color: $gray-lighter !default; +//** Page header border color +$page-header-border-color: $gray-lighter !default; +//** Width of horizontal description list titles +$dl-horizontal-offset: $component-offset-horizontal !default; +//** Point at which .dl-horizontal becomes horizontal +$dl-horizontal-breakpoint: $grid-float-breakpoint !default; +//** Horizontal line color. +$hr-border: $gray-lighter !default; + + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + +$state-success-text: #3c763d !default; +$state-success-bg: #dff0d8 !default; +$state-success-border: darken(adjust-hue($state-success-bg, -10), 5%) !default; + +$state-info-text: #31708f !default; +$state-info-bg: #d9edf7 !default; +$state-info-border: darken(adjust-hue($state-info-bg, -10), 7%) !default; + +$state-warning-text: #8a6d3b !default; +$state-warning-bg: #fcf8e3 !default; +$state-warning-border: darken(adjust-hue($state-warning-bg, -10), 5%) !default; + +$state-danger-text: #a94442 !default; +$state-danger-bg: #f2dede !default; +$state-danger-border: darken(adjust-hue($state-danger-bg, -10), 5%) !default; + + +//== Labels +// +//## + +//** Default label background color +$label-default-bg: $gray-light !default; +//** Primary label background color +$label-primary-bg: $brand-primary !default; +//** Success label background color +$label-success-bg: $brand-success !default; +//** Info label background color +$label-info-bg: $brand-info !default; +//** Warning label background color +$label-warning-bg: $brand-warning !default; +//** Danger label background color +$label-danger-bg: $brand-danger !default; + +//** Default label text color +$label-color: #fff !default; +//** Default text color of a linked label +$label-link-hover-color: #fff !default; + + +//== Badges +// +//## + +$badge-color: #fff !default; +//** Linked badge text color on hover +$badge-link-hover-color: #fff !default; +$badge-bg: $gray-light !default; + +//** Badge text color in active nav link +$badge-active-color: $link-color !default; +//** Badge background color in active nav link +$badge-active-bg: #fff !default; + +$badge-font-weight: bold !default; +$badge-line-height: 1 !default; +$badge-border-radius: 10px !default; \ No newline at end of file diff --git a/src/mol-app/skin/colors/blue.scss b/src/mol-app/skin/colors/blue.scss new file mode 100644 index 000000000..c31a02bd7 --- /dev/null +++ b/src/mol-app/skin/colors/blue.scss @@ -0,0 +1,24 @@ +$default-background: #2D3E50; +$font-color: #EDF1F2; +$hover-font-color: #3B9AD9; +$entity-current-font-color: #FFFFFF; +$lm-btn-remove-background: #BF3A31; +$lm-btn-remove-hover-font-color:#ffffff; +$lm-btn-commit-on-font-color: #ffffff; +$entity-badge-font-color: #ccd4e0; + +// used in LOG +$log-message: #0CCA5D; +$log-info: #5E3673; +$log-warning: #FCC937; +$log-error: #FD354B; + +$logo-background: rgba(0,0,0,0.75); + +@function color-lower-contrast($color, $amount) { + @return darken($color, $amount); +} + +@function color-increase-contrast($color, $amount) { + @return lighten($color, $amount); +} \ No newline at end of file diff --git a/src/mol-app/skin/colors/dark.scss b/src/mol-app/skin/colors/dark.scss new file mode 100644 index 000000000..6df758473 --- /dev/null +++ b/src/mol-app/skin/colors/dark.scss @@ -0,0 +1,22 @@ +$default-background: #111318; +$font-color: #ccd4e0; +$hover-font-color: #51A2FB; +$entity-current-font-color: #68BEFD; +$molstar-btn-remove-background: #DE0A28; +$molstar-btn-remove-hover-font-color:#F2F4F7; +$molstar-btn-commit-on-font-color: #68BEFD; +$entity-badge-font-color: #ccd4e0; + +// used in LOG +$log-message: #0CCA5D; +$log-info: #5E3673; +$log-warning: #FCC937; +$log-error: #FD354B; + +@function color-lower-contrast($color, $amount) { + @return darken($color, $amount); +} + +@function color-increase-contrast($color, $amount) { + @return lighten($color, $amount); +} \ No newline at end of file diff --git a/src/mol-app/skin/colors/light.scss b/src/mol-app/skin/colors/light.scss new file mode 100644 index 000000000..678b744d5 --- /dev/null +++ b/src/mol-app/skin/colors/light.scss @@ -0,0 +1,30 @@ +// this is complement of the dark theme + +@function compl($color) { + @return rgb(255 - red($color), 255 - green($color), 255 - blue($color)); +} + +$default-background: compl(#111318); +$font-color: compl(#ccd4e0); +$hover-font-color: compl(#51A2FB); +$entity-current-font-color: compl(#68BEFD); +$molstar-btn-commit-on-font-color: compl(#68BEFD); +$entity-badge-font-color: lighten(#ccd4e0,10%); +$molstar-btn-remove-background: #DE0A28; +$molstar-btn-remove-hover-font-color:#F2F4F7; + +// used in LOG +$log-message: #0CCA5D; +$log-info: #5E3673; +$log-warning: #FCC937; +$log-error: #FD354B; + +$logo-background: rgba(204,201,193,0.85); + +@function color-lower-contrast($color, $amount) { + @return lighten($color, $amount); +} + +@function color-increase-contrast($color, $amount) { + @return darken($color, $amount); +} \ No newline at end of file diff --git a/src/mol-app/skin/components/controls-base.scss b/src/mol-app/skin/components/controls-base.scss new file mode 100644 index 000000000..e1f17a683 --- /dev/null +++ b/src/mol-app/skin/components/controls-base.scss @@ -0,0 +1,144 @@ +.molstar-btn { + padding: 0 $control-spacing; + line-height: $row-height; + border: none; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +.molstar-btn, .molstar-btn:active, .molstar-btn-link:focus, .molstar-btn:hover { + outline: none !important; +} + +.molstar-btn-icon { + height: $row-height; + width: $row-height; + line-height: $row-height; + padding: 0; + text-align: center; +} + +.molstar-btn-link { + .molstar-icon { + font-size: 100%; + } +} + +.molstar-btn-link, .molstar-btn-link:active, .molstar-btn-link:focus { + color: $molstar-btn-link-font-color; + text-decoration: none; +} + +.molstar-btn-link:hover { + color: $hover-font-color; + text-decoration: none; +} + +.molstar-btn-link-toggle-on { + color: $molstar-btn-link-toggle-on-font-color; +} + +.molstar-btn-link-toggle-off, .molstar-btn-link-toggle-off:active, .molstar-btn-link-toggle-off:focus { + color: $molstar-btn-link-toggle-off-font-color; +} + +.molstar-btn-link-toggle-off:hover, .molstar-btn-link-toggle-on:hover { + color: $hover-font-color; +} + +@mixin molstar-btn($name, $font, $bg) { + .molstar-btn-#{$name}, .molstar-btn-#{$name}:active, .molstar-btn-#{$name}:focus { + color: $font; + background: $bg; + } + .molstar-btn-#{$name}:hover { + color: $hover-font-color; + background: color-lower-contrast($bg, 2.5%); + } + + .molstar-btn-#{$name}[disabled], .molstar-btn-#{$name}[disabled]:hover, + .molstar-btn-#{$name}[disabled]:active, .molstar-btn-#{$name}[disabled]:focus { + color: color-lower-contrast($font, 1%); + } +} + +@include molstar-btn('remove', $molstar-btn-remove-font-color, $molstar-btn-remove-background); +@include molstar-btn('action', $font-color, $molstar-btn-action-background); +@include molstar-btn('commit-on', $molstar-btn-commit-on-font-color, $molstar-btn-commit-on-background); +@include molstar-btn('commit-off', $molstar-btn-commit-off-font-color, $molstar-btn-commit-off-background); + +.molstar-btn-remove:hover { + color: $molstar-btn-remove-hover-font-color; +} +.molstar-btn-commit-on:hover { + color: $molstar-btn-commit-on-hover-font-color; +} + +.molstar-btn-action { + height: $row-height; + line-height: $row-height; +} + +.molstar-form-control { + width: 100%; + background: $molstar-form-control-background; + color: $font-color; + border: none !important; + padding: 0 $control-spacing; + line-height: $row-height - 2px; + height: $row-height; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + box-shadow: none !important; + + &:hover { + color: $hover-font-color; + background-color: color-increase-contrast($molstar-form-control-background, 5%); + border: none; + outline-offset: -1px; + outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%); + } + + &:active, &:focus { + color: $font-color; + background-color: $molstar-form-control-background; + border: none; + outline-offset: 0; + outline: none; + } +} + +.molstar-btn-commit { + text-align: right; + padding-top: 0; + padding-bottom: 0; + padding-right: $control-spacing; + padding-left: 0; + line-height: $row-height; + border: none; + overflow: hidden; + + .molstar-icon { + display: block-inline; + line-height: $row-height; + margin-right: $control-spacing; + width: $row-height; + text-align: center; + float: left; + } +} + +select.molstar-form-control { + background: none; + background-color: $molstar-form-control-background; + background-size: 8px 12px; + background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC); + background-repeat: no-repeat; + background-position: right $control-spacing top (($row-height - 12px) / 2); +} + +select.molstar-form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 $font-color; +} \ No newline at end of file diff --git a/src/mol-app/skin/components/controls.scss b/src/mol-app/skin/components/controls.scss new file mode 100644 index 000000000..2c8aeda0a --- /dev/null +++ b/src/mol-app/skin/components/controls.scss @@ -0,0 +1,197 @@ + +.molstar-control-row { + position: relative; + height: $row-height; + background: $default-background; + margin-top: 1px; + + > span { + line-height: $row-height; + display: block; + width: $control-label-width + $control-spacing; + text-align: right; + padding: 0 $control-spacing; + color: color-lower-contrast($font-color, 15%); + + @include non-selectable; + } + + select, button, input[type=text] { + @extend .molstar-form-control; + } + + button { + @extend .molstar-btn; + @extend .molstar-btn-block; + } + + > div:nth-child(2) { + background: $molstar-form-control-background; + position: absolute; + left: $control-label-width + $control-spacing; + top: 0; + right: 0; + bottom: 0; + } +} + +.molstar-control-group { + position: relative; +} + +.molstar-toggle-button { + .molstar-icon { + display: inline-block; + margin-right: 6px; + } + + > div > button:hover { + border-color: color-increase-contrast($molstar-form-control-background, 5%) !important; + border: none; + outline-offset: -1px !important; + outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%) !important; + } +} + +.molstar-slider { + > div { + > div:first-child { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + width: 100%; + padding-right: 50px; + display: table; + + > div { + height: $row-height; + display: table-cell; + vertical-align: middle; + padding: 0 ($control-spacing + 4px); + } + } + > div:last-child { + position: absolute; + height: $row-height; + right: 0; + width: 50px; + top: 0; + bottom: 0; + } + } + + input[type=text] { + text-align: right; + } + + input[type=range] { + width: 100%; + } +} + +.molstar-toggle-color-picker { + button { + border: $control-spacing solid $molstar-form-control-background !important; + margin: 0; + text-align: center; + padding-right: $control-spacing; + padding-left: $control-spacing; + + &:hover { + border-color: color-increase-contrast($molstar-form-control-background, 5%) !important; + border: none; + outline-offset: -1px !important; + outline: 1px solid color-increase-contrast($molstar-form-control-background, 20%) !important; + } + } + + .molstar-color-picker { + position: absolute; + z-index: 100000; + background: $default-background; + border-top: 1px solid $default-background; + padding-bottom: $control-spacing / 2; + width: 100%; + + // input[type=text] { + // background: $molstar-form-control-background !important; + // } + } +} + +.molstar-toggle-color-picker-above { + .molstar-color-picker { + top: -2 * 32px - 16px - $control-spacing / 2; + height: 2 * 32px + 16px + $control-spacing / 2; + } +} + +.molstar-toggle-color-picker-below { + .molstar-color-picker { + top: $row-height; + height: 2 * 32px + 16px; + } +} + + +.molstar-control-subgroup { + margin-top: 1px; + + .molstar-control-row { + margin-left: $control-spacing !important; + > span { + width: $control-label-width !important; + } + + > div:nth-child(2) { + left: $control-label-width !important; + } + } +} + +.molstar-conrol-group-expander { + display: block; + position: absolute; + line-height: $row-height; + padding: 0; + left: 0; + top: 0; + width: $control-label-width + $control-spacing; + text-align: left; + + .molstar-icon { + line-height: $row-height - 3; + width: $row-height - 1; + text-align: center; + display: inline-block; + font-size: 100%; + } +} + +.molstar-plugin-layout_controls { + position: absolute; + left: $control-spacing; + top: $control-spacing; +} + +.molstar-plugin-layout_controls > button:first-child { + margin-right: 6px; +} + +.molstar-empty-control { + display: none; +} + +.molstar-control .molstar-btn-block { + margin-bottom: 0px; + margin-top: 0px; +} + +.molstar-row-text { + > div { + line-height: $row-height; + text-align: center; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/components/entity.scss b/src/mol-app/skin/components/entity.scss new file mode 100644 index 000000000..3ce2e2305 --- /dev/null +++ b/src/mol-app/skin/components/entity.scss @@ -0,0 +1,225 @@ + + +.molstar-entity-tree { + overflow: hidden; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: 0; + padding-top: $control-spacing; + background: $control-background; + + .molstar-entity-tree-children { + overflow-x: hidden; + overflow-y: auto; + position: absolute; + bottom: 0; + left: 0; + right: 0; + top: $row-height + $control-spacing + 1; + padding: $control-spacing 0; + } +} + +.molstar-entity-store-header { + height: $row-height + 1; + position: relative; + + > span { + margin-left: 6px; + display: inline-block; + line-height: $row-height; + font-weight: bold; + + @include non-selectable + } + + button { + display: block !important; + height: $row-height !important; + margin: 0 !important; + line-height: $row-height !important; + border: none !important; + position: absolute; + top: 0; + } + + border-bottom: 1px solid $border-color; +} + +.molstar-entity-store-root { + overflow-x: hidden; + overflow-y: auto; + position: absolute; + bottom: 0; + left: 0; + top: $row-height + 1; + right: 0; +} + +.molstar-entity-tree-entry { + height: $row-height + 1; + position: relative; + border-bottom: 1px solid $control-background; +} + +.molstar-entity-tree-entry-current { + background: color-lower-contrast($default-background, 4%) !important; + + .molstar-entity-tree-entry-label { + color: $entity-current-font-color; + font-weight: bold; + .molstar-entity-tree-entry-label-tag { + font-weight: normal; + } + &:hover { + color: $hover-font-color; + } + } +} + +.molstar-entity-tree-entry-current-path { + background: color-lower-contrast($default-background, 2%) !important; + .molstar-entity-tree-entry-label { + color: color-lower-contrast($entity-current-font-color, 5%); + &:hover { + color: $hover-font-color; + } + } +} + +.molstar-entity-tree-entry button, .molstar-entity-tree-entry > div { + display: block !important; + height: $row-height !important; + margin: 0 !important; + line-height: $row-height !important; + border: none !important; + position: absolute; + top: 0; +} + + +.molstar-entity-tree-entry-toggle-group { + width: $row-height; + height: $row-height; + padding: 0; + left: 0; +} + +.molstar-entity-tree-entry-toggle-visible { + width: $row-height; + right: 0; //$row-height + 6; + padding: 0 !important; + font-size: 80%; +} + +.molstar-entity-tree-entry-toggle-visible-full, .molstar-entity-tree-entry-toggle-visible-full:focus, .molstar-entity-tree-entry-toggle-visible-full:active { + color: $entity-color-fully-visible; +} + +.molstar-entity-tree-entry-toggle-visible-partial, .molstar-entity-tree-entry-toggle-visible-partial:focus, .molstar-entity-tree-entry-toggle-visible-partial:active { + color: $entity-color-partialy-visible; +} + +.molstar-entity-tree-entry-toggle-visible-none, .molstar-entity-tree-entry-toggle-visible-none:focus, .molstar-entity-tree-entry-toggle-visible-none:active { + //background: transparent !important; + color: $entity-color-not-visible; +} + +.molstar-entity-tree-entry-remove { + width: $row-height; + height: $row-height; + right: $row-height; + padding: 0 !important; + text-align: center; + font-size: 80%; + color: color-lower-contrast($font-color, 66%) +} + +.molstar-entity-tree-entry-body { + position: absolute; + left: $row-height; + border-radius: 0 0 0 $entity-subtree-offset; + right: 0; + background: $default-background; +} + +.molstar-entity-tree-entry .molstar-entity-badge { + width: $row-height; + position: absolute; + height: $row-height; + left: 0; + top: 0; + border-radius: 0 $entity-subtree-offset 0 $entity-subtree-offset; +} + +.molstar-entity-tree-entry-label-wrap { + right: 2 * $row-height; + overflow: hidden; + left: $row-height; + height: $row-height; + position: absolute; +} + +.molstar-entity-tree-entry-label { + position: absolute; + right: 0; + top: 0; + left: 0; + text-align: left !important; + width: 100%; + padding: 0 $control-spacing !important; +} + +.molstar-entity-tree-entry-label-tag { + color: $entity-tag-color; + font-size: 70%; + display: inline-block; + margin-left: 6px; +} + + +.molstar-entity-tree-children-wrap { + padding-left: $entity-subtree-offset; +} + +.molstar-entity-tree-root { + > .molstar-entity-tree-entry { + .molstar-entity-badge { + border-top-right-radius: 0; + } + .molstar-entity-tree-entry-label { + font-weight: bold; + } + .molstar-entity-tree-entry-toggle-group { + display: none !important; + } + .molstar-entity-tree-entry-body { + left: $row-height - $entity-subtree-offset !important; + } + background: $default-background; + border-bottom: 1px solid $border-color; + } + + > .molstar-entity-tree-children-wrap { + margin-top: $control-spacing; + padding-left: 0 !important; + } +} + +.molstar-panel { + .molstar-entity-tree-entry-toggle-visible { + position: absolute; + top: 0; + right: 0; + height: $row-height; + font-size: 100%; + + background: $default-background; //color-increase-contrast($default-background, 4%); + } + + // .molstar-entity-tree-entry-toggle-visible-full { + // background: color-increase-contrast($default-background, 8%); + // } +} \ No newline at end of file diff --git a/src/mol-app/skin/components/help.scss b/src/mol-app/skin/components/help.scss new file mode 100644 index 000000000..31b400458 --- /dev/null +++ b/src/mol-app/skin/components/help.scss @@ -0,0 +1,28 @@ + +.molstar-help-row { + position: relative; + height: $row-height; + background: $default-background; + margin-top: 1px; + display: table; + width: 100%; + + > span { + width: $control-label-width + $control-spacing; + text-align: right; + padding: $info-vertical-padding $control-spacing; + color: color-lower-contrast($font-color, 15%); + display: table-cell; + font-weight: bold; + + @include non-selectable; + } + + > div { + background: $molstar-form-control-background; + position: relative; + padding: $info-vertical-padding $control-spacing; + display: table-cell; + color: color-lower-contrast($font-color, 15%); + } +} \ No newline at end of file diff --git a/src/mol-app/skin/components/jobs.scss b/src/mol-app/skin/components/jobs.scss new file mode 100644 index 000000000..13bae9ef1 --- /dev/null +++ b/src/mol-app/skin/components/jobs.scss @@ -0,0 +1,131 @@ +.molstar-job-state { + + line-height: $row-height; + //height: $row-height; + //position: relative; + //margin-top: 1px; + + > span { + @include non-selectable; + //display: inline-block; + //padding: 0 $control-spacing; + } + + // > button { + // margin-top: -2px; + // float: left; + // display: block; + // line-height: $row-height; + // height: $row-height; + // } +} + +/* overlay */ + +.molstar-overlay { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + z-index: 1000; + + .molstar-overlay-background { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: transparent; + //background: black; + //opacity: 0.5; + } + + .molstar-overlay-content-wrap { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: block; + width: 100%; + height: 100%; + } + + .molstar-overlay-content { + text-align: center; + + > div { + + padding-top: 2 * $row-height; + + .molstar-job-state { + $size: $row-height; + text-align: center; + + > div { + height: $size; + margin-top: $control-spacing; + position: relative; + text-align: center; + width: 100%; + + > div { + height: $size; + line-height: $size; + display: inline-block; + background: $default-background; + padding: 0 ($control-spacing); + font-weight: bold; + @include non-selectable; + } + + > button { + display: inline-block; + margin-top: -3px; + font-size: 140%; + } + } + } + } + } +} + +/* background */ + +.molstar-background-jobs { + position: absolute; + left: 0; + bottom: 0; + z-index: 1000; + + .molstar-job-state { + $size: $row-height; + + > div { + height: $size; + margin-top: 1px; + position: relative; + width: 100%; + background: $default-background; + + > div { + height: $size; + line-height: $size; + display: inline-block; + padding: 0 ($control-spacing); + @include non-selectable; + } + + > button { + display: inline-block; + margin-top: -3px; + font-size: 140%; + } + } + } +} + +// .molstar-background-jobs .molstar-job-state { +// color: +// } diff --git a/src/mol-app/skin/components/log.scss b/src/mol-app/skin/components/log.scss new file mode 100644 index 000000000..9bc74918c --- /dev/null +++ b/src/mol-app/skin/components/log.scss @@ -0,0 +1,97 @@ + +.molstar-log-wrap { + position: absolute; + right: 0; + top: 0; + left: 0; + bottom: 0; + overflow: hidden; +} + +.molstar-log { + position: absolute; + right: -20px; + top: 0; + left: 0; + bottom: 0; + overflow-y: scroll; + overflow-x: hidden; + font-size: 90%; + background: $control-background; +} + +.molstar-log { + ul { + padding: 0; + margin: 0; + } + + color: $log-font-color; + + li { + clear: both; + margin: 0; + background: $default-background; + position: relative; + + &:not(:last-child) { + border-bottom: 1px solid $border-color; + } + } + + + .molstar-log-entry { + margin-left: $control-label-width; + background: color-lower-contrast($control-background, 5%); + padding: $info-vertical-padding ($control-spacing + 15px) $info-vertical-padding $control-spacing ; + } + + .molstar-log-timestamp { + padding: ($info-vertical-padding + 1) $control-spacing ($info-vertical-padding - 1) $control-spacing; + float: left; + text-align: right; + width: $control-label-width; + color: $log-timestamp-font-color; + //vertical-align: baseline; + //line-height: $row-height; + font-size: 90%; + } + + .molstar-log-timestamp small { + font-size: 90%; + } +} + +// .molstar-log hr { +// border-color: $separator-color; +// margin: 3px 3px 0 5px; +// } + +.molstar-log .label { + margin-top: -3px; + font-size: 7pt; +} + +.molstar-log-entry-badge { + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 6px; +} + +.molstar-log-entry-message { + background: $log-message; +} + +.molstar-log-entry-info { + background: $log-info; +} + +.molstar-log-entry-error { + background: $log-error; +} + +.molstar-log-entry-warning { + background: $log-warning; +} diff --git a/src/mol-app/skin/components/misc.scss b/src/mol-app/skin/components/misc.scss new file mode 100644 index 000000000..a19eefd6a --- /dev/null +++ b/src/mol-app/skin/components/misc.scss @@ -0,0 +1,69 @@ +.molstar-description { + padding: $control-spacing; + font-size: 85%; + background: $default-background; + text-align: center; + //font-style: italic; + + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ + + /* Rules below not implemented in browsers yet */ + -o-user-select: none; + user-select: none; + + font-weight: light; + + cursor: default; +} + +.molstar-description:not(:first-child) { + border-top: 1px solid $control-background; +} + +.molstar-color-picker input { + color: black !important; +} + +.molstar-no-webgl { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + display: table; + text-align: center; + + > div { + b { + font-size: 120%; + } + display: table-cell; + vertical-align: middle; + text-align: center; + width: 100%; + height: 100%; + } +} + +.molstar-loader-molstar-btn-file { + position: relative; + overflow: hidden; +} + +.molstar-loader-molstar-btn-file input[type=file] { + position: absolute; + top: 0; + right: 0; + min-width: 100%; + min-height: 100%; + font-size: 100px; + text-align: right; + filter: alpha(opacity=0); + opacity: 0; + outline: none; + background: white; + cursor: inherit; + display: block; +} \ No newline at end of file diff --git a/src/mol-app/skin/components/panel.scss b/src/mol-app/skin/components/panel.scss new file mode 100644 index 000000000..affbcf9f2 --- /dev/null +++ b/src/mol-app/skin/components/panel.scss @@ -0,0 +1,142 @@ +.molstar-panel-header .molstar-panel-expander { + display: block; + width: 100%; + text-align: left; +} + +.molstar-panel-header { + + //border-bottom-width: 1px; + //border-bottom-style: solid; + height: $row-height; + border-color: $border-color; + position: relative; + + //border-radius: $control-spacing 0 0 0; + + .molstar-panel-expander-wrapper { + + position: absolute; + top: 0; + left: 0; + right: 2 * $row-height; + + button { + // width: 100%; + + display: block; + width: 100%; + text-align: left; + + height: $row-height; + line-height: $row-height; + border: none; + font-weight: bold; + //color: $panel-header-font-color; + padding-left: 0; + background: color-lower-contrast($default-background, 4%); + //text-align: right!important; + + .molstar-icon { + display: inline-block; + margin-right: $control-spacing; + width: $row-height; + text-align: center; + } + + &:hover { + background: color-lower-contrast($default-background, 4%); + } + } + } + + .molstar-panel-description-standalone { + > .molstar-icon { + margin-left: $row-height; + } + + width: 2 * $row-height; + } + + .molstar-panel-description-with-action { + width: $row-height; + margin-right: $row-height; + } + + .molstar-panel-description { + color: $font-color; + float: right; + background: color-lower-contrast($default-background, 4%); + //margin-right: $row-height; + + > .molstar-icon { + display: block; + width: $row-height; + height: $row-height; + line-height: $row-height; + text-align: center; + font-size: 70%; + cursor: default; + background: color-lower-contrast($default-background, 4%); + color: color-lower-contrast($font-color, 66%); + } + + .molstar-panel-description-content { + @include non-selectable; + + color: $font-color; + display: none; + position: absolute; + left: 0; + width: 100%; + background: color-increase-contrast($molstar-form-control-background, 20%); + min-height: $row-height; + z-index: 1000000; + padding: $info-vertical-padding $control-spacing $info-vertical-padding ($row-height + $control-spacing); + text-align: left; + //border-bottom: 1px solid color-lower-contrast($default-background, 4%); + + > .molstar-icon { + position: absolute; + width: $row-height; + height: $row-height; + line-height: $row-height; + text-align: center; + font-size: 80%; + cursor: default; + top: 0; + left: 0; + } + } + + &:hover { + color: $hover-font-color; + > .molstar-icon { + color: $hover-font-color; + } + .molstar-panel-description-content { + display: block; + } + } + } +} + +.molstar-panel-body { + background: $control-background; +} + +.molstar-panel { + margin-bottom: $control-spacing; +} + +.molstar-transform-view { + padding-top: $control-spacing; +} + +.molstar-expandable-group-color-stripe { + position: absolute; + left: 0; + top: $row-height - 2px; + width: $control-label-width + $control-spacing; + height: 2px; +} \ No newline at end of file diff --git a/src/mol-app/skin/components/slider.scss b/src/mol-app/skin/components/slider.scss new file mode 100644 index 000000000..2785a3de7 --- /dev/null +++ b/src/mol-app/skin/components/slider.scss @@ -0,0 +1,164 @@ +@mixin borderBox { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // remove tap highlight color for mobile safari + + * { + box-sizing: border-box; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); // remove tap highlight color for mobile safari + } + } + + .molstar-slider-base { + position: relative; + height: 14px; + padding: 5px 0; + width: 100%; + border-radius: $slider-border-radius-base; + @include borderBox; + + &-rail { + position: absolute; + width: 100%; + background-color: $border-color; + height: 4px; + border-radius: 2px; + } + + &-track { + position: absolute; + left: 0; + height: 4px; + border-radius: $slider-border-radius-base; + background-color: tint($font-color, 60%); + } + + &-handle { + position: absolute; + margin-left: -11px; + margin-top: -9px; + width: 22px; + height: 22px; + cursor: pointer; + border-radius: 50%; + background-color: $font-color; + border: 4px solid $border-color; + + &:hover { + background-color: $hover-font-color; + } + } + + &-mark { + position: absolute; + top: 18px; + left: 0; + width: 100%; + font-size: 12px; + } + + &-mark-text { + position: absolute; + display: inline-block; + vertical-align: middle; + text-align: center; + cursor: pointer; + color: #999; + + &-active { + color: #666; + } + } + + &-step { + position: absolute; + width: 100%; + height: 4px; + background: transparent; + } + + &-dot { + position: absolute; + bottom: -2px; + margin-left: -4px; + width: 8px; + height: 8px; + border: 2px solid #e9e9e9; + background-color: #fff; + cursor: pointer; + border-radius: 50%; + vertical-align: middle; + &:first-child { + margin-left: -4px; + } + &:last-child { + margin-left: -4px; + } + &-active { + border-color: tint($font-color, 50%); + } + } + + &-disabled { + background-color: #e9e9e9; + + .molstar-slider-base-track { + background-color: $slider-disabledColor; + } + + .molstar-slider-base-handle, .molstar-slider-base-dot { + border-color: $slider-disabledColor; + background-color: #fff; + cursor: not-allowed; + } + + .molstar-slider-base-mark-text, .molstar-slider-base-dot { + cursor: not-allowed!important; + } + } + } + + .molstar-slider-base-vertical { + width: 14px; + height: 100%; + padding: 0 5px; + + .molstar-slider-base { + &-rail { + height: 100%; + width: 4px; + } + + &-track { + left: 5px; + bottom: 0; + width: 4px; + } + + &-handle { + margin-left: -5px; + margin-bottom: -7px; + } + + &-mark { + top: 0; + left: 18px; + height: 100%; + } + + &-step { + height: 100%; + width: 4px; + } + + &-dot { + left: 2px; + margin-bottom: -4px; + &:first-child { + margin-bottom: -4px; + } + &:last-child { + margin-bottom: -4px; + } + } + } + } \ No newline at end of file diff --git a/src/mol-app/skin/components/viewport.scss b/src/mol-app/skin/components/viewport.scss new file mode 100644 index 000000000..3ef5c8a6a --- /dev/null +++ b/src/mol-app/skin/components/viewport.scss @@ -0,0 +1,93 @@ + +.molstar-viewport { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + background: black; + + .molstar-btn-link { + background: rgba(0,0,0,0.2); + } + +} + +.molstar-viewport-expanded { + position: fixed; + z-index: 1000; +} + +.molstar-viewport-container { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + -webkit-user-select: none; + -webkit-tap-highlight-color: rgba(0,0,0,0); + -webkit-touch-callout: none; +} + +.molstar-viewport-controls { + position: absolute; + right: $control-spacing; + top: $control-spacing; +} + +.molstar-viewport-controls-buttons { + text-align: right; + + > button { + padding: 0; + text-align: center; + width: $row-height; + } + + > button:last-child { + margin-left: $control-spacing; + } + + .molstar-btn-link, .molstar-btn-link-toggle-on { + color: #eee; + } + + .molstar-btn-link-toggle-off { + color: $molstar-btn-link-toggle-off-font-color; + } + + .molstar-btn-link:hover { + color: $hover-font-color; + } +} + +.molstar-viewport-controls-scene-options { + width: 290px; + background: $control-background; +} + +/* highlight */ + +.molstar-highlight-info { + + color: $highlight-info-font-color; + padding: $info-vertical-padding $control-spacing; + background: $default-background; //$highlight-info-background; + + position: absolute; + top: $control-spacing; + left: $control-spacing; + text-align: left; + min-height: $row-height; + max-width: 95%; + + //border-bottom-right-radius: 6px; + z-index: 10000; + @include non-selectable; +} + +.molstar-highlight-info-additional { + font-size: 85%; + display: inline-block; + color: $highlight-info-additional-font-color; +} \ No newline at end of file diff --git a/src/mol-app/skin/fonts/fontello.eot b/src/mol-app/skin/fonts/fontello.eot new file mode 100644 index 0000000000000000000000000000000000000000..b522411b265bb3854a7f23579001bfe51e4229cb GIT binary patch literal 49272 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�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 BVu}C& literal 0 HcmV?d00001 diff --git a/src/mol-app/skin/fonts/fontello.svg b/src/mol-app/skin/fonts/fontello.svg new file mode 100644 index 000000000..753bf788b --- /dev/null +++ b/src/mol-app/skin/fonts/fontello.svg @@ -0,0 +1,442 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata>Copyright (C) 2016 by original authors @ fontello.com</metadata> +<defs> +<font id="fontello" horiz-adv-x="1000" > +<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" /> +<missing-glyph horiz-adv-x="1000" /> +<glyph glyph-name="palette" unicode="" d="M857 622q72-48 101-110t20-104-35-48q-16-4-54 10t-80 10-80-46q-30-46-21-75t34-65 23-50q-2-26-36-63t-126-74-216-37q-186 0-291 101t-95 245q8 118 104 235t216 151q290 84 536-80z m-318-466q30 0 52 22t22 54-22 53-52 21q-32 0-54-21t-22-53 22-54 54-22z" horiz-adv-x="980" /> + +<glyph glyph-name="search" unicode="" d="M772 78q30-34 6-62l-46-46q-36-32-68 0l-190 190q-74-42-156-42-128 0-223 95t-95 223 90 219 218 91 224-95 96-223q0-88-46-162z m-678 358q0-88 68-156t156-68 151 63 63 153q0 88-68 155t-156 67-151-63-63-151z" horiz-adv-x="789" /> + +<glyph glyph-name="flashlight" unicode="" d="M807 706q62-62 85-130t-5-92l-134-134q-16-16-62-26t-96-4l-408-408q-18-18-57-6t-75 50q-36 36-49 74t5 56l408 408q-6 50 4 96t26 62l136 136q24 28 92 4t130-86z m-448-408q32-32 80 14 46 46 14 82-14 14-38 10t-44-24-23-43 11-39z m336 298q30-30 68-50t62-25 28-1q2 4-4 27t-26 60-50 67-66 50-59 26-27 2 1-28 25-62 48-66z" horiz-adv-x="902" /> + +<glyph glyph-name="mail" unicode="" d="M30 586q-32 18-28 40 2 14 26 14l846 0q38 0 20-32-8-14-24-22-14-6-192-102t-182-98q-16-10-46-10-28 0-46 10-4 2-182 98t-192 102z m850-100q20 10 20-10l0-368q0-16-17-32t-33-16l-800 0q-16 0-33 16t-17 32l0 368q0 20 20 10l384-200q18-10 46-10t46 10z" horiz-adv-x="900" /> + +<glyph glyph-name="heart" unicode="" d="M790 644q70-64 70-156t-70-158l-360-330-360 330q-70 66-70 158t70 156q62 58 151 58t153-58l56-52 58 52q62 58 150 58t152-58z" horiz-adv-x="860" /> + +<glyph glyph-name="heart-empty" unicode="" d="M790 642q70-64 70-156t-70-156l-360-330-360 330q-70 64-70 156t70 156q64 58 152 58t150-58l58-52 56 52q64 58 152 58t152-58z m-54-260q42 40 42 104 0 66-38 100-38 38-102 38-52 0-104-48l-104-92-106 92q-48 48-102 48-64 0-104-38-38-36-38-100 0-66 44-104l306-286z" horiz-adv-x="860" /> + +<glyph glyph-name="star" unicode="" d="M440 790l120-336 320 0-262-196 94-348-272 208-272-208 94 348-262 196 320 0z" horiz-adv-x="880" /> + +<glyph glyph-name="star-empty" unicode="" d="M880 454l-262-196 94-348-272 208-272-208 94 348-262 196 320 0 120 336 120-336 320 0z m-440-238l150-124-62 178 144 114-176-4-56 202-54-202-176 4 142-114-62-178z" horiz-adv-x="880" /> + +<glyph glyph-name="user" unicode="" d="M736 128q204-72 204-122l0-106-940 0 0 106q0 50 204 122 94 34 128 69t34 95q0 22-22 49t-32 73q-2 12-9 18t-14 8-14 17-9 43q0 16 5 26t9 12l4 4q-8 50-12 88-4 54 41 112t157 58 158-58 40-112l-12-88q18-8 18-42-2-28-9-43t-14-17-14-8-9-18q-8-48-31-74t-23-48q0-60 35-95t127-69z" horiz-adv-x="940" /> + +<glyph glyph-name="users" unicode="" d="M1000-90l-224 0 0 150q0 54-30 81t-154 89q40 30 40 84 0 16-13 33t-19 51q-2 8-14 16t-14 42q0 24 12 30-6 34-8 60-4 38 23 78t95 40 96-40 24-78l-8-60q12-6 12-30-2-34-14-42t-14-16q-6-34-19-51t-13-33q0-42 21-66t77-48q112-46 130-80 6-8 9-61t5-101l0-48z m-488 262q182-78 182-124l0-138-694 0 0 184q0 44 84 78 76 32 104 64t28 88q0 20-19 44t-25 68q-2 10-18 22t-20 56q0 14 3 23t7 13l4 2q-6 46-10 82-4 50 33 103t127 53 127-53 33-103l-10-82q14-8 14-38-4-44-20-56t-18-22q-6-44-25-68t-19-44q0-56 28-88t104-64z" horiz-adv-x="1000" /> + +<glyph glyph-name="user-add" unicode="" d="M620 128q180-64 180-122l0-106-800 0 0 202q36 14 82 26 94 34 129 69t35 95q0 22-23 48t-31 74q-2 12-23 25t-25 61q0 16 5 26t9 12l4 4q-8 50-12 88-6 54 40 112t160 58 160-58 42-112l-14-88q18-8 18-42-2-28-9-43t-14-17-14-8-9-18q-10-46-33-73t-23-49q0-60 36-95t130-69z m230 272l150 0 0-100-150 0 0-150-100 0 0 150-150 0 0 100 150 0 0 150 100 0 0-150z" horiz-adv-x="1000" /> + +<glyph glyph-name="video" unicode="" d="M980 600l-100 0 0-100 100 0 0-100-100 0 0-100 100 0 0-100-100 0 0-100 100 0 0-60q0-16-12-28t-28-12l-900 0q-16 0-28 12t-12 28l0 60 100 0 0 100-100 0 0 100 100 0 0 100-100 0 0 100 100 0 0 100-100 0 0 60q0 18 12 29t28 11l900 0q16 0 28-11t12-29l0-60z m-600-400l250 150-250 150 0-300z" horiz-adv-x="980" /> + +<glyph glyph-name="picture" unicode="" d="M856 518l-100 0-124 150-214-150-180 0q-52 0-90-39t-38-91l0-160-108 296q-10 38 22 52l680 248q36 10 50-24z m106-90q16 0 27-12t11-28l0-472q0-16-11-28t-27-12l-724 0q-16 0-27 12t-11 28l0 472q0 16 11 28t27 12l724 0z m-56-452l0 162-72 160-166-60-130-132-138 170-92-214 0-86 598 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="camera" unicode="" d="M500 450q64 0 107-44t43-106-44-106-106-44-106 44-44 106 44 106 106 44z m400 150q42 0 71-29t29-71l0-450q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 450q0 42 30 71t70 29l120 0q28 0 40 30l30 92q10 28 40 28l340 0q30 0 40-28l30-92q12-30 40-30l120 0z m-400-550q104 0 177 73t73 177-73 177-177 73-177-73-73-177 73-177 177-73z m366 380q14 0 24 11t10 25-10 24-24 10q-36 0-36-34 0-16 11-26t25-10z" horiz-adv-x="1000" /> + +<glyph glyph-name="layout" unicode="" d="M170 650q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m350 0q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m-350-350q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z m350 0q80 0 80-80l0-90q0-80-80-80l-90 0q-80 0-80 80l0 90q0 80 80 80l90 0z" horiz-adv-x="600" /> + +<glyph glyph-name="menu" unicode="" d="M650 400q22 0 36-15t14-35-15-35-35-15l-600 0q-20 0-35 15t-15 35 14 35 36 15l600 0z m-600 100q-20 0-35 15t-15 35 14 35 36 15l600 0q22 0 36-15t14-35-15-35-35-15l-600 0z m600-300q22 0 36-15t14-35-15-35-35-15l-600 0q-20 0-35 15t-15 35 14 35 36 15l600 0z" horiz-adv-x="700" /> + +<glyph glyph-name="check" unicode="" d="M249 0q-34 0-56 28l-180 236q-16 24-12 52t26 46 51 14 47-28l118-154 296 474q16 24 43 30t53-8q24-16 30-43t-8-53l-350-560q-20-32-56-32z" horiz-adv-x="667" /> + +<glyph glyph-name="cancel" unicode="" d="M452 194q18-18 18-43t-18-43q-18-16-43-16t-43 16l-132 152-132-152q-18-16-43-16t-43 16q-16 18-16 43t16 43l138 156-138 158q-16 18-16 43t16 43q18 16 43 16t43-16l132-152 132 152q18 16 43 16t43-16q18-18 18-43t-18-43l-138-158z" horiz-adv-x="470" /> + +<glyph glyph-name="cancel-circled" unicode="" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m86-420l154 154-86 86-154-152-152 152-88-86 154-154-154-152 88-86 152 152 154-152 86 86z" horiz-adv-x="840" /> + +<glyph glyph-name="cancel-squared" unicode="" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-146-638l86 86-154 152 154 154-86 86-154-152-152 152-88-86 154-154-154-152 88-86 152 152z" horiz-adv-x="800" /> + +<glyph glyph-name="plus" unicode="" d="M550 400q30 0 30-50t-30-50l-210 0 0-210q0-30-50-30t-50 30l0 210-210 0q-30 0-30 50t30 50l210 0 0 210q0 30 50 30t50-30l0-210 210 0z" horiz-adv-x="580" /> + +<glyph glyph-name="plus-circled" unicode="" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m52-470l200 0 0 102-200 0 0 202-102 0 0-202-202 0 0-102 202 0 0-202 102 0 0 202z" horiz-adv-x="840" /> + +<glyph glyph-name="plus-squared" unicode="" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-50-450l0 100-200 0 0 200-100 0 0-200-200 0 0-100 200 0 0-200 100 0 0 200 200 0z" horiz-adv-x="800" /> + +<glyph glyph-name="minus" unicode="" d="M550 400q30 0 30-50t-30-50l-520 0q-30 0-30 50t30 50l520 0z" horiz-adv-x="580" /> + +<glyph glyph-name="minus-circled" unicode="" d="M420 770q174 0 297-123t123-297-123-297-297-123-297 123-123 297 123 297 297 123z m252-368l-504 0 0-102 504 0 0 102z" horiz-adv-x="840" /> + +<glyph glyph-name="minus-squared" unicode="" d="M700 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l600 0z m-50-450l0 100-500 0 0-100 500 0z" horiz-adv-x="800" /> + +<glyph glyph-name="help" unicode="" d="M494 740q86-62 86-184 0-64-42-124-12-20-88-80l-46-30q-40-34-48-60-6-16-8-44 0-14-16-14l-128 0q-16 0-16 12 4 98 28 124 16 22 48 48t56 42l24 14q22 16 34 34 28 44 28 70 0 40-26 78-28 36-92 36-68 0-94-44-28-42-28-92l-166 0q6 162 114 232 70 42 166 42 130 0 214-60z m-216-636q44 0 73-30t27-74q-2-46-32-73t-74-25q-44 0-73 29t-27 75 32 73 74 25z" horiz-adv-x="580" /> + +<glyph glyph-name="help-circled" unicode="" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m-2-740q30 0 49 19t19 47q2 30-17 49t-49 19l-2 0q-28 0-47-18t-21-46q0-30 19-49t47-21l2 0z m166 328q26 34 26 78 0 78-54 116-52 38-134 38-64 0-104-26-68-42-72-146l0-4 110 0 0 4q0 26 16 54 16 24 54 24 40 0 52-20 16-20 16-44 0-18-16-40-8-12-20-20l-6-4q-6-4-16-11t-20-15-21-17-17-17q-14-20-18-78l0-8 108 0 0 4q0 12 4 28 6 20 28 36l28 18q46 34 56 50z" horiz-adv-x="920" /> + +<glyph glyph-name="info" unicode="" d="M352 850q48 0 74-27t26-69q0-50-39-88t-95-38q-48 0-74 26t-24 72q0 46 35 85t97 39z m-206-1000q-100 0-54 178l60 254q14 56 0 56-12 0-54-18t-72-38l-26 44q90 78 189 126t151 48q78 0 36-162l-70-266q-16-64 6-64 44 0 118 60l30-40q-84-86-175-132t-139-46z" horiz-adv-x="460" /> + +<glyph glyph-name="info-circled" unicode="" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m52-152q-42 0-65-24t-23-50q-2-28 15-44t49-16q38 0 61 22t23 54q0 58-60 58z m-120-594q30 0 84 26t106 78l-18 24q-48-36-72-36-14 0-4 38l42 160q26 96-22 96-30 0-89-29t-115-75l16-26q52 34 74 34 12 0 0-34l-36-152q-26-104 34-104z" horiz-adv-x="920" /> + +<glyph glyph-name="back" unicode="" d="M750 540q40 0 70-29t30-71l0-290q0-40-30-70t-70-30l-690 0 0 140 650 0 0 210-500 0 0-110-210 180 210 180 0-110 540 0z" horiz-adv-x="850" /> + +<glyph glyph-name="home" unicode="" d="M888 336q16-16 11-27t-27-11l-84 0 0-310q0-14-1-21t-8-13-23-6l-204 0 0 310-204 0 0-310-194 0q-28 0-35 10t-7 30l0 310-84 0q-22 0-27 11t11 27l400 402q16 16 38 16t38-16z" horiz-adv-x="900" /> + +<glyph glyph-name="link" unicode="" d="M294 116q14 14 34 14t36-14q32-34 0-70l-42-40q-56-56-132-56-78 0-134 56t-56 132q0 78 56 134l148 148q70 68 144 77t128-43q16-16 16-36t-16-36q-36-32-70 0-50 48-132-34l-148-146q-26-26-26-64t26-62q26-26 63-26t63 26z m450 574q56-56 56-132 0-78-56-134l-158-158q-74-72-150-72-62 0-112 50-14 14-14 34t14 36q14 14 35 14t35-14q50-48 122 24l158 156q28 28 28 64 0 38-28 62-24 26-56 31t-60-21l-50-50q-16-14-36-14t-34 14q-34 34 0 70l50 50q54 54 127 51t129-61z" horiz-adv-x="800" /> + +<glyph glyph-name="attach" unicode="" d="M244-140q-102 0-170 72-72 70-74 166t84 190l496 496q80 80 174 54 44-12 79-47t47-79q26-96-54-176l-474-474q-40-40-88-46-48-4-80 28-30 24-27 74t47 92l332 334q24 26 50 0t0-50l-332-332q-44-44-20-70 12-8 24-6 24 4 46 26l474 474q50 50 34 108-16 60-76 76-54 14-108-36l-494-494q-66-76-64-143t52-117q50-48 117-50t141 62l496 494q24 24 50 0 26-22 0-48l-496-496q-82-82-186-82z" horiz-adv-x="939" /> + +<glyph glyph-name="lock" unicode="" d="M640 476q20 0 40-19t20-41l0-390q0-48-48-66l-60-18q-42-16-96-16l-290 0q-56 0-98 16l-60 18q-48 18-48 66l0 390q0 22 15 41t35 19l100 0 0 70q0 110 51 170t149 60 149-60 51-170l0-70 90 0z m-390 90l0-90 200 0 0 90q0 52-27 81t-73 29-73-29-27-81z" horiz-adv-x="700" /> + +<glyph glyph-name="lock-open" unicode="" d="M640 450q20 0 40-20t20-40l0-390q0-20-14-39t-34-25l-60-20q-52-16-96-16l-290 0q-46 0-98 16l-60 20q-20 6-34 25t-14 39l0 390q0 22 15 41t35 19l400 0 0 140q0 110-100 110t-100-110l0-40-100 0 0 20q0 110 51 170t149 60q200 0 200-230l0-120 90 0z" horiz-adv-x="700" /> + +<glyph glyph-name="eye" unicode="" d="M500 630q92 0 177-25t141-62 99-77 63-71 20-45-20-44-63-71-99-78-141-62-177-25-177 25-141 62-99 78-63 71-20 44 20 45 63 71 99 77 141 62 177 25z m0-494q92 0 157 63t65 151q0 90-65 153t-157 63-157-63-65-153q0-88 65-151t157-63z m0 214q8-8 37-2t50 11 25-9q0-44-33-75t-79-31-78 31-32 75q0 46 32 77t78 31q14 0 10-23t-12-47 2-38z" horiz-adv-x="1000" /> + +<glyph glyph-name="tag" unicode="" d="M944 830q36-106-8-199t-128-157l18-24q16-28 6-54l-48-158q-12-30-36-46l-464-328q-42-30-64 4l-210 304q-12 18-9 39t21 33l464 328q26 18 54 18l158 0q30 0 48-26l28-40q168 130 114 286-10 28 18 40 32 8 38-20z m-216-468q40 32 34 80l-32-16q-8-4-12-4-18 0-28 18-12 30 16 40l24 14q-48 34-92 0-28-18-34-51t14-61q18-26 51-32t59 12z" horiz-adv-x="960" /> + +<glyph glyph-name="bookmark" unicode="" d="M310 800q22 0 36-15t14-35l0-850-180 180-180-180 0 850q0 50 40 50l270 0z" horiz-adv-x="360" /> + +<glyph glyph-name="bookmarks" unicode="" d="M500 850q20 0 35-15t15-35l0-850-150 180 0 620q0 20-15 35t-35 15l-100 0q0 50 40 50l210 0z m-250-150q20 0 35-15t15-35l0-800-150 180-150-180 0 800q0 50 40 50l210 0z" horiz-adv-x="550" /> + +<glyph glyph-name="flag" unicode="" d="M874 616q14 6 22-1t0-19q-96-138-164-213t-110-90-73-2-60 37-63 40-93-4-139-86l90-352-100 0-184 720 92 34q90 66 152 86t98 3 64-51 62-71 79-62 129-20 198 51z" horiz-adv-x="900" /> + +<glyph glyph-name="thumbs-up" unicode="" d="M582 480q2-6 58-13t108-24 52-47q0-72-61-284t-107-212q-144 0-288 42t-144 88l0 342q0 14 15 34t46 45 53 41 62 43 46 31q50 34 104 100t85 104 41 26q48-76 29-137t-59-119-40-60z m-432-4q14 0 0-14-50-50-50-104l0-318q0-50 52-104 10-10-2-10-26 0-55 8t-62 45-33 99l0 242q0 62 33 100t63 47 54 9z" horiz-adv-x="800" /> + +<glyph glyph-name="thumbs-down" unicode="" d="M218 218q-2 6-57 13t-108 24-53 47q0 72 62 285t106 213q144 0 288-43t144-89l0-342q0-10-8-24t-25-30-32-29-42-32-41-29-41-28l-33-22q-50-34-104-100t-85-104-41-26q-48 76-29 137t59 119 40 60z m432 4q-12 0 2 14 48 50 48 104l0 318q0 50-52 104-10 10 2 10 26 0 55-8t62-45 33-99l0-242q0-48-18-81t-45-48-48-21-39-6z" horiz-adv-x="800" /> + +<glyph glyph-name="download" unicode="" d="M968 198q18-10 27-32t3-40l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-10 48 32 72l158 108 98 0-170-130 178 0q8 0 12-8l40-110 300 0 40 110q8 8 12 8l178 0-170 130 98 0z m-208 322l-260-244-260 244 166 0 0 256 190 0 0-256 164 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="upload" unicode="" d="M500 776l260-244-164 0 0-256-190 0 0 256-166 0z m468-578q18-10 27-32t3-40l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-10 48 32 72l158 108 98 0-170-130 178 0q8 0 12-8l40-110 300 0 40 110q8 8 12 8l178 0-170 130 98 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="upload-cloud" unicode="" d="M760 494q100 0 170-68t70-166-70-166-170-68l-190 0 0 190 106 0-176 230-174-230 104 0 0-190-248 0q-74 0-128 52t-54 124q0 74 53 126t129 52q14 0 20-2-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z" horiz-adv-x="1000" /> + +<glyph glyph-name="reply" unicode="" d="M900 10q-86 152-208 197t-330 45l0-218-362 334 362 322 0-192q90 0 168-27t131-70 96-95 69-104 44-95 24-69z" horiz-adv-x="900" /> + +<glyph glyph-name="reply-all" unicode="" d="M362 556l-212-188 212-196 0-138-362 334 362 322 0-134z m250-58q104 0 182-50t115-122 60-144 27-122l4-50q-86 154-168 198t-220 44l0-218-362 334 362 322 0-192z" horiz-adv-x="1000" /> + +<glyph glyph-name="forward" unicode="" d="M540 252q-210 0-332-45t-208-197q4 20 13 53t50 117 96 148 156 117 225 53l0 192 360-322-360-334 0 218z" horiz-adv-x="900" /> + +<glyph glyph-name="quote" unicode="" d="M146 680q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z m420 0q146 0 184-146 38-140-40-302-80-168-224-204-32-8-66-8l0 70q112 0 182 108 54 86 26 146-16 36-62 36-60 0-103 44t-43 106 43 106 103 44z" horiz-adv-x="762" /> + +<glyph glyph-name="code" unicode="" d="M380 636q16-14 16-32t-16-30l-246-224 246-226q16-12 16-30t-16-32q-30-30-60 0l-320 288 320 286q30 30 60 0z m302 0l318-286-318-288q-32-30-62 0-32 32 0 62l248 226-248 224q-32 30 0 62 30 30 62 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="export" unicode="" d="M750 60l0 56 100 82 0-188q0-20-15-35t-35-15l-750 0q-20 0-35 15t-15 35l0 550q0 22 14 36t36 14l288 0q-32-24-59-49t-39-39l-10-12-130 0 0-450 650 0z m-82 348q-166 0-242-41t-160-181q0 8 1 22t9 56 22 79 44 83 70 79 107 56 149 23l0 156 332-250-332-260 0 178z" horiz-adv-x="1000" /> + +<glyph glyph-name="pencil" unicode="" d="M718 680q32-32 47-64t15-48l0-16-252-252-290-288-238-52 50 240 290 288 252 252q54 12 126-60z m-494-640l24 24q-2 44-52 94-22 22-45 35t-35 13l-14 2-22-24-18-80q28-16 46-34 24-24 36-48z" horiz-adv-x="780" /> + +<glyph glyph-name="feather" unicode="" d="M60-138q-6-20-26-8-18 8-16 34 4 100 50 226-100 154-52 316 10-32 32-78t44-80 32-30q8 4 0 83t-11 166 25 157q22 44 80 94t104 70q-24-46-33-94t-4-78 21-32q12 0 84 120t106 122q46 4 114-29t82-65q12-24 0-79t-40-83q-44-44-146-62t-114-24q-16-10 12-34 54-48 176-20-56-80-136-114t-132-38-54-10q-4-24 49-54t101-14q-30-56-63-84t-54-35-76-11-85-8z" horiz-adv-x="698" /> + +<glyph glyph-name="print" unicode="" d="M66 526q-26 0-22 22 4 10 12 14 2 0 49 17t93 32 58 15l44 0 0 150 380 0 0-150 46 0q12 0 57-15t92-32 49-17q18-8 12-26-4-10-20-10l-850 0z m860-56q20 0 37-19t17-41l0-174q0-22-17-41t-37-19l-100 0 44-250-760 0 44 250-98 0q-20 0-38 19t-18 41l0 174q0 22 18 41t38 19l870 0z m-716-444l560 0-70 324-420 0z" horiz-adv-x="980" /> + +<glyph glyph-name="retweet" unicode="" d="M250 190l272 0 128-140-448 0q-42 0-71 30t-29 70l0 302-102 0 176 198 174-198-100 0 0-262z m650 60l100 0-174-200-176 200 102 0 0 260-274 0-128 140 450 0q40 0 70-29t30-71l0-300z" horiz-adv-x="1000" /> + +<glyph glyph-name="keyboard" unicode="" d="M930 650q28 0 49-21t21-49l0-460q0-30-21-50t-49-20l-860 0q-28 0-49 20t-21 50l0 460q0 28 21 49t49 21l860 0z m-380-100l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-300 150l0-100 100 0 0 100-100 0z m150-150l-100 0 0-100 100 0 0 100z m-50-250l0 100-100 0 0-100 100 0z m550 0l0 100-500 0 0-100 500 0z m150 0l0 100-100 0 0-100 100 0z m-150 150l100 0 0 100-100 0 0-100z m150 150l0 100-200 0 0-100 200 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="comment" unicode="" d="M700 700q42 0 71-29t29-71l0-350q0-40-29-70t-71-30l-200 0 0-150-200 150-200 0q-40 0-70 30t-30 70l0 350q0 42 30 71t70 29l600 0z" horiz-adv-x="800" /> + +<glyph glyph-name="chat" unicode="" d="M290 240l350 0q2 0 6 2l4 0 0-92q0-40-29-70t-71-30l-250 0-150-150 0 150-50 0q-40 0-70 30t-30 70l0 300q0 42 30 71t70 29l190 0 0-310z m610 560q42 0 71-29t29-71l0-300q0-40-29-70t-71-30l-50 0 0-150-150 150-350 0 0 400q0 42 30 71t70 29l450 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="bell" unicode="" d="M632 426q16-34 40-52t45-22 44-23 35-55q22-62-74-161t-252-157q-164-58-297-45t-155 75q-20 54 12 111t18 111q-56 192-47 300t113 192q26 22 29 51t29 39q24 8 46-12t56-18q132 2 198-66t160-268z m-186-404q88 32 159 85t100 91 25 50q-8 22-49 33t-124 1-187-48q-102-38-173-87t-94-84-17-53q4-12 50-22t134-4 176 38z m-62 174q8 2 21 7t17 7l2-2q14-40-17-83t-89-63q-96-36-152 14 78 68 218 120z" horiz-adv-x="800" /> + +<glyph glyph-name="attention" unicode="" d="M957-24q10-16 0-34-10-16-30-16l-892 0q-18 0-28 16-13 18-2 34l446 782q8 18 30 18t30-18z m-420 50l0 100-110 0 0-100 110 0z m0 174l0 300-110 0 0-300 110 0z" horiz-adv-x="962" /> + +<glyph glyph-name="alert" unicode="" d="M885 234q20-16 16-33t-28-23l-78-22q-24-6-40-28t-14-48l4-82q2-24-14-34t-38 0l-86 44q-22 12-47 4t-35-30l-46-88q-12-22-29-23t-33 19l-50 78q-34 48-88 20l-122-70q-22-14-32-6t-2 32l54 164q8 24-4 44t-36 22l-106 12q-24 4-29 18t15 30l86 76q20 16 20 41t-20 41l-86 76q-20 16-16 33t28 23l78 22q24 6 41 28t15 48l-6 82q0 26 15 36t37 0l80-38q24-10 49-2t37 30l46 80q12 22 30 21t30-23l50-86q12-22 35-29t45 7l136 84q22 14 30 6t0-32l-60-170q-10-22 2-41t38-21l114-12q26-2 30-16t-16-30l-86-76q-18-16-18-41t18-41z m-384-92l0 104-100 0 0-104 100 0z m0 160l0 260-100 0 0-260 100 0z" horiz-adv-x="901" /> + +<glyph glyph-name="vcard" unicode="" d="M900 750q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l800 0z m0-700l0 600-800 0 0-600 800 0z m-450 196l0-90-250 0 0 90 250 0z m0 150l0-90-250 0 0 90 250 0z m0 150l0-90-250 0 0 90 250 0z m346-320l4-70-250 0q0 70 6 70 84 22 84 66 0 16-27 56t-27 88q0 110 90 110t90-110q0-48-28-88t-28-56q0-20 21-36t43-22z" horiz-adv-x="1000" /> + +<glyph glyph-name="address" unicode="" d="M426 800q20 0 20-20l0-860q0-20-20-20l-46 0q-20 0-20 20l0 440-176 0q-16 0-28 6-12 2-26 12l-120 82q-10 6-10 16t10 16l120 82q14 10 26 12 8 4 28 4l176 0 0 190q0 20 20 20l46 0z m564-208q10-6 10-16t-10-16l-118-82q-22-12-26-12-14-6-28-6l-302 0-40 230 342 0q18 0 28-4t26-12z" horiz-adv-x="1000" /> + +<glyph glyph-name="location" unicode="" d="M250 750q104 0 177-73t73-177q0-106-62-243t-126-223l-62-84q-10 12-27 35t-60 89-76 130-60 147-27 149q0 104 73 177t177 73z m0-388q56 0 96 40t40 96-40 95-96 39-95-39-39-95 39-96 95-40z" horiz-adv-x="500" /> + +<glyph glyph-name="map" unicode="" d="M984 600q16-10 16-30l0-584q0-20-16-30-8-6-16-6t-18 6l-216 136-216-136q-18-10-34 0l-218 136-216-136q-16-10-34 0-16 10-16 30l0 584q0 20 16 30l234 146q18 10 34 0l216-136 218 136q16 10 32 0z m-750-450l0 506-168-104 0-506z m234-104l0 506-168 104 0-506z m234 104l0 506-170-104 0-506z m232-104l0 506-168 104 0-506z" horiz-adv-x="1000" /> + +<glyph glyph-name="direction" unicode="" d="M848 768q8-8 11-16t-2-22-10-26-19-39-24-49q-54-112-147-286t-157-292l-66-118-54 380-380 56q442 246 696 368 20 10 48 25t39 20 25 9 23 1 17-11z m-92-96l-304-280 28-234z" horiz-adv-x="860" /> + +<glyph glyph-name="compass" unicode="" d="M474 830q198 2 340-136t146-336q2-200-136-342t-338-146q-198-2-341 137t-145 337q-4 200 135 342t339 144z m12-858q156 2 266 114t108 270-115 267-269 107q-158-2-267-114t-107-270 114-267 270-107z m-234 154q4 26 12 66t41 128 77 132 125 76 141 42l60 10q-4-26-12-66t-41-128-77-132q-42-42-124-74t-142-42z m180 276q-22-20-22-48t22-50q20-22 49-22t49 22q52 52 88 186-136-36-186-88z" horiz-adv-x="960" /> + +<glyph glyph-name="cup" unicode="" d="M340 760q152 0 249-41t91-87l-72-594q-2-14-34-36t-97-42-137-20-136 20-97 42-35 36l-72 594q-4 28 36 57t121 50 183 21z m0-216q72 0 137 15t98 33 33 30-33 29-98 32-137 15-137-15-98-32-33-29 33-30 98-33 137-15z" horiz-adv-x="681" /> + +<glyph glyph-name="trash" unicode="" d="M50 458q122-70 330-70t330 70l-54-486q-2-14-35-36t-100-43-141-21-140 21-100 43-36 36z m488 300q94-18 158-55t64-71l0-10q0-58-112-99t-268-41-268 41-112 99l0 10q0 34 64 71t158 55l42 48q22 26 70 26l92 0q52 0 70-26z m-54-112l84 0q-92 110-104 126-14 16-32 16l-102 0q-22 0-32-16l-106-126 84 0 64 66 82 0z" horiz-adv-x="760" /> + +<glyph glyph-name="doc" unicode="" d="M600 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m0-800l0 700-500 0 0-700 500 0z" horiz-adv-x="700" /> + +<glyph glyph-name="docs" unicode="" d="M970 480q38-10 30-46l-150-556q-4-16-18-23t-30-3l-406 110q-16 4-24 18t-4 28l24 92-180-48q-40-10-50 26l-160 602q-10 36 28 48l454 122q16 4 30-3t18-23l66-244z m-888 190l144-542 392 106-144 540z m702-742l132 492-298 82 76-282q10-34-28-46l-196-52-26-102z" horiz-adv-x="1001" /> + +<glyph glyph-name="doc-landscape" unicode="" d="M0 600q0 42 30 71t70 29l800 0q42 0 71-29t29-71l0-500q0-40-29-70t-71-30l-800 0q-40 0-70 30t-30 70l0 500z m900 0l-800 0 0-500 800 0 0 500z" horiz-adv-x="1000" /> + +<glyph glyph-name="doc-text" unicode="" d="M212 308l0 90 280 0 0-90-280 0z m388 492q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m0-800l0 700-500 0 0-700 500 0z m-110 592l0-88-280 0 0 88 280 0z m0-392l0-88-280 0 0 88 280 0z" horiz-adv-x="700" /> + +<glyph glyph-name="doc-text-inv" unicode="" d="M600 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m-460-208l0-88 420 0 0 88-420 0z m420-480l0 88-420 0 0-88 420 0z m0 196l0 90-418 0 0-90 418 0z" horiz-adv-x="700" /> + +<glyph glyph-name="newspaper" unicode="" d="M700 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l600 0z m0-800l0 700-600 0 0-700 600 0z m-250 250l0-50-250 0 0 50 250 0z m150 200l0-50-200 0 0 50 200 0z m-200 50l0 100 200 0 0-100-200 0z m-50 100l0-200-150 0 0 200 150 0z m-50-250l0-50-100 0 0 50 100 0z m50-50l0 50 250 0 0-50-250 0z m250-150l0-50-400 0 0 50 400 0z m-100 50l0 50 100 0 0-50-100 0z" horiz-adv-x="800" /> + +<glyph glyph-name="book-open" unicode="" d="M340 238l0-68-200 80 0 68z m0 208l0-68-200 80 0 68z m538 346q22-12 22-42l0-640q0-34-32-46l-398-160q-8-2-10-2t-5-1-5-1-5 1-5 1l-10 2-398 160q-32 12-32 46l0 640q0 30 22 42 22 16 46 6l382-154 382 154q24 10 46-6z m-478-788l0 560-320 128 0-560z m420 128l0 560-320-128 0-560z m-60 186l0-68-200-80 0 68z m0 208l0-68-200-80 0 68z" horiz-adv-x="900" /> + +<glyph glyph-name="book" unicode="" d="M682 594q18-8 18-28l0-562q0-14-12-25t-28-11q-46 0-46 36l0 522q0 12-12 18l-404 216q-32 10-68-10-44-20-56-44l408-228q18-8 18-28l0-550q0-22-18-28-6-4-16-4-14 0-20 4-8 6-202 127t-212 131q-26 18-26 34l-6 524q0 28 14 52 28 46 102 77t116 9z" horiz-adv-x="700" /> + +<glyph glyph-name="folder" unicode="" d="M954 500q32 0 40-12t6-36l-42-452q-2-24-12-37t-42-13l-806 0q-52 0-56 50l-42 452q-2 24 6 36t40 12l908 0z m-34 110l10-40-846 0 14 132q4 20 20 34t36 14l164 0q52 0 86-34l30-30q32-36 86-36l340 0q20 0 38-12t22-28z" horiz-adv-x="1001" /> + +<glyph glyph-name="archive" unicode="" d="M840 600l0-50-696 0 0 50q0 22 13 35t25 15l608 0q6 0 14-1t22-14 14-35z m-148 150q6 0 14-1t22-14 14-35l-498 0q0 22 13 35t25 15l410 0z m248-200q34-32 38-46 6-18 0-54l-76-450q-4-22-20-35t-28-15l-710 0q-52 0-60 50-6 26-39 223t-39 227q-10 22-3 44t10 26 21 20l10 10 30 30 0-80 836 0 0 80z m-248-270l0 100-70 0 0-80-260 0 0 80-68 0 0-100q0-50 48-50l300 0q22 0 35 12t13 24z" horiz-adv-x="981" /> + +<glyph glyph-name="box" unicode="" d="M870 750q12 0 21-9t9-21l0-120-900 0 0 120q0 12 9 21t21 9l840 0z m-820-730l0 530 800 0 0-530q0-30-21-50t-49-20l-660 0q-28 0-49 20t-21 50z m250 430l0-100 300 0 0 100-300 0z" horiz-adv-x="900" /> + +<glyph glyph-name="rss" unicode="" d="M0 730q314 0 537-223t223-537l-118 0q0 266-188 453t-454 187l0 120z m0-238q218 0 371-153t153-369l-118 0q0 166-119 285t-287 119l0 118z m114-296q46 0 80-33t34-81q0-46-34-79t-80-33-80 33-34 79q0 48 34 81t80 33z" horiz-adv-x="760" /> + +<glyph glyph-name="phone" unicode="" d="M461 290q162 162 118 206l-8 8q-30 30-41 48t-4 54 49 88q20 24 37 39t35 16 30 1 29-13 24-18 26-25 21-22q48-48-6-194t-204-294q-150-150-295-205t-193-7q-2 2-23 22t-25 25-18 24-13 31 2 30 15 35 38 37q42 34 70 47t54 2 35-18 39-37q44-44 208 120z" horiz-adv-x="800" /> + +<glyph glyph-name="cog" unicode="" d="M760 350q0-72 80-122-12-40-34-82-70 18-136-44-54-58-34-136-40-20-84-36-46 82-132 82t-132-82q-44 16-84 36 20 80-34 136-54 54-136 34-14 26-34 82 82 52 82 132 0 72-82 124 20 56 34 82 74-18 136 44 54 56 34 136 42 22 84 34 46-80 132-80t132 80q42-12 84-34-20-78 34-136 66-62 136-44 22-42 34-82-80-50-80-124z m-340-182q76 0 129 53t53 129-53 130-129 54-129-54-53-130 53-129 129-53z" horiz-adv-x="840" /> + +<glyph glyph-name="tools" unicode="" d="M155 506q-8-8-11-22t-3-25-2-11q-2-2-17-15t-19-17q-16-14-28 4l-70 76q-11 12 2 24 2 2 18 14t20 16q6 6 27 6t37 14q14 14 18 38t10 30q2 0 9 7t26 22 41 31q134 90 186 96 122 0 148-2 12 0-8-8-120-52-152-76-80-56-36-114 34-46 38-48 8-8-2-14-2-2-38-35t-38-35q-14-8-18-4-42 48-71 60t-67-12z m286-26l410-476q18-22-2-38l-48-42q-22-14-38 4l-414 472q-8 8 0 20l72 62q12 8 20-2z m554 202q16-104-16-166-50-88-154-62-56 12-100-32l-82-78-68 78 68 70q24 24 31 53t6 65 5 58q12 56 140 112 12 6 18-3t2-15q-12-12-46-80-14-10-12-35t40-53q58-40 96 22 6 12 26 41t22 33q4 10 13 9t11-17z m-858-684l254 248 76-86-246-242q-20-20-38-4l-46 46q-22 18 0 38z" horiz-adv-x="1000" /> + +<glyph glyph-name="share" unicode="" d="M650 200q62 0 106-43t44-107q0-62-44-106t-106-44-106 44-44 106q0 6 1 14t1 12l-260 156q-42-32-92-32-62 0-106 44t-44 106 44 106 106 44q54 0 92-30l260 156q0 4-1 12t-1 12q0 62 44 106t106 44 106-43 44-107q0-62-44-106t-106-44q-52 0-90 32l-262-156q2-8 2-26 0-16-2-24l262-156q36 30 90 30z" horiz-adv-x="800" /> + +<glyph glyph-name="shareable" unicode="" d="M340 350q0 68 47 114t113 46 113-46 47-114q0-66-47-113t-113-47-113 47-47 113z m-114 60q-14-60-66-60l-160 0 0 120 118 0q40 124 145 202t237 78q164 0 284-116 16-18 16-43t-16-43q-18-16-43-16t-43 16q-78 82-198 82-100 0-176-62t-98-158z m614-60l160 0 0-120-118 0q-40-124-144-202t-238-78q-164 0-282 118-18 18-18 43t18 41q16 18 41 18t43-18q82-82 198-82 100 0 176 63t98 157q12 60 66 60z" horiz-adv-x="1000" /> + +<glyph glyph-name="basket" unicode="" d="M150 0q0 40 30 70t70 30q42 0 71-30t29-70q0-42-29-71t-71-29q-40 0-70 29t-30 71z m500 0q0 40 30 70t70 30q42 0 71-30t29-70q0-42-29-71t-71-29q-40 0-70 29t-30 71z m-322 236q-36-10-34-23t44-13l562 0 0-76q0-20-20-20l-654 0q-20 0-20 20l0 76-10 46-98 454-98 0 0 80q0 20 20 20l156 0q20 0 20-20l0-86 704 0 0-274q0-22-18-26z" horiz-adv-x="900" /> + +<glyph glyph-name="bag" unicode="" d="M835 668q28-26 24-60l-98-648q-8-30-38-30l-586 0q-28 0-40 30-94 620-96 648-5 34 22 60 6 6 54 43t56 43q18 16 56 16l480 0q38 0 56-16 78-58 110-86z m-406-436q56 0 98 34t63 89 30 89 13 66l-92 0q-38-188-112-188t-112 188l-92 0q46-278 204-278z m-352 368l704 0-110 116-484 0z" horiz-adv-x="859" /> + +<glyph glyph-name="calendar" unicode="" d="M800 700q42 0 71-29t29-71l0-600q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l46 0 0-100 160 0 0 100 290 0 0-100 160 0 0 100 44 0z m0-700l0 400-700 0 0-400 700 0z m-540 800l0-170-70 0 0 170 70 0z m450 0l0-170-70 0 0 170 70 0z" horiz-adv-x="900" /> + +<glyph glyph-name="login" unicode="" d="M800 800q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-450 0q-40 0-69 30t-29 70l0 100 98 0 0-100 450 0 0 700-450 0 0-150-98 0 0 150q0 42 29 71t69 29l450 0z m-350-670l0 120-450 0 0 150 450 0 0 120 200-194z" horiz-adv-x="900" /> + +<glyph glyph-name="logout" unicode="" d="M502 0l0 100 98 0 0-100q0-40-29-70t-71-30l-400 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l400 0q42 0 71-29t29-71l0-150-98 0 0 150-402 0 0-700 402 0z m398 326l-198-196 0 120-450 0 0 150 450 0 0 120z" horiz-adv-x="900" /> + +<glyph glyph-name="mic" unicode="" d="M620 488q20 0 20-20l0-138q0-92-69-164t-201-84l0-132 130 0q20 0 20-20l0-60q0-20-20-20l-360 0q-20 0-20 20l0 60q0 20 20 20l130 0 0 132q-132 12-201 84t-69 164l0 138q0 20 20 20l30 0q20 0 20-20l0-138q0-66 59-123t191-57 191 57 59 123l0 138q0 20 20 20l30 0z m-300-238q-80 0-115 25t-35 55l0 158 300 0 0-158q0-30-35-55t-115-25z m150 520l0-212-300 0 0 212q0 30 35 55t115 25 115-25 35-55z" horiz-adv-x="640" /> + +<glyph glyph-name="mute" unicode="" d="M868 778q16-16 16-36t-16-36l-782-782q-18-14-34-14-18 0-36 14-16 14-16 36t16 36l782 782q34 32 70 0z m-216-386l50 50q74-92 101-172t-7-116q-24-24-75-57t-131-71-161-45-165 23l278 276q44-32 88-54t67-25 33 1q6 10 2 34t-26 68-54 88z m-276 62l-270-270q-40 132 28 283t132 215q34 32 105 11t159-85l-52-50q-58 38-105 53t-57 5q-4-8-2-28t19-58 43-76z" horiz-adv-x="884" /> + +<glyph glyph-name="sound" unicode="" d="M176 588q42 42 149-5t217-157 157-217 5-149q-28-28-92-67t-156-78-194-29-176 84-84 176 29 194 78 156 67 92z m464-480q8 10-3 49t-49 101-96 118q-56 58-118 96t-101 49-49 3q-8-10 3-49t49-101 94-120q58-56 120-94t101-49 49-3z m6 394q-18 0-34 16-16 14-16 35t16 35l94 96q36 32 72 0 32-36 0-72l-96-94q-16-16-36-16z m-180 124q-18 10-23 30t5 38l54 96q26 44 68 20 18-10 23-30t-5-38l-54-96q-14-26-42-26-14 0-26 6z m438-150q10-18 4-38t-24-30l-96-54q-16-8-24-8-28 0-44 26-10 18-4 38t24 30l96 54q18 10 38 5t30-23z" horiz-adv-x="910" /> + +<glyph glyph-name="volume" unicode="" d="M896 180q0-34-24-57t-56-23l-780 0q-22 0-31 5t-3 15 24 20l802 452q28 18 48 7t20-45l0-374z" horiz-adv-x="896" /> + +<glyph glyph-name="clock" unicode="" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-820q150 0 255 106t105 254q0 150-105 255t-255 105q-148 0-254-105t-106-255q0-148 106-254t254-106z m36 620l0-244 150-150-50-50-170 170 0 274 70 0z" horiz-adv-x="920" /> + +<glyph glyph-name="hourglass" unicode="" d="M560 622q0-44-48-96t-97-99-49-77 49-76 97-97 48-97l0-118q0-34-86-73t-194-39-194 39-86 73l0 118q0 46 48 97t97 97 49 76-49 77-97 99-48 96l0 118q0 32 87 71t193 39 193-39 87-71l0-118z m-482 112l-18-14q-4-8 4-14 92-52 216-52 132 0 220 50 14 10-16 30-96 54-202 54-120 0-204-54z m228-384q0 18 4 33t18 33 20 25 31 31 29 28q92 92 92 122l2 50q-100-54-222-54t-222 54l4-50q0-32 90-122 6-6 22-21t23-22l19-19t17-21 11-20 9-23 3-24q0-10-1-19t-6-18-8-16-11-17l-12-15t-15-16-16-15-18-16-17-16q-90-90-90-122l0-66q8 4 66 23t92 43 34 58q0 30 26 30t26-30q0-34 33-58t94-43 67-23l0 66q0 30-92 122-4 4-21 20t-22 21-18 19-18 22-12 20-9 23-2 23z" horiz-adv-x="560" /> + +<glyph glyph-name="lamp" unicode="" d="M209-110l0 104 282 0 0-104q-70-42-142-40-70-2-140 40z m276 164l-270 0q0 72-36 140t-78 113-74 112-26 139q8 120 94 206t254 86q170 0 255-86t95-206q4-60-16-113t-52-96-65-85-57-96-24-114z m-378 496q-4-4 0-20t2-20 5-19 6-18 8-18 11-19 13-19 14-19 15-21 16-23q88-122 112-212l82 0q24 94 112 212 4 6 25 35t25 36 17 29 16 33 6 28 1 35q-16 196-244 196-226 0-242-196z" horiz-adv-x="700" /> + +<glyph glyph-name="light-down" unicode="" d="M350 510q68 0 114-47t46-113q0-68-46-114t-114-46q-66 0-113 46t-47 114q0 66 47 113t113 47z m0-264q44 0 73 30t29 74q0 42-29 72t-73 30q-42 0-72-30t-30-72q0-44 30-74t72-30z m-300 144q20 0 35-12t15-28q0-40-50-40t-50 40q0 16 15 28t35 12z m546 204q28-28-8-64-14-14-33-16t-29 10q-12 12-10 31t16 33q36 34 64 6z m54-204q20 0 35-12t15-28q0-40-50-40-48 0-48 40 0 16 14 28t34 12z m-300-290q16 0 28-15t12-35-12-35-28-15-28 15-12 35 12 35 28 15z m-238 62q36 36 64 8t-8-64q-14-14-33-16t-29 8q-30 28 6 64z m-10 430q28 28 64-8 14-14 16-33t-8-29q-30-28-64 6-36 36-8 64z m432-484q-34 36-6 64t64-8q14-14 16-33t-10-29q-30-28-64 6z m-184 492q-16 0-28 15t-12 35 12 35 28 15 28-15 12-35-12-35-28-15z" horiz-adv-x="700" /> + +<glyph glyph-name="light-up" unicode="" d="M950 390q20 0 35-12t15-28q0-40-50-40l-48 0q-50 0-50 40 0 16 15 28t35 12l48 0z m-450 234q114 0 195-80t81-194q0-116-81-196t-195-80-194 80-80 196q0 114 80 194t194 80z m0-474q82 0 141 58t59 142q0 82-59 141t-141 59-141-59-59-141q0-84 59-142t141-58z m-350 200q0-40-50-40l-50 0q-50 0-50 40 0 16 15 28t35 12l50 0q20 0 35-12t15-28z m350 350q-16 0-28 15t-12 35l0 50q0 20 12 35t28 15 28-15 12-35l0-50q0-20-12-35t-28-15z m0-700q16 0 28-15t12-35l0-50q0-20-12-35t-28-15-28 15-12 35l0 50q0 20 12 35t28 15z m368 660l-34-34q-34-34-64-8-28 28 8 64 4 6 34 36 36 34 64 6t-8-64z m-700-588q14 16 33 18t29-10q12-12 10-31t-16-33l-36-36q-14-14-33-16t-29 10q-30 28 6 64 6 4 36 34z m20 646l36-36q36-36 6-64-10-10-29-8t-33 16q-30 30-36 34-14 14-16 33t10 31q10 12 29 10t33-16z m590-702q-36 36-8 64t64-8l34-34q36-36 8-64t-64 6q-30 30-34 36z" horiz-adv-x="1000" /> + +<glyph glyph-name="adjust" unicode="" d="M950 390q20 0 35-12t15-28q0-40-50-40l-48 0q-50 0-50 40 0 16 15 28t35 12l48 0z m-450 234q114 0 195-80t81-194q0-116-81-196t-195-80-194 80-80 196q0 114 80 194t194 80z m6-474l0 400q-86 0-146-59t-60-141q0-84 60-142t146-58z m-356 200q0-40-50-40l-50 0q-50 0-50 40 0 16 15 28t35 12l50 0q20 0 35-12t15-28z m350 350q-16 0-28 15t-12 35l0 50q0 20 12 35t28 15 28-15 12-35l0-50q0-20-12-35t-28-15z m0-700q16 0 28-15t12-35l0-50q0-20-12-35t-28-15-28 15-12 35l0 50q0 20 12 35t28 15z m368 660l-34-34q-34-34-64-8-28 28 8 64 4 6 34 36 36 34 64 6t-8-64z m-700-588q14 16 33 18t29-10q12-12 10-31t-16-33l-36-36q-14-14-33-16t-29 10q-30 28 6 64 6 4 36 34z m20 646l36-36q36-36 6-64-10-10-29-8t-33 16q-30 30-36 34-14 14-16 33t10 31q10 12 29 10t33-16z m590-702q-36 36-8 64t64-8l34-34q36-36 8-64t-64 6q-30 30-34 36z" horiz-adv-x="1000" /> + +<glyph glyph-name="block" unicode="" d="M480 830q200 0 340-140t140-340q0-198-140-339t-340-141q-198 0-339 141t-141 339q0 200 141 340t339 140z m258-220z m-622-260q0-132 82-230l514 514q-100 82-232 82-152 0-258-107t-106-259z m106-258z m258-106q152 0 259 107t107 257q0 130-82 232l-514-514q98-82 230-82z" horiz-adv-x="960" /> + +<glyph glyph-name="resize-full" unicode="" d="M476 746l316 0 0-316-100 124-146-152-100 100 152 146z m-230-444l100-100-152-146 122-100-316 0 0 316 100-122z" horiz-adv-x="792" /> + +<glyph glyph-name="resize-small" unicode="" d="M156 146l-106 100 296 0 0-296-100 106-146-156-100 100z m744 554l-154-144 104-100-294 0 0 294 100-104 144 154z" horiz-adv-x="900" /> + +<glyph glyph-name="popup" unicode="" d="M700 750q42 0 71-29t29-71l0-400q0-40-29-70t-71-30l-400 0q-40 0-70 30t-30 70l0 402q0 40 29 69t71 29l400 0z m0-500l0 400-400 0 0-400 400 0z m-600 100l0-300 300 0 0-100-300 0q-40 0-70 30t-30 70l0 300 100 0z" horiz-adv-x="800" /> + +<glyph glyph-name="publish" unicode="" d="M900 800q42 0 71-30t29-70l0-600q0-42-29-71t-71-29l-198 0 0 98 200 0 0 462-802 0 0-462 200 0 0-98-200 0q-40 0-70 29t-30 71l0 600q0 40 30 70t70 30l800 0z m-770-168q38 0 38 38 0 16-11 26t-27 10-27-11-11-25q0-16 11-27t27-11z m100 0q38 0 38 38 0 16-11 26t-27 10-27-11-11-25q0-16 11-27t27-11z m672 6l0 62-602 0 0-62 602 0z m-404-198l242-240-150 0 0-300-184 0 0 300-150 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="window" unicode="" d="M900 750q42 0 71-30t29-70l0-600q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 600q0 40 30 70t70 30l800 0z m-670-94q-16 0-27-11t-11-25q0-16 11-27t27-11q38 0 38 38 0 16-11 26t-27 10z m-138-36q0-16 11-27t27-11q38 0 38 38 0 16-11 26t-27 10-27-11-11-25z m810-570l0 460-802 0 0-460 802 0z m0 540l0 60-602 0 0-60 602 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="arrow-combo" unicode="" d="M230 850l230-364-460 0z m0-1000l-230 366 460 0z" horiz-adv-x="460" /> + +<glyph glyph-name="down-circled" unicode="" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-820q148 0 254 106t106 254q0 150-106 255t-254 105-254-105-106-255q0-148 106-254t254-106z m90 554l0-206 112 0-202-190-202 190 112 0 0 206 180 0z" horiz-adv-x="920" /> + +<glyph glyph-name="left-circled" unicode="" d="M920 350q0-190-135-325t-325-135-325 135-135 325q0 192 135 326t325 134 325-134 135-326z m-820 0q0-148 106-254t254-106 254 106 106 254q0 150-106 255t-254 105-254-105-106-255z m552-90l-204 0 0-112-190 202 190 204 0-114 204 0 0-180z" horiz-adv-x="920" /> + +<glyph glyph-name="right-circled" unicode="" d="M0 350q0 190 135 325t325 135 325-135 135-325-135-325-325-135-325 135-135 325z m820 0q0 150-105 255t-255 105q-148 0-254-105t-106-255q0-148 106-254t254-106q150 0 255 106t105 254z m-552 90l204 0 0 114 190-204-190-202 0 112-204 0 0 180z" horiz-adv-x="920" /> + +<glyph glyph-name="up-circled" unicode="" d="M460-110q-190 0-325 135t-135 325q0 192 135 326t325 134 325-134 135-326q0-190-135-325t-325-135z m0 820q-148 0-254-105t-106-255q0-148 106-254t254-106q150 0 255 106t105 254q0 150-105 255t-255 105z m-90-552l0 204-112 0 202 192 202-192-112 0 0-204-180 0z" horiz-adv-x="920" /> + +<glyph glyph-name="down-open" unicode="" d="M564 422l-234-224q-18-18-40-18t-40 18l-234 224q-16 16-16 41t16 41q38 38 78 0l196-188 196 188q40 38 78 0 16-16 16-41t-16-41z" horiz-adv-x="580" /> + +<glyph glyph-name="left-open" unicode="" d="M242 626q14 16 39 16t41-16q38-36 0-80l-186-196 186-194q38-44 0-80-16-16-40-16t-40 16l-226 236q-16 16-16 38 0 24 16 40 206 214 226 236z" horiz-adv-x="341" /> + +<glyph glyph-name="right-open" unicode="" d="M98 626l226-236q16-16 16-40 0-22-16-38l-226-236q-16-16-40-16t-40 16q-36 36 0 80l186 194-186 196q-36 44 0 80 16 16 41 16t39-16z" horiz-adv-x="340" /> + +<glyph glyph-name="up-open" unicode="" d="M564 280q16-16 16-41t-16-41q-38-38-78 0l-196 188-196-188q-40-38-78 0-16 16-16 41t16 41l234 224q16 16 40 16t40-16z" horiz-adv-x="580" /> + +<glyph glyph-name="down-open-mini" unicode="" d="M405 470q22 26 48 0 26-22 0-48l-196-192q-22-22-48 0l-196 192q-26 26 0 48 24 24 50 0l170-156z" horiz-adv-x="466" /> + +<glyph glyph-name="left-open-mini" unicode="" d="M252 180q26-26 0-48-26-26-48 0l-192 194q-24 24 0 50l192 194q22 26 48 0 26-22 0-48l-156-172z" horiz-adv-x="265" /> + +<glyph glyph-name="right-open-mini" unicode="" d="M13 180l158 170-158 172q-26 26 0 48 26 26 48 0l192-194q24-26 0-50l-192-194q-22-26-48 0-26 22 0 48z" horiz-adv-x="265" /> + +<glyph glyph-name="up-open-mini" unicode="" d="M62 230q-26-22-50 0-24 24 0 50l196 190q26 26 48 0l196-190q24-26 0-50-24-22-50 0l-170 158z" horiz-adv-x="464" /> + +<glyph glyph-name="down-open-big" unicode="" d="M63 570l370-356 372 356q22 26 48 0 26-22 0-48l-396-392q-22-22-48 0l-396 392q-26 26 0 48 24 24 50 0z" horiz-adv-x="866" /> + +<glyph glyph-name="left-open-big" unicode="" d="M452-20q26-26 0-48-26-26-48 0l-392 394q-24 24 0 50l392 394q22 26 48 0 26-22 0-48l-358-372z" horiz-adv-x="465" /> + +<glyph glyph-name="right-open-big" unicode="" d="M13-20l358 370-358 372q-26 26 0 48 26 26 48 0l392-394q24-26 0-50l-392-394q-22-26-48 0-26 22 0 48z" horiz-adv-x="465" /> + +<glyph glyph-name="up-open-big" unicode="" d="M804 130l-372 358-370-358q-26-22-50 0-24 24 0 50l396 390q26 26 48 0l396-390q24-26 0-50-26-22-48 0z" horiz-adv-x="864" /> + +<glyph glyph-name="down" unicode="" d="M660 366l-330-380-330 380 192 0 0 350 276 0 0-350 192 0z" horiz-adv-x="660" /> + +<glyph glyph-name="left" unicode="" d="M378 20l-378 330 378 330 0-190 352 0 0-278-352 0 0-192z" horiz-adv-x="730" /> + +<glyph glyph-name="right" unicode="" d="M350 680l380-330-380-330 0 192-350 0 0 278 350 0 0 190z" horiz-adv-x="730" /> + +<glyph glyph-name="up" unicode="" d="M660 336l-192 0 0-350-276 0 0 350-192 0 330 380z" horiz-adv-x="660" /> + +<glyph glyph-name="down-dir" unicode="" d="M460 550l-230-400-230 400 460 0z" horiz-adv-x="460" /> + +<glyph glyph-name="left-dir" unicode="" d="M400 580l0-460-400 230z" horiz-adv-x="400" /> + +<glyph glyph-name="right-dir" unicode="" d="M0 580l400-230-400-230 0 460z" horiz-adv-x="400" /> + +<glyph glyph-name="up-dir" unicode="" d="M0 150l230 400 230-400-460 0z" horiz-adv-x="460" /> + +<glyph glyph-name="down-bold" unicode="" d="M760 366l-380-380-380 380 192 0 0 350 376 0 0-350 192 0z" horiz-adv-x="760" /> + +<glyph glyph-name="left-bold" unicode="" d="M378 730l0-190 352 0 0-378-352 0 0-192-378 380z" horiz-adv-x="730" /> + +<glyph glyph-name="right-bold" unicode="" d="M350 730l380-380-380-380 0 192-350 0 0 378 350 0 0 190z" horiz-adv-x="730" /> + +<glyph glyph-name="up-bold" unicode="" d="M760 336l-192 0 0-350-376 0 0 350-192 0 380 380z" horiz-adv-x="760" /> + +<glyph glyph-name="down-thin" unicode="" d="M500 100l-250-240-250 240 162 0 0 740 176 0 0-740 162 0z" horiz-adv-x="500" /> + +<glyph glyph-name="left-thin" unicode="" d="M240 100l-240 250 240 250 0-160 740 0 0-178-740 0 0-162z" horiz-adv-x="980" /> + +<glyph glyph-name="right-thin" unicode="" d="M742 100l0 162-742 0 0 178 742 0 0 160 238-250z" horiz-adv-x="980" /> + +<glyph glyph-name="up-thin" unicode="" d="M500 602l-162 0 0-742-176 0 0 742-162 0 250 238z" horiz-adv-x="500" /> + +<glyph glyph-name="ccw" unicode="" d="M532 736q170 0 289-120t119-290-119-290-289-120q-142 0-252 88l70 74q84-60 182-60 126 0 216 90t90 218-90 218-216 90q-124 0-214-87t-92-211l142 0-184-204-184 204 124 0q2 166 122 283t286 117z" horiz-adv-x="940" /> + +<glyph glyph-name="cw" unicode="" d="M408 760q168 0 287-116t123-282l122 0-184-206-184 206 144 0q-4 124-94 210t-214 86q-126 0-216-90t-90-218q0-126 90-216t216-90q104 0 182 60l70-76q-110-88-252-88-168 0-288 120t-120 290 120 290 288 120z" horiz-adv-x="940" /> + +<glyph glyph-name="arrows-ccw" unicode="" d="M186 140l116 116 0-292-276 16 88 86q-116 122-114 290t120 288q100 100 240 116l4-102q-100-16-172-88-88-88-90-213t84-217z m332 598l276-16-88-86q116-122 114-290t-120-288q-96-98-240-118l-2 104q98 16 170 88 88 88 90 213t-84 217l-114-116z" horiz-adv-x="820" /> + +<glyph glyph-name="level-down" unicode="" d="M100 200q-42 0-71 30t-29 70l0 350 140 0 0-310 364 0 0 150 240-220-240-220 0 150-404 0z" horiz-adv-x="744" /> + +<glyph glyph-name="level-up" unicode="" d="M200 350l0-90-200 160 200 170 0-100 550 0q40 0 70-29t30-71l0-280-140 0 0 240-510 0z" horiz-adv-x="850" /> + +<glyph glyph-name="shuffle" unicode="" d="M754 516q-54 0-105-32t-80-66-83-104q-48-62-75-94t-78-77-107-66-122-21l-104 0 0 140 104 0q54 0 106 32t81 66 83 104q62 82 101 126t116 88 163 44l36 0 0 120 210-180-210-180 0 100-36 0z m-484-88q-74 78-166 78l-104 0 0 140 104 0q140 0 254-108-14-16-37-45t-27-33q-8-12-24-32z m520-242l0 100 210-180-210-180 0 120-36 0q-140 0-260 116 46 58 72 92 0 2 6 9t8 11q84-88 174-88l36 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="loop" unicode="" d="M800 540q42 0 71-29t29-71l0-290q0-40-29-70t-71-30l-700 0q-40 0-70 30t-30 70l0 290q0 42 30 71t70 29l250 0 0 110 200-180-200-180 0 110-210 0 0-210 620 0 0 210-150 0 0 140 190 0z" horiz-adv-x="900" /> + +<glyph glyph-name="switch" unicode="" d="M700 592l0-140-500 0 0-90-200 160 200 170 0-100 500 0z m300-420l-200-160 0 90-500 0 0 140 500 0 0 100z" horiz-adv-x="1000" /> + +<glyph glyph-name="play" unicode="" d="M486 376q14-10 14-26 0-14-14-24l-428-266q-24-16-41-6t-17 40l0 514q0 30 17 40t41-6z" horiz-adv-x="500" /> + +<glyph glyph-name="stop" unicode="" d="M526 650q74 0 74-64l0-470q0-66-74-66l-450 0q-76 0-76 66l0 470q0 36 18 50t58 14l450 0z" horiz-adv-x="600" /> + +<glyph glyph-name="pause" unicode="" d="M440 700q90 0 90-64l0-570q0-66-90-66t-90 66l0 570q0 64 90 64z m-350 0q90 0 90-64l0-570q0-66-90-66t-90 66l0 570q0 64 90 64z" horiz-adv-x="530" /> + +<glyph glyph-name="record" unicode="" d="M350 700q146 0 248-102t102-248q0-144-102-247t-248-103-248 103-102 247q0 146 102 248t248 102z" horiz-adv-x="700" /> + +<glyph glyph-name="to-end" unicode="" d="M412 374q14-10 14-24 0-12-14-22l-362-228q-22-14-36-5t-14 35l0 442q0 26 14 35t36-5z m114 268q74 0 74-58l0-466q0-58-74-58-76 0-76 58l0 466q0 58 76 58z" horiz-adv-x="600" /> + +<glyph glyph-name="to-start" unicode="" d="M174 350q0 14 14 24l364 228q20 14 34 5t14-35l0-442q0-26-14-35t-34 5l-364 228q-14 10-14 22z m-174 234q0 58 76 58 74 0 74-58l0-466q0-58-74-58-76 0-76 58l0 466z" horiz-adv-x="600" /> + +<glyph glyph-name="fast-forward" unicode="" d="M866 374q14-10 14-24t-14-22l-372-248q-22-14-37-6t-15 36l0 482q0 28 15 36t37-6z m-454 0q14-10 14-24t-14-22l-360-248q-20-14-36-6t-16 36l0 482q0 28 16 36t36-6z" horiz-adv-x="880" /> + +<glyph glyph-name="fast-backward" unicode="" d="M0 350q0 14 14 24l374 248q20 14 36 6t16-36l0-482q0-28-16-36t-36 6l-374 248q-14 8-14 22z m454 0q0 14 14 24l360 248q20 14 36 6t16-36l0-482q0-28-16-36t-36 6l-360 248q-14 8-14 22z" horiz-adv-x="880" /> + +<glyph glyph-name="progress-0" unicode="" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="progress-1" unicode="" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="progress-2" unicode="" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z m250 0l0 198 200 0 0-198-200 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="progress-3" unicode="" d="M1000 450l0-250q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 300q0 40 30 70t70 30l800 0q42 0 71-30t29-70l0-50z m-100-250l0 300-800 0 0-300 800 0z m-750 50l0 198 200 0 0-198-200 0z m250 0l0 198 200 0 0-198-200 0z m250 198l200 0 0-198-200 0 0 198z" horiz-adv-x="1000" /> + +<glyph glyph-name="target" unicode="" d="M430 780q178 0 304-126t126-304-126-304-304-126-304 126-126 304 126 304 304 126z m36-778q124 14 212 102t100 212l-192 0 0 70 192 0q-12 124-100 212t-212 102l0-194-70 0 0 194q-124-14-213-102t-101-212l194 0 0-70-194 0q12-124 101-212t213-102l0 194 70 0 0-194z" horiz-adv-x="860" /> + +<glyph glyph-name="list" unicode="" d="M100 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m0 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m0 200q20 0 35-15t15-35-15-35-35-15l-50 0q-20 0-35 15t-15 35 14 35 36 15l50 0z m200-100q-20 0-35 15t-15 35 15 35 35 15l350 0q22 0 36-15t14-35-15-35-35-15l-350 0z m350-100q22 0 36-15t14-35-15-35-35-15l-350 0q-20 0-35 15t-15 35 15 35 35 15l350 0z m0-200q22 0 36-15t14-35-15-35-35-15l-350 0q-20 0-35 15t-15 35 15 35 35 15l350 0z" horiz-adv-x="700" /> + +<glyph glyph-name="list-add" unicode="" d="M350 400q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z m0-200q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z m620 200q30 0 30-50t-30-50l-170 0 0-170q0-30-50-30t-50 30l0 170-164 0q-30 0-30 50t30 50l164 0 0 170q0 30 50 30t50-30l0-170 170 0z m-620 200q22 0 36-15t14-35-15-35-35-15l-300 0q-20 0-35 15t-15 35 14 35 36 15l300 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="battery" unicode="" d="M770 350q0-98 36-157t78-59l66 0q-30-46-64-65t-118-19l-500 0q-130 0-199 94t-69 206q0 110 69 205t199 95l500 0q84 0 118-19t64-65l-66 0q-42 0-78-60t-36-156z m-136-90q10 12-8 26-136 134-178 164-16 10-26 13t-18-5-10-12-8-18l-22-56-148 66q-26 12-34 0-8-14 8-28 136-132 180-162 34-16 42-11t18 31l24 58 146-68q26-12 34 2z m310 192q22 0 39-27t17-71-17-72-39-28l-38 0q-22 0-38 28t-16 72 16 71 38 27l38 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="back-in-time" unicode="" d="M532 760q170 0 289-120t119-290-119-290-289-120q-138 0-252 88l70 76q82-60 182-60 126 0 216 90t90 216q0 128-90 218t-216 90q-124 0-213-86t-93-210l142 0-184-206-184 206 124 0q4 166 123 282t285 116z m-36-190l70 0 0-204 130-130-50-50-150 150 0 234z" horiz-adv-x="940" /> + +<glyph glyph-name="monitor" unicode="" d="M900 790q42 0 71-30t29-70l0-550q0-42-29-77t-69-43l-218-44 86-38q50-28-20-28l-500 0q-98 0 32 52l36 14-220 44q-40 8-69 43t-29 77l0 550q0 40 30 70t70 30l800 0z m0-646l0 556-800 0 0-556 800 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="mobile" unicode="" d="M480 840q42 0 71-29t29-71l0-780q0-40-29-70t-71-30l-380 0q-40 0-70 30t-30 70l0 780q0 42 30 71t70 29l380 0z m-190-940q30 0 50 15t20 35q0 22-20 36t-50 14q-28 0-49-15t-21-35 21-35 49-15z m210 150l0 660-420 0 0-660 420 0z" horiz-adv-x="580" /> + +<glyph glyph-name="cd" unicode="" d="M460 810q190 0 325-135t135-325-135-325-325-135-325 135-135 325 135 325 325 135z m0-610q62 0 106 44t44 106q0 64-43 107t-107 43q-62 0-106-44t-44-106 44-106 106-44z" horiz-adv-x="920" /> + +<glyph glyph-name="inbox" unicode="" d="M967 398q40-42 30-72l-28-154q-4-20-22-33t-40-13l-816 0q-22 0-40 13t-22 33l-28 154q-8 32 32 72 8 10 36 38t68 67 52 51q22 22 52 22l516 0q30 0 52-22 16-16 53-52t67-65 38-39z m-266-32l178 0-102 114-556 0-102-114 178 0q8 0 12-8l40-100 300 0 40 100q4 8 12 8z" horiz-adv-x="999" /> + +<glyph glyph-name="install" unicode="" d="M884 306q24-52 14-96l-34-184q-2-20-19-35t-39-15l-712 0q-22 0-39 15t-19 35l-34 184q-8 50 14 96l158 374q22 46 72 46l104 0-20-204-134 0 254-210 256 210-136 0-18 204 102 0q50 0 74-46z m-68-132q2 22-10 38t-34 16l-644 0q-22 0-34-16t-10-38l14-74q2-22 19-37t37-15l592 0q22 0 39 15t19 37z" horiz-adv-x="901" /> + +<glyph glyph-name="globe" unicode="" d="M480 830q200 0 340-141t140-339q0-200-140-340t-340-140q-198 0-339 140t-141 340q0 198 141 339t339 141z m410-480q0 132-78 239t-202 149q-18-24-16-32 4-38 18-51t30-7l32 12t20 2q22-24 0-47t-45-56-1-77q34-64 96-64 28-2 43-36t17-66q10-80-14-140-22-44 14-76 86 112 86 250z m-466 404q-112-14-199-84t-127-174q6 0 22-2t28-3 26-4 24-8 12-13q4-12-14-45t-18-61q0-30 38-56t38-46q0-28 8-68t8-44q0-12 36-54t52-42q10 0 11 22t-2 54-3 40q0 32 14 74 12 42 59 70t55 46q16 34 9 61t-17 43-34 28-41 17-37 9-22 4q-16 6-42 7t-36-3-27 11-17 29q0 10 15 27t35 37 28 30q8 14 17 21t22 16 27 21q4 4 25 17t27 23z m-72-794q66-20 128-20 128 0 226 68-26 44-118 34-24-2-65-17t-47-17q-74-16-76-16-12-2-26-14t-22-18z" horiz-adv-x="960" /> + +<glyph glyph-name="cloud" unicode="" d="M760 494q100 0 170-68t70-166-70-166-170-68l-578 0q-74 0-128 52t-54 124q0 74 53 126t129 52q2 0 10-1t10-1q-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z" horiz-adv-x="1000" /> + +<glyph glyph-name="cloud-thunder" unicode="" d="M760 494q100 0 170-68t70-166-70-166-170-68l-578 0q-74 0-128 52t-54 124q0 74 53 126t129 52q2 0 10-1t10-1q-2 12-2 38 0 108 78 184t188 76q90 0 160-52t94-134q28 4 40 4z m-192-216q14 16 14 30 0 20-30 32l-4 0q-26 14-38 16l50 116q6 0 6 20 0 14-8 18-16 10-34-8-2-2-30-32t-61-66-45-52q-12-18-12-30 0-22 30-30l4-2q8-4 38-16l-52-114-2-8q-2-8-2-14 0-10 8-18 18-10 34 10 100 100 134 148z" horiz-adv-x="1000" /> + +<glyph glyph-name="flash" unicode="" d="M40-100q-4 4 35 94t79 182 38 98-94 45-98 55q-4 12 84 120t180 209 96 97q6-4-74-186t-78-186 95-43 97-57q4-20-174-227t-186-201z" horiz-adv-x="400" /> + +<glyph glyph-name="moon" unicode="" d="M524 238q106 106 125 252t-53 270q52-26 96-72 128-128 128-309t-128-309-310-128-310 128q-40 40-72 94 124-70 271-51t253 125z" horiz-adv-x="820" /> + +<glyph glyph-name="flight" unicode="" d="M268-120l124 400-180 0-112-100-100 0 80 170-80 170 100 0 112-100 180 0-124 400 100 0 224-400 274 0t36-4 46-11 36-21 16-34q0-32-38-49t-74-19l-38-2-258 0-224-400-100 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="paper-plane" unicode="" d="M894 720q14 4 22-3t4-19q-2-6-72-310t-74-316q-2-14-14-19t-24 1l-248 134-30 16 22 26q388 420 394 426 4 4-1 9t-9 1l-550-402-112 44-190 76q-12 4-12 12t12 12q8 4 441 157t441 155z m-582-728l0 204 160-82q-130-116-142-128-18-14-18 6z" horiz-adv-x="921" /> + +<glyph glyph-name="leaf" unicode="" d="M236 646q182 106 506 66 168-22 196-50 4-6-2-10-76-40-130-109t-78-132-65-132-93-105q-138-96-382-4-66-76-114-176-12-24-47-7t-25 39q44 100 129 193t176 153 176 106 141 68l54 20q-14 0-41-1t-104-14-148-38-162-84-161-141q-22 242 174 358z" horiz-adv-x="940" /> + +<glyph glyph-name="lifebuoy" unicode="" d="M454 810q190 2 326-130t140-322q2-190-131-327t-323-141q-190-2-327 131t-139 323q-4 190 130 327t324 139z m0-60q-94 0-178-44l62-104q56 28 122 28t122-28l62 104q-88 46-190 44z m-246-522q-28 60-28 122 0 64 28 124l-102 62q-46-88-46-190 2-96 46-180z m258-278q98 4 178 46l-62 104q-60-30-122-30t-122 30l-62-104q86-46 190-46z m-6 180q92 0 156 65t64 155q0 92-64 156t-156 64-156-64-64-156q0-90 64-155t156-65z m252 98l104-62q46 96 44 190 0 96-44 180l-104-62q28-60 28-124 0-62-28-122z" horiz-adv-x="920" /> + +<glyph glyph-name="mouse" unicode="" d="M551 130q28-80-17-157t-139-111q-94-28-175 9t-103 117l-106 384q-20 68 6 134t84 106l-96 186q-14 34 14 48 30 18 48-14l98-192q80 22 154-16t102-116z m-324 274q28 10 40 36t4 54q-10 28-35 41t-53 5q-28-10-40-36t-4-54q10-28 35-41t53-5z" horiz-adv-x="561" /> + +<glyph glyph-name="briefcase" unicode="" d="M456 326l0-100-456 0q8 226 10 292 4 108 100 108l160 0q16 26 37 67t23 45q14 26 23 32t37 6l222 0q26 0 36-7t22-31q18-32 60-112l160 0q96 0 100-108l10-292-454 0 0 100-90 0z m-74 354l-28-54 292 0-28 54q-14 26-42 26l-152 0q-28 0-42-26z m164-604l0 100 430 0q-6-88-10-166-6-84-90-84l-750 0q-90 0-90 84l-10 166 430 0 0-100 90 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="suitcase" unicode="" d="M900 650q42 0 71-30t29-70l0-550q0-42-29-71t-71-29l-50 0 0 750 50 0z m-900-100q0 40 30 70t70 30l50 0 0-750-50 0q-40 0-70 29t-30 71l0 550z m670 204l0-104 110 0 0-750-560 0 0 750 110 0 0 104q98 46 170 46t170-46z m-60-104l0 66q-52 24-110 24-54 0-110-24l0-66 220 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="dot" unicode="" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z" horiz-adv-x="220" /> + +<glyph glyph-name="dot-2" unicode="" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 32-32 78 32 78 78 32z m350 0q46 0 78-32t32-78q0-44-33-77t-77-33q-46 0-78 32t-32 78 32 78 78 32z" horiz-adv-x="570" /> + +<glyph glyph-name="dot-3" unicode="" d="M110 460q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z m350 0q46 0 78-32t32-78q0-44-33-77t-77-33-77 33-33 77q0 46 32 78t78 32z m350 0q46 0 78-32t32-78q0-44-32-77t-78-33-78 33-32 77q0 46 32 78t78 32z" horiz-adv-x="920" /> + +<glyph glyph-name="brush" unicode="" d="M118 170q38 34 85 29t87-45q42-40 48-87t-30-83q-86-84-228-102-84-12-80 14 0 4 6 10 52 60 64 145t48 119z m840 646q26-26-148-248t-292-338q-38-38-124-104-8-6-16 8-18 34-48 64-32 32-66 48-16 6-8 16 64 84 104 122 118 116 344 287t254 145z" horiz-adv-x="962" /> + +<glyph glyph-name="infinity" unicode="" d="M796 570q84 0 144-53t60-167q0-112-60-166t-144-54q-78 0-157 40t-139 106q-58-66-137-106t-157-40q-86 0-146 54t-60 166q0 114 60 167t146 53q78 0 157-39t137-105q58 66 138 105t158 39z m-590-352q60 0 127 37t113 95q-46 58-112 95t-128 37q-114 0-114-132t114-132z m590 0q114 0 114 132t-114 132q-62 0-129-37t-111-95q44-58 111-95t129-37z" horiz-adv-x="1000" /> + +<glyph glyph-name="erase" unicode="" d="M902 700q42 0 71-29t29-71l0-500q0-40-29-70t-71-30l-478 0q-38 0-70 28l-340 296q-28 26 0 54l340 296q30 26 70 26l478 0z m-140-550l72 74-128 126 128 128-72 72-128-126-128 126-72-72 128-128-128-126 72-74 128 128z" horiz-adv-x="1002" /> + +<glyph glyph-name="chart-pie" unicode="" d="M368 770l0-368-368 0q18 146 121 249t247 119z m106 0q156-20 261-139t105-279q0-174-123-298t-299-124q-160 0-278 105t-140 263l424 0q20 0 35 14t15 36l0 422z" horiz-adv-x="840" /> + +<glyph glyph-name="chart-line" unicode="" d="M34 284q-42 10-32 56 10 42 54 32l98-24-52-80z m890-12q14 12 33 11t31-15q32-32-2-64l-252-226q-12-12-30-12-14 0-28 10l-286 220-54 14 50 80 36-8q12-4 16-8l264-204z m-490 220l-350-550q-12-22-38-22-12 0-24 8-16 10-20 29t6 33l374 588q8 16 28 20 18 6 36-6l246-156 226 326q10 16 28 19t34-9q38-24 12-62l-252-362q-24-36-62-12z" horiz-adv-x="1003" /> + +<glyph glyph-name="chart-bar" unicode="" d="M750 800q22 0 36-15t14-35l0-850-200 0 0 850q0 50 40 50l110 0z m-300-300q22 0 36-15t14-35l0-550-200 0 0 550q0 50 40 50l110 0z m-300-300q22 0 36-15t14-35l0-250-200 0 0 250q0 50 40 50l110 0z" horiz-adv-x="800" /> + +<glyph glyph-name="chart-area" unicode="" d="M964 732q16 22 16-4l0-768-964 0q-12 0-15 7t5 17l230 288q20 22 40 2l74-66q10-8 21-7t17 11l158 238q16 26 38 4l112-104q20-20 38 4z" horiz-adv-x="980" /> + +<glyph glyph-name="tape" unicode="" d="M770 580q96 0 163-67t67-163q0-94-67-162t-163-68l-540 0q-94 0-162 68t-68 162q0 96 68 163t162 67q96 0 163-67t67-163q0-72-40-130l160 0q-40 64-40 130 0 96 68 163t162 67z m-670-230q0-52 38-91t92-39 92 39 38 91q0 54-38 92t-92 38-92-38-38-92z m670-130q54 0 92 39t38 91q0 54-38 92t-92 38-92-38-38-92q0-52 38-91t92-39z" horiz-adv-x="1000" /> + +<glyph glyph-name="graduation-cap" unicode="" d="M166 238l334-168 276 136q-4-22-8-47t-6-35-11-23-24-23-45-22q-40-18-80-41t-63-34-39-11-40 13-64 37-80 40q-72 32-103 69t-47 109z m810 246q24-14 24-33t-24-33l-78-44-308 102q-22 36-90 36-40 0-67-16t-27-40 27-40 67-16q26 0 36 4l292-68-268-152q-60-32-120 0l-416 234q-24 14-24 33t24 33l416 234q60 32 120 0z m-128-442q18 116 13 182t-19 90l-14 22 70 38q6-8 12-28t17-101-7-197q-4-26-22-30t-35 5-15 19z" horiz-adv-x="1000" /> + +<glyph glyph-name="language" unicode="" d="M988 306q30-82-10-176t-134-160q-10 0-12 2t-16 19-16 19q-2 6 2 10 86 60 117 152t-11 148q-16-38-39-76t-59-80-86-65-106-15q-52 6-84 41t-32 93q0 84 60 148 50 50 114 66l-2 100q-140-24-146-24-6-2-10 4 0 2-5 29t-5 31q-2 2 1 4t7 2l156 28q0 110-2 114 0 8 8 8 46 0 52 2 10 0 10-8l0-104q158 22 164 22 8 4 10-6 0-2 4-23t4-25q4-10-4-12l-176-30 0-102 12 0q86 0 148-36t86-100z m-370-160q28-6 62 6l-4 214q-34-12-60-40-44-44-44-108 0-66 46-72z m122 28q28 24 58 68t45 79 7 41q-36 18-96 18-2 0-6-1t-6-1z m-448 382q10-28 53-165t83-261 40-126q0-4-4-4l-86 0q-6 0-6 4l-50 166-176 0q-48-164-50-166 0-4-6-4l-86 0q-4 0-4 4 10 18 176 552 2 8 10 8l96 0q10 0 10-8z m-130-316l144 0-72 264z" horiz-adv-x="1001" /> + +<glyph glyph-name="ticket" unicode="" d="M216 272l326 326 178-178-326-326z m710 244q14-14 14-36t-14-36l-550-550q-16-16-36-16t-36 16l-76 76q12 20 12 48 0 42-29 72t-71 30q-22 0-50-14l-74 76q-16 16-16 36t16 36l550 550q14 14 36 14t36-14l74-76q-12-22-12-48 0-42 30-71t72-29q26 0 48 12z m-532-502l406 406-258 258-408-406z" horiz-adv-x="940" /> + +<glyph glyph-name="water" unicode="" d="M168 844q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155q2 4 7 4t5-4z m616 0q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 48 21 93t48 78 53 92 34 127q2 4 7 4t5-4z m-320-444q2 4 7 4t5-4q10-86 50-155t73-123 33-112q0-66-48-113t-114-47-114 47-48 113q0 58 33 112t73 123 50 155z" horiz-adv-x="940" /> + +<glyph glyph-name="droplet" unicode="" d="M290 822q14-118 60-219t92-159 82-136 36-160q0-114-83-196t-197-82-197 82-83 196q0 82 36 160t82 136 92 159 60 219q2 8 11 8t9-8z m-42-392q2 4-2 14-6 6-14 6t-12-6l-40-58q-32-46-48-70t-34-75-18-101q0-24 17-41t41-17q58 0 58 68 0 94 42 246 2 6 5 17t5 17z" horiz-adv-x="560" /> + +<glyph glyph-name="air" unicode="" d="M85 534q-16-14-36-12t-34 18q-14 14-12 36t18 36q48 40 79 60t89 40 129 4 159-66 155-53 100 16 89 67q38 30 70-6 32-40-6-72-122-110-234-110-100 0-222 70-68 38-119 52t-93 0-65-29-67-51z m736-110q38 32 70-6 32-40-6-72-40-34-65-53t-72-38-97-19q-96 0-222 70-68 38-119 52t-93 0-65-29-67-51q-14-14-35-12t-35 18q-32 40 6 72 38 34 60 50t69 38 88 23 105-15 134-56q68-38 119-52t93 0 65 29 67 51z m0-256q38 32 70-6 14-14 12-36t-18-36q-40-34-65-53t-72-38-97-19q-96 0-222 70-68 38-119 52t-93 1-66-29-66-52q-14-14-35-12t-35 18q-32 40 6 72 38 34 60 50t69 38 88 23 105-15 134-56q68-38 119-52t93 0 65 29 67 51z" horiz-adv-x="905" /> + +<glyph glyph-name="credit-card" unicode="" d="M900 700q42 0 71-30t29-70l0-500q0-42-29-71t-71-29l-800 0q-40 0-70 29t-30 71l0 500q0 40 30 70t70 30l800 0z m0-600l0 300-800 0 0-300 800 0z m0 450l0 50-800 0 0-50 800 0z m-700-256l30 0 0-30-30 0 0 30z m180-60l30 0 0 30 30 0 0 30 60 0 0-30-30 0 0-30-30 0 0-30-60 0 0 30z m120-30l-30 0 0 30 30 0 0-30z m-150 0l-60 0 0 30 60 0 0-30z m30 60l0-30-30 0 0 60 60 0 0-30-30 0z m-120-30l0-30-60 0 0 30 30 0 0 30 30 0 0 30 60 0 0-30-30 0 0-30-30 0z" horiz-adv-x="1000" /> + +<glyph glyph-name="floppy" unicode="" d="M658 750l142-156 0-544q0-40-29-70t-71-30l-600 0q-40 0-70 30t-30 70l0 600q0 42 30 71t70 29l558 0z m-58-300l0 250-400 0 0-250q0-20 15-35t35-15l300 0q20 0 35 15t15 35z m-50 200l0-200-100 0 0 200 100 0z" horiz-adv-x="800" /> + +<glyph glyph-name="clipboard" unicode="" d="M630 750q28 0 49-21t21-49l0-760q0-30-21-50t-49-20l-560 0q-28 0-49 20t-21 50l0 760q0 28 21 49t49 21l60-150 440 0z m-100-100l-360 0-44 100 108 0 36 100 160 0 36-100 110 0z" horiz-adv-x="700" /> + +<glyph glyph-name="megaphone" unicode="" d="M792 500q58-138 67-258t-39-140q-28-12-61 3t-65 40-99 41-149 8q-28-4-42-19t-6-37q22-56 46-108 4-10 24-22t24-20q14-34-22-46-50-22-102-40-30-10-54 42-32 76-58 132-6 12-34 17t-46 31q-30-10-38-14-34-12-74 12t-54 60q-17 32-5 79t43 61q126 52 213 108t124 103 59 92 25 78 15 59 36 36q48 20 130-70t142-228z m-28-300q8 4 10 38t-11 98-41 128q-28 66-67 123t-67 84-36 23-10-42 10-105 40-133 68-119 68-76 36-19z" horiz-adv-x="860" /> + +<glyph glyph-name="database" unicode="" d="M686 208q14 20 14-2l0-100q0-74-104-135t-246-61q-140 0-245 61t-105 135l0 100q0 8 4 10t10-8q32-52 125-86t211-34 211 34 125 86z m2 254q8 16 12 0l0-116q0-68-102-114t-248-46q-144 0-247 46t-103 114l0 116q0 20 14 0 30-46 124-75t212-29 212 29 126 75z m-338 328q144 0 247-39t103-93l0-64q0-58-103-99t-247-41-247 41-103 99l0 64q0 54 103 93t247 39z" horiz-adv-x="700" /> + +<glyph glyph-name="drive" unicode="" d="M884 304q26-44 14-96l-34-184q-2-20-19-35t-39-15l-712 0q-20 0-38 15t-20 35l-34 184q-8 52 14 96l158 374q22 46 72 46l408 0q50 0 74-46z m-68-132q2 22-10 38t-34 16l-644 0q-22 0-34-16t-10-38l14-74q2-22 19-37t39-15l590 0q22 0 39 15t19 37z" horiz-adv-x="902" /> + +<glyph glyph-name="bucket" unicode="" d="M522 780q174 0 286-49t104-105q-6-38-48-307t-44-281q-2-18-37-44t-107-50-154-24-153 24-106 50-37 44q0 2-4 30 82-6 163 35t139 117q28 0 48 20t20 50q0 28-20 49t-50 21q-28 0-49-21t-21-49q0-20 10-36-48-58-115-89t-131-27q-102 10-157 57t-59 109q-8 122 156 184-18 94-22 138-8 56 104 105t284 49z m-452-470q4-32 37-59t91-39l-32 204q-100-44-96-106z m452 212q82 0 157 18t113 39 38 35-38 35-112 39-158 18q-82 0-156-18t-112-39-38-35 38-35 112-39 156-18z" horiz-adv-x="913" /> + +<glyph glyph-name="thermometer" unicode="" d="M400 356q64-36 102-98t38-138q0-112-79-191t-191-79-191 79-79 191q0 76 38 138t102 98l0 444q0 50 40 50l170 0q20 0 35-15t15-35l0-444z m-130-406q70 0 120 50t50 120q0 56-32 100t-84 60l0 370-100 0 0-368q-54-16-89-61t-35-101q0-70 50-120t120-50z" horiz-adv-x="540" /> + +<glyph glyph-name="key" unicode="" d="M774 612q20-116-28-215t-150-117q-66-12-130-2l-118-194-70-12-104-166q-14-28-46-32l-76-14q-12-4-22 4t-12 22l-16 98q-8 30 12 56l258 386q-24 50-38 120-18 106 53 187t185 101q106 20 195-45t107-177z m-126-76q30 44 21 97t-51 83q-42 32-92 22t-80-54q-8-12-12-23t-1-20 5-16 13-17 18-15 22-16 23-17q6-4 22-16t23-16 19-12 19-8 17 1 18 8 16 19z" horiz-adv-x="780" /> + +<glyph glyph-name="flow-cascade" unicode="" d="M520 120q50 0 85-35t35-85-35-85-85-35q-80 0-110 74l-164 0q-88 0-131 54t-43 118l0 464q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-114q0-78 78-78l164 0q30 72 110 72 50 0 85-35t35-85-35-85-85-35q-80 0-110 74l-164 0q-42 0-78 16l0-194q0-78 78-78l164 0q30 72 110 72z m0 300q-28 0-49-20t-21-50q0-28 21-48t49-20 49 20 21 48q0 30-21 50t-49 20z m-470 280q0-28 21-48t49-20 49 20 21 48q0 30-21 50t-49 20-49-20-21-50z m470-768q28 0 49 20t21 48q0 30-21 50t-49 20-49-20-21-50q0-28 21-48t49-20z" horiz-adv-x="640" /> + +<glyph glyph-name="flow-branch" unicode="" d="M640 650q0-80-74-110-6-58-28-101t-61-69-68-38-75-26q-42-14-63-22t-47-24-38-40-16-60q70-30 70-110 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-204q40 30 138 60 58 18 84 29t51 41 29 76q-70 32-70 108 0 50 35 85t85 35 85-35 35-85z m-588 0q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z m400 600q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="640" /> + +<glyph glyph-name="flow-tree" unicode="" d="M868 112q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 114q0 78-76 78l-100 0q-44 0-78 12l0-204q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 204q-30-12-76-12l-100 0q-34 0-53-19t-22-33-3-26l0-114q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 114q0 64 43 118t131 54l100 0q76 0 76 52l0 140q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-140q0-52 78-52l100 0q86 0 129-54t43-118l0-114z m-678-112q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m212 700q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m138-700q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m280-68q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="940" /> + +<glyph glyph-name="flow-line" unicode="" d="M168 162q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-378z m-116 488q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="240" /> + +<glyph glyph-name="flow-parallel" unicode="" d="M240 650q0-76-72-110l0-378q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85z m-50-600q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20 49 20 21 48z m-70 532q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z m448-420q72-34 72-112 0-50-35-85t-85-35-85 35-35 85q0 78 72 112l0 378q-72 34-72 110 0 50 35 85t85 35 85-35 35-85q0-76-72-110l0-378z m-116 488q0-28 20-48t48-20 49 20 21 48q0 30-21 50t-49 20-48-20-20-50z m68-668q28 0 49 20t21 48q0 30-21 50t-49 20-48-20-20-50q0-28 20-48t48-20z" horiz-adv-x="640" /> + +<glyph glyph-name="rocket" unicode="" d="M543 236q6-50 8-81t-8-59-13-40-35-32-45-26-70-31-85-37q-32-12-45 4t-3 44l40 110-130 132-106-40q-28-12-43 2t-3 46q12 30 31 79t27 65 22 45 25 36 29 20 41 13l52 0t71-6q10 14 29 39t77 85 118 104 145 75 165 19q8 0 14-6 4-4 6-14 10-82-18-168t-76-151-98-118-86-81z m50 296q22-22 54-22t54 22q22 24 22 56t-22 56q-22 22-54 22t-54-22q-22-24-22-56t22-56z" horiz-adv-x="860" /> + +<glyph glyph-name="gauge" unicode="" d="M406 178q34 56 214 284t194 220q12-6-96-278t-138-326q-50-86-136-36t-38 136z m94 380q-168 0-284-127t-116-311q0-30 2-46 2-22-12-37t-34-17-36 12-18 34q0 8-1 26t-1 28q0 226 145 382t355 156q72 0 134-18l-70-86q-40 4-64 4z m362-62q138-154 138-376 0-38-2-56-2-20-16-33t-34-13l-4 0q-22 4-35 20t-11 36q2 14 2 46 0 150-80 268 6 14 20 51t22 57z" horiz-adv-x="1000" /> +</font> +</defs> +</svg> \ No newline at end of file diff --git a/src/mol-app/skin/fonts/fontello.ttf b/src/mol-app/skin/fonts/fontello.ttf new file mode 100644 index 0000000000000000000000000000000000000000..39a234370e14bbf53e699758840b2ea5de8ccede GIT binary patch literal 49104 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 X@uERw2LDyI_MM{Yw=q$GclQ4Qo9#|w literal 0 HcmV?d00001 diff --git a/src/mol-app/skin/fonts/fontello.woff b/src/mol-app/skin/fonts/fontello.woff new file mode 100644 index 0000000000000000000000000000000000000000..674807bae0bb1793ab6274a50737b707d9356fd2 GIT binary patch literal 29392 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	Xlb?M=rJv=ekMXZ)^zi@8gVJB41Z6%{7jl07 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 F001y~g2(^> literal 0 HcmV?d00001 diff --git a/src/mol-app/skin/fonts/fontello.woff2 b/src/mol-app/skin/fonts/fontello.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..49eb9640642432843920fcacac3346c95157ba26 GIT binary patch literal 23892 zcmb4qLv$t#ux#v!ZD(TJ{9@a-@x`|7WMbPB+qP}nljQ#Qyw!WVSDQZFr+Zn8u2bc% zAi)9x3i2QHR6&sbxAx9~fat#bpV|NR|2ObOpwvOKv|)&0(;QJ?;DSm$0^b3k9AM<& zVW#lGY#<QCkfCzWiFycuP<Z;KRHpjyxt#>qi|W_rF;Q9dmh<Z{n}^8Mwi#bPKb}&f z*aP(>W?5Q2-|*)t_&p-_;!!iGof<~e*AWcUyqn`qQJhRHF|;=g3K%rO(|3W*;#oKK zWn?$!goqU%D|ItjSss#cIagqHS=m6XT(6`E<@&avqCR%U7n^rfS)M}0PGnv=!NWu6 zG76(IH<)?tP)qHI-)!-wMr#tjjJ^zy%F7}Lz0Z=ZeNOs{F}cWz_}rbJ&C+5Q5i}UZ zEa!cQ)tc+C%-6oElbN2!KMK-2#@;fW+6&nj`uHU{Aw@R2`m7)@Z0Y`g1lQNg<`by+ z<`s1)V97D}FuXxQ;w>*9rP6GX`WFCyx%(J|nn}*G5EQdth=wmkYKhBmF;I|%#KgqN zLy&_YV1>!aXs|_VBt*_bP-S8oYTHw^i&NZUxwAHCnU~n<NKQ+$tg~=0JE~=_Qwi&A z^zz1=hHjxh%5>_GghJ^Jjj2S^_Lcu`x*`1&-00C#3Q}+x)M-F4uX=~-frHJe#$Xrc z*K=d>SXOyP@OAnAvGu-9&U|3Y7T1moTn+hHHW|4>K-Z2C`XFpezod8Y2X6Lmzc?iS zf>o4>Ai*f72&^O~100_o{<3XzCnucgzhT}MWoZcIrY$iFl;}J63i-X&G=Ej9Z(x*< zWXt&y7jmKKiw3s=qdfYp{UKBV5t&T0PLQka@A~-Tq8z*+A_4|$Pp^9G)-!Q2*vO~D zIil~3wnf%wTDlSW;HU{Uzf-bI?ebFIxB~9nRJ8o}gulYYr)8yg8=k1c;)UT1L@@(K z9NhgC#Aw=eSWaULS{<~}C_O4lpq%gIHOtCXc7bD+z2(*KCgP1;m?Yw7t@LF#4^aQ8 zhG9n%Z0kZ9OU^OfdtAL>P%@(H(l>R*;IeC)z2Gw9O`w$jb1C%mLnEaICR@>08gg9< zv0anYr!Hk*mvE*}vh~U2qNkw-YeMih9CZyG*y}fT!_C(nZt>D_L%u_t$+jiM8Ot%t ziPVN#5b`G;&njUbI-oQ&KNlJm6WZBJ$hEIbUW`7RVDDe@5trO5P>|*H$9urBMW2>f zF_xSpQ7j_B76b6P^MluKH|0(w3!NRYMkld{t;6Ma*8rzMA<+#Ag+CBj4GaVM=OGNb zF#pT|#XI9g20FGRl?bxdfK9l_xa6qJ!S2jLtdRfslxEoCkO+{bz~5mr+FZd};{AR# za_q3X0WCi9?>GC^wS)umLW!LZatD$I$X!9uih%don6J$V|JM*pOhZc0=H2oA^B@O< zKjYVqwW&(c<)}wMU*pqXWl{3(&-DkDDMyl2MNwI4ac*H=CD~~bbHRyU5T=Mrs;TRD zRfkol9~b`UxoeNE!)y3x*=hP}+ba$T3AQkE4l#9>5w|`vg_aWgHWU5k|8s?1=Bd^8 z#Z~v|*-P~QyB}GyFv3G*JW<jraI$d<hXM~R=88zi{JOi+dXK;n#1|}%5>+p$iB5-$ z2_79f+<#yMTQ8=GN{5pM7CfMA#Dt9>7C>V3dku(<n+7%5SI>lxfJD|8QSYh=MTgc3 zo(4G?cHAdfuZxY723^$mU<6q&qKOg%SJZ!2Z;TDD4rVeSZA7Pu6Z4BU+{qAwiJ=;G zBv`Hgtv;6t)D}w?X7d+X|4ZLceK!+qH7;f7b?~?$I}^99pe!gah{}Mq5sfB+>@Qvz zaKmS&U)7*1Fg8I}Mm(CVSIE_nK<v`+ZZMnBZaACZmch6_9z&=#r3{o8+$jie0;qww z{;7ISL(DZNO{y!o3=AOH3M4P=Wms;|bKg$T^T0R6WpHj-ZeVU`Zg6h+^B|NV$J%DS z@ER`@uPw|;KsUV0z)o<xp?}z~!EZ3UK)rz20fqsF{@T8sQD#0oljZO^K$trbQl7wZ zvCW*DC4Ps|+sR4SY(DH+-5j|q+JlgPaMv9D81#17jgWi6=N$Oi?VRYDi{-@K<_cSz z7lbe9v{2KW$(hU<>=OnXG=JzxL0tjl8POc+lfoF%7rb`R_b<qNha8`xckOkvyD7pi z_p|Uj#lBakl;U)M%X9<OA0kr5S@K1nT6_LN_3sm?Z%J?C(O8#0!kreQco0EoB;la- z_V8l);ZX)mpR?O6OkY=)jUVjX_R>vMna=sZ3Y6(PyHcstLx!>~0>U+ls1pwNg5yI~ zC;>loqxT_vl;Wlk16&>GXG{5~MIjGqC?AYj<p;$SxY*Vt91V$16aX=)>Gq$5DbYn& z=~b&|(uj#XY)_^c79JAvI1UE4AnRD|tC5*;JI0gk(yKB{YKg&MF0yGH*oxXyqX?xc ze#-{K!S52{jDShSxdVLaL<9vs37TMT_1u3A63M7k9<)?II;8n_*{fKSz4(DB+B5bk zI*;+fK9Rqmw<jF*HcNUZSb?+@7>F{;hgUhm^H~5UTG9716Mpr5ntmF@S(|yV`ZL>? z6%8_LzK#)HZ70IGRNL4`2OKXC=Mt%UFDE*LB$!AjlOChLvhhMBnBf(IuA<rIa5arQ zyLKKBj}bne*i~wSY|t8;obf~H-uK6#!d6>+`Ftf)aJhcy7ahz=&^!uv4AuoJ42v0K zGb!P3_SKLU#+V&4(ef1(GjS;H%4p$lMoj>e9Opo)jzidzF*EIn;VLDPO=Og=d9<U- z^Gl(9)n5dHF|IJUN+gP4r0^4x-%SRxqNu8&H?ur1EM3(coJjz)P^)DV6E$d}H9Dvu zJ&<vRz`@#p$=bkC`+!**V#DS8dca$QK1;vY)bJ3@zA5%T`aZ%w{yx|~>OR~)t{fl+ zy=A6^I?z?Sbe4^apCH9ugyqI&Nj9u_JMYg>atWJ~5>+){qmlJ_LwyDQ%h80O0nijJ z;<|=aCyg<Y^ynPS&0rL%V2rwpqStyzA;J<?^CzI?a<`PfGPKlgSY_fdx+pS^_0HZi zWL7crI@L`fbXLf9U^9P0nj_)Snmurmhxf7T)8BX@>cH`ul?3K1n$oEA??DBMrs!sv zNvE>qbG0{uRkw<Yy2bF4d~%Qkazu1dT28F@eW!qVupB3>`8G8K8f8T^j9&uBOh(6; zE@lYQ0YmXag>XeqBZ8V0ko=WFeE52p{H$+eIZUpdS;B6z<c_pScKm}mR{iA{Hm$r8 zVmZQbm^M^C)%A-3<T-g#S>jIu7c81kSdHKVn-ml!gqJ0PE2*kdvzqSjM-i)ycZODN z&Q-A??i5wUEonB}^~XyZ|3qmH?E>X|zvMr_3nM+uB!^D%S%t2caYQ-0NZ+1&za=kg zo6?F}ua088YxOhOv^f7&UuBobG|&ofcV$(T{8EZJ?)>E>CnOGr22-VJZA)zpn|M=S zR~wt<rN~gPw_nUw9=|UwC6!icc7Io2D5XD<4F(}U&Ri7D(2*bwo=(O{Ri#zDxb`bd zhNdYW;FPUXt<Xex1wPDV=oWQAssJ_PWot%qp@M4U>z2hs;|1tgZ9>ljR|1$dK`Dn} zT8*6owvn2RplSO`V7$5dZlSXr12B-4L&)%vU%~0Q`r#z02XGL_J_CWehL9=f`*6@p zUqY<*^<dgjj^QpU1_5uzEV6LckP;F_i;VpvXNGZ-*{1ykHYBdWJZrpW&g9{L0_Wht zZsbGx#tR&XpMvpUv3#&d2fBll5&>$oK93~U0Iw@`;}7b<UMUD3uT8#7lmaKxefV{U zw$393_jMDWseSlO9BF~5;QGKeEYGfb$<thEdF@(k6u|wHgNpxr!G59S5{VCot~xog zKuV^N{KcAxamo&3yXr;)wEiGbDx&~2N_g}G>33}~d&#_Z+Nh0o?a>2F8f$zPki~^0 zTm73lk-sGJFp1Y8QL(1DI18VXm*@o`-~-)~(RXiJI@Px^=GIDFLbey*jrpyBIl4G( zW6X(Rl$G=M`?uWVQpPzQ8)=D$J@}`)PK4ID<Cq$tfJ*FVuiVNL6^t@X=G`2k+z~}^ zfvT#Zkj#E;Zu^qK63O=#Un_bW1MANwxHV)L{bFAPVoa-Nz%hDPBN7-J+B>^b40|iG z*<XdB{`w*uz$zt&O(!Z82#%URn6E#$IV+k<i~uvQ*xMcF1=CHiFw7JgqKSznW=d7$ zu@zg=T$oF~cIN6$s7)U9bO1WUg1O1?-Qgw>XV7ttj!ljlTib~`(^n%;oA@D~(~3#> zbW0k4yv{AyK|4W0&WbG;3)?X7mp5QspP~|dn<HtkJSV<i1Ms0jXWMrUw{j+9=Qwkr z+y5e~d(^{a!URu4@zt}L*>-Z3q{Ts*{i16_GPIqOP{t+6W$6si9xxvvbr@jq=Fh?C z+R-LEEtY`i%Spa8VJ%6NjGOf`F_zMG0(dI)jEKftC11KQ1FxY-!&veqR}DFL6{RbX zigP7`v$?hy={l}_o|Hc6qB>n?bUsfE{jkt(yS$}Ob_9x=jC+W#uHn?1{Z>?oE<=n- zJL90a18P%~am4LcN7i=4T2hyqU0P0y2MtE79I0+PdJ?mV?@1yW#iF8Qmq$iy=<TY# z=z5ly83FylDGsJ!#Sc0@dMS0V&LFXT^#~_RJ~i9SP~5_pDaFI8xBhOU(QZX6_UIx) zLXu-+^q?cWD=DGdIU1HIVB&Bkr;N}qWXy(+OspWmfY@M-Raa$4k}0;o=uC)Ae;?hU zZ6@T*;bp>d#3}U?!qW|#%!af@lgF%61!7hhiwu$hIYFu6IK{g0;KafcaBzVB8`m@| zG(eUN&IUUFMq~;W+is4I<iWpx-RAsBK8kq)I-Xqc>3(c7&%{wGj}5R@VM(K=IbOLP zDbtj;T>rRu*SAsxyr_#bvGU(688V`*GCuAk$eEviE;15`*Wr{~k&CLsC=zBCid1Vc za8d~pV8QI;=q2CPs=+3=hx%4viVcRHbSf5V=t5c*w38=eV>7d?#8(vhyMSPGA=1U$ zGo(7mGS2G>f_@)^oPxeDkHQxE?SikP%9U)B&w7?NU?&`5phZjMxs^r<G<pWP!pXS) znT^o&>uWp?T+zhL8R^A*<|<x0=5m)MPY&_CGsGpA*i9Ee*sb<Bd8ht`>#>(K)G0<K zSXS@03&@4#41$_p-q!WOP$Cf5(_F=*?F_|9a1*d4uJqbNE=vh61oE}pO@{DQxzg`S zOHX>6&-jPc?Ptx1bXi2kP>-X`ReR;Lzv(sU-#_JuI$Hhh{Xbb&?MNYFC4JNC_PKP8 z={?&8l{IZYmMr6}GgrnMJ-)ufjYigSW6}Sxhprpsviw%w%x0ZCx^{R)FzvuyEFJ`< zOB)<Y>PB{uUF!B+&GP%YzNd!Q9E1`w){{)7SL1Y>>>6J)iw@jeA;7t*J~u$Y^>tKZ z&+Im(CnLaN<{F$HOy$zY>S_HJI&d{94$#bAl}fUlaz(ef)Xk_8{DpV=Vyfj5hbNDy z3wx7y2?Hl6Pwy!igfG=e@SMB!eDBilNR<<-{LqsA&nCqsK_KwGb81D8;b!M6Xu&F# zb>;F6b7piGpp<b74=FBCz)XVvj(^_PobuLUtCDTAT86Fg7Men$)9K>V9d9Ce>jV<X zpToS6+~Lw~b#2YknoT-dd4X}yagZ<A|0t~LJkQV<B+au&2H=-qAV1nep6dg}v6fx; za^g3E0X1^_g>IW5u?(`ptWM;4jL~Q|XU;`E4@9n|d*$L8MJOYtNzEQLtx-i824|P< z@5oNIJC8>P^W8V0*_D>s*2PvOeX^0YYZ9b;^Kl%Whb=b~zU<J{6v*m?>7zHnEkHtS zi}TIt1<ms#D9d(H^Xf9{l28^7=bnWTE<3(~1**Fp#Z`4#XRxPc%-Z=$;PhtoaP2w; zPS<x3c8)CKQ==3?s?W@~r84*kPl71JCOMEEb3>XDKJ$X3)o_AayF?M*)Wesv^<R)$ zUY4l`=`ol7d!hp=qS_M|+)v2IgPN2^#-=812p))#4F+x`sr?F67^Cl_*+C;QZ>=f{ zKVE;xibiO<<uFL9gOh?k#bZ4tNOG4ESjt150;r|PT$r)BrF4%Aq>0YM^8fM|VfD)M zg2o8aRG#<aRSs7{TZLiW6dJi*!QPZ*&-6ws8*w1;xR{e=TZlPkExUeP9->*gJcoAW zn~b*r1%N6@+}Fp5n)}wt7BF6)?YtD;sFM)K%m9)2(Akv4ejfx333cc@aP3Wi;L*&l z5j`enlqe}YA{XW7W+hAZ<JB(_ELz78PAf3QO~ckI7kAT2JcX#|*?hvvb4PiTZF}KP zm-9R?_13tC(u3di)O6#-jxwsUVAMhf?JiCw%TmS;dfj#q<o*P#D~SE$BR{q{z<Z&l zlB<8_+=*aqEgSM%O=X{yzhp53M>N>J79jAj{T79#b^~M~=ckT#zEN-Nvq((sU8n!K zdSLGGpIb*Raw1fIF+y)I=ZQ6{e9>r0&v<|~fsH-L+z>V4717sT{_$XPwJfJy<L<yP z>3JBf{m9ea)Vt49i3+9GI>E%&b&C2ka?@`d2$hzv><ziZcu_il45w@YBXAFM6C}#| z*EB;0nF)7dzHL-)`MINY3fsra>7S)wZ03j1Z<nj|XtlCGbt`@QDZ(><o9U)x-<Y?7 zH7p4ce=&CMI>WkHXxSH?%k~j8fJ3+OeER$el7%msKl$UNlk1y&k=6Q~BNi(`FSqh+ z0DQ;{{>L#lH;)#N^xNGFN`Wf*oEO~J;8C80KH{9Da!4~`z-x3XSXKp5)s(UTqjN31 zW_9nk6Qu9+BF!8TXk@^5?Zv67*v;c<*^vYMaFx8TOLKeszj379E1U689aL>O=MJ~; z@gi53_qtMnM)f_&uMKshL)y;bzQ}216zBG^Hpb)w#GASG+$%D9s@3|PQfpwl6WR`F zamO-wsV8LHFm=ufe%;#dUB`=Do|Pd#J@q1itB+H{OW*bm{)&rl&G34phuD~{-C(Kk zUY)`dzUMGs2H@<C&x_7&+(}8i5`hKY%Q5t`L4HIT-8b{lsC!s7bs;X(?Zve+TA(pR z+h$NsU<ltn^OI4NbC)?5Qxh4C<(Ov!xdS_8u6dm5!lPtbuDDCTJ{+Xg7wcd-coB^O z93l*41K1gQ#mrdEP26TnRc|Gs&GU<Yg0T9f=UU>wn(DwQ%DW%%A41vgXl8^-1}+s2 zarh7dO;v||k){?Bp@tqDHl?SN57`FwqHitm0d+CG9IxVQ#yHSV#em-*=UkXD;zUCV zQf30#YY*rAeJQ9MaOEovDZYaXF~$JiQ}4!@n#3_TfhtcEQf$n;HmvsVeV2W2Fx<@3 z29iV|^(P;RfIjLH=!~HD)2i7ieJrNCeC;p;4pCLXBJW~R_o;~?B0{|5D^YoR>@;p$ z$|R8j2X5$P{xVVgkK;obnjjMQLkQP++#}(hAQ|nfGGp)z?72+IX#rd_>{RbnB|i1D zHpEn}y7PvOUFw94Xm*$Jg?DzgOM_I#A4L%Xbte-FGnl>B6#fQ<8MR^3Pi^Dx3r!IX zIt1{ds+2-#iAVI+Sr*Yt_we44<;rrwTb|k?*loo*?d0*r<%6R`$*x5$!ePCfc5GK? z;`JzWC>QUo%zsvP`tb>OP2i2}EWMHr2ea-NFA@OXbzc`>mq`-tr$~qhl3J0hT9N3B zP#`*Dl8dGb3UA#WiVKKxtM?=k2f><(_4w_p_9@oSiQaVVo4kg5?`l)q){*}G=~qL; zz}2q8hy4{j<JF|Uk4bF~EEX5eTXF@Fo!jOu0+%w%1E+wZ5(L0fbM&Kf0no^Z)9Ud` zoUOGZZ55@c**Co6Ohb~s4F>w_wXBlVlh@`%F@d?9ZSTw1Wd0I3<RZ?*NthHGJB!qC zU6+NU$~G+v1U1T*yjKw<w9$&N6$B#@>nlM{@R+I>tIAFC7dy)pf%jzb{TKezB=t)^ z!alSx-0`RpIO$<%?N=v1Nn-ut5vRQq{4CVwC8=sQJC<|+Ncv4t;yGh*U$Gz%)E`U6 zhtIBTIm5nhI~n29s#Vrr>L5-1T9jf!`M}2enptrwO<!Eb2n)mdMQ%745dWeoZanQv zN9OPpo;(M0lksKqmngMWQ#&>Q$f84QG@SAh5ygd6W9NpCt=XW-I7O@Ci_&OT2U?|Q z0gI;Uf&mUmV9AgG51e!8kFu_O()G*fm;DV8Cv3y{QzCa1F!-Dh*Exhztm<E8Qsz2r zjGm|BIR|G>huZfKZHg&TRXWH@CDO%+Rwy^9S+S;4#3K!7kP~aIDX21ul~Rd)xOFt# zs>6Hn^kw}$Wjyy+TG_ofPW##TdXh;&r#`{#p0H{McHF)8aryodf0k0U;6+3c$$Ci& z>4vf*MK-2BMhNtur9_Wv&SyBW8YIw0i%vTF6i<GZRpc0yXfYiU^MA*z&mI8vZ=(fS zYc0$64Ujs8x$>wdYfq_x?A17gA>QGbY*O%UuX0{;iqJ6=VUYcVeescqOSsWj8c^+p zIIs8(7|4962`pe)lY~x%Z(^e@^Gp)gfdO_YtW4!DnE|w2ST|GyEyv9V4QJB*-wF2_ zFLs?L*D+71pT2|#ic6NtR8cxL04H6K1M|9iwV<*W?Y3}jL>ahPdfk)hku{q}^vbW# zEYIAy&|y$Y=R5u0>VH1bXrgol4#kG<{43ESkJWgb47G6<ctochu5x6+BQk{QOP*v; z+!=wvy-O)XOq*hZZb6+O)8+Y`m9-4o2Di4kwyr3LoHgp7bmpt_=XqD{SeE2qIgW+J zQ}o0}bFD|tTHV05o7EH5%s;DLvIR>A4KMBw=<;@UCU+7q4cQzeIG)E&-modY{eMV* zIc_Nsi2rM#)Xf5^(2rd)$%9YIs!jBYDGbfH8!e9ULp?4MM3|TzFY0!i;LO~d>P2!D z1~3!Pg5PW^_{j-Y(mlPcs1e-KAKYN|P(|mg<gqiJe4^p-ReApq`JKr;kAXP&H8E#& zPr?j3DWMhL&+xu%4=!tV1$01b`X6+O5AVyYU=H++L6cJ;UAHlpVU0ORrVo+66>^W^ zm<Qn_KY+8Wa8<YJITKhg7<m1=8yE(l7?x5-+&Zi!OcAfRs>uT?-D?N>KQXdzg(sjf zyy<YY2oYXz#EU+*)Z05s*!jR~v$C8H&L;esdf!2Y^y*5ELFfp|u_-lbL8M3R@7Q>2 z8=B3mj{N#4u0G1NA2v<0wix|6B&Gq7Mz(BUq0V{}^<@6uI(<I>cAp`pt~A>II?0^W zp+_4S{jsfPLX+Z#cAjI6d|h&2%-tz98O&C5v2zc!|K7WF)lvQRL=<<M*}!bq=bctr z=<cbpd}+f=3G7Z{Ij_vu2dRVNdjc)!?nbJ0b&<WKF=Wk}w`}CCwr0=5a@w&X4F6UV z``*X!KN`bpvO$3{;>@n;9I0LB90HQWG;4(it1t<&5s@r|btCN}#_7ZsP&s_Xqc1PB z@l5Put=VJe7;X0U1e6{*NMQT+_!<0O==ld_RJ+Slw)LQh-s|?X><l9N)@mIWlr4au z>FMAFooPjr*-TvW12x5<+x1D^+nI&P_5``}>diN_5eTJ%!tWzT<==TwMunjj)Ly$| ze^`E6?uB10UK|@v_GJ{DUTqyM{|>d}61O)77ut>RPk2k*Sm*NYAM@sGRU|BwGEN0) z^Jus8X=PV}PwBt>zxIv%4jGZiR~w0>4z&EkcYhLd-J~%~j(AqX>&mfK#Q{Fm(jz+F ze!6D2K^DZbZ^J)+RxBjE08e;bj2sH48LmeKIV)OA;$uSB`I@7%%(Q+oh8U+@z>c3a z7~mymlXfl*RR*lAQ4NbzysoN9ho~oWgN|eLrmfhAwUC1m@7QbZc!c!!sTf<+8AtG+ zEz>(G*|G|wGXO|`FI17mrt!*K$D<#~{Qj`lS$OSU_CC3N|4Qz%ysW!GRPvo9r0meR z8tS2@sN^jl7XE$<3*T+PjqRzfcaNk#A?X8>Q}u*gS66&s0EOz11m#U|=-%@R2ORw( zpT}(%3n@-Iv?5v4=B%xp+ySy3JS0@})&qe*l7MMY8CVmQ%ajk)s5clfc1@<@Me zhOd<rbh1nW<b1*!!gVan3FWJ2bh*{oZyi34#vf!n-jGHKxv&XgNJnTa!4~QIG9C1L z(4iNR=ujJIGK{b#H=PlAANj2!5*<O*3qRB-wY-Y>DfRm?Zq!}&<{o!7eDAK-;qS`? zawb+452({ygax(4t{i&%6wcyseGU9lTHKC@UU54fn{dC3$<XvTz@-RgW{#?An8p1h zJ}`SwB@cP<#zj6D3=XM9_^i~x4zGbmB$HsNo2$dvp3O7?rEpG`J(kL@{~TQ6jX#Uz z-2EUI7mr2Ic681w?o_>+qG_s5=|JtrPSK86axDEfx0*I@QWTa>qWuV~uCKdy);){b zES&k3EdjUC58hPXULw}m1N#BeiXtf52yR@aOm5|Es|z}7Ey!R?mw~^KLFPc6<Q2~+ zQ9MJrW%eZFxDctdP4-$fEYNs3F-O?ZV*6R15bV<rCcH4a$U+lDTw51FEwsw!O3nzD zN>#va<Us4hiw&W}saE~TEb*TV6iPU9$)Wzjm<O$X1&_27HQ<j*WfnGOY!<FmiBhCe zzK_Gw*h81%!AjGT5rJGBQNaQ4d`6`qSg#(zAyaT?Xo2t+UAvCI27HD**a}Y(kf2W* z-6SNvj}J90FR@4yX0TGosKS_K9zkU%h*RwinN&v{mL<fyP*Ve8rx=Z@AIF>nK~$Uy zZ#D~(VByP}$cHIKwF)+=Of8{AKd4Gmj55~{TMA8E(H~$u$cZXrmeBn4GZN5qzAxpX z`1EA!(+1eBpP+c9B+9^U(9Q)n;n~$&BP<;9mvKERQkSgVTqgBN{Q9-P79cmKrgfO* zYx(mP@%(*2$Qo=s_B{#$dRdC`L>#D2RF^MS%st^UoF}x@39seZOEQ#GS%o`afB{Y{ z(iRPjYl5_xGEt1pCu?oB_Vp)!uvJp1f5aYEzacvab<yK=jojT{V=J%_?^bWsXi`Q~ zv$y$LlXH(g$+0peAChj8`HM|7iR7;bZbv&;s0HX~F6*aXFt(n9C?W$(sIJE(6Nzvy zH{0s$mHk{^4}?IBS(OX~7XWa5Hv6DSNEMHkcEvd&oAH%~VH;nYP-nn=*%|zwFt0V9 zeQ3CLUSy;jVJlVKg3SVCBmQSxyPDZ$PJBu+&eDZ^-@!LFzqc|*ef%v`VFvD8xX{IZ zfsz5(Zietd)!&jksk*E!vh*rd0$1qNByUm)jUV?s{}#*aDI{<`cj;w`vc3=Iu(Bt3 zDRz_G6gc^RsTL!QW~4^I2@Exm`Qv|(@$Ub?deu(qybJex7A+iOHi1JmK_#a+lTv^# zSo$^CUoXXYFG>R4kLA<DU;K_NtAr9Z9UHxq4uZ-lQ;g&zyn@1)lYEWF=M&R3%PCNC z@vI`ly?O^}>$xBW@M%Z)Fb*|BIiy?kbyciRPDQjyH^>$^I4id=H&qNYDKEqEQ;B|` zYBASIkUJh{-M4F#7neo|mr4zlh(9s42%*Bii>C|D>}bm-Tg-H-CWU7mTx=-`+rmhp zG+-S^!9EY2Z}yl|F_sy@3|&zeXX=DGlq@P;ZYE!~lUd54><nEXC?~=mW+J-EEW(8y zkyxNhti(2z<q0_Cxm20+Mz6OpngVVZr3UCrrqYI5dg61#)2{#<qF=4Z6e)kq5E5Ef zRtWaI0dZZ>h=_1+!HS*{=m)4UEbIBx?dct(?>ek+gj{@ds5dnMD?J8pM<Bw#@qwPN zvRT1|XPDF=p~4ugV-L@);cp@ZAkF~L>RRa%kPTX7k9pjRrCVtBhJs<q8UP*hB7t$k zDj&DK>f4uM&^aQpj@Eu83IE0jVMIh{2bgnCRTT?Pe+L97$u5mH9uf8UZvq?xS}*u^ zkkxBz2dJML8N1!ovdZjQT6F?d=IYly;(zpS5QEbtrs$5_F|b^Dc6+v;uDnvvr#sS% zPYR=Jm!-ibOJt;>&1g1CW32)b-I0Q^Q$g3Nk}6BI(49OE89PreD%~I*Irwn#E{;|9 zRm2snLl&YmEYH&MInf)gn24X%3L%4y?E_92=FN*?U+{$do-NV)&Yqkn>NQyj%%saF zkFK?`@uQDxmij)FvNB>Hd+axEz+bT;q<1!Zft}{=y&ddsY|3#2Ak5_kree4irAG(* zq4d$Eb*l`c?|?T5mAI6$g)_>$@<la1nK-80@zZG4rK$$(ewG7CV;LU5ws-4(5Mg~s zVk@Vc8N6K~oUSkWS>4bBx~3%$dKPLB`igw!99SzdKBeIw^}C@%k6xnrv~q<d3!Ea5 zVJfUPsXGI{OQP5u|L~B$jUIHf2N0~De?zC1F$jmnO$p+Xm;{ITP6%xrv2NQDMkmcm ze25ZTf2Y|n7=%_rcYmN8s)+-Mz_ohudrEI*qzYBPMxt(HZ+E|*^E-V%Q*CAiJb`a7 zMBdsuJ1cEha!E!D`fl7Cmov@zruO<)W;p{5wI3G-UXg#?+h&I6Xwm7P(quzvbn-ZF zbNwB&*4R8w>t$#!W^c=sF=k{Y7K5&5M{Nc4G^p`ie=7i6ravV8|JlRWi6@zKegM<x zFJnwqy;=la$V@cC^HU4%YOzub(1H$|d(CW9Z5nK^*L)iO9du3;=mdhK_YFf1am9&* z0Ji5=?zcxC)PEy44KOTE*sF1TZ@){Ml66ZSgiW3Dc;;33?UE$tMfQ_VGLyDyPh5`I zy*F|F4!iR5VQ<b!YHZJx*DO%c;Pv=!%kN=<G|q?fAIL$a=>~_SQo0i0#tRLzeB^DJ z8JJJxb0Z{5<->o6mB{C?KIJXIUDX!(p>gd=Z{v%!e)O_np$baIWeuYy*l!af%kTnP zCXV9UNB?0wrYL3B9C0Mpi<QYoARwZVRNN1Ea!iRyJqRF6$RkB@)YHF1vXOIy0$yV~ zmbFRt_3|M<m1xdxQFKZ|hS&1{J1VHXO}t62uu%yoRj#qPN#--P*BY^^M&pfkH$*Zf zc+>Ls(W)vEY3XDH>N6b)@z!|Xz<6KRtGF$8#CPjAChICVCzgemUM-R{R{XglL0Qna z9IQZrmWubaYD1dGjskWvkBuu9Hk15*2B)>7;#ztAD%5oZcPH%7OxJ9%4CrU4W4aZ> z(m4XAqUD4D!`)shm!$`|`DLa!#*sA)<A+aiA0b&<ae?%NUu%2CB3Rr}!g8g;1AwO9 z4FQo*A_anW_g{~qNrA>fjOQC}c6o{$)W=i4KZEQU<S>hrxTK=&9Wpa2XjktOuxC7K zB8u6NAXuilV8X!yJ7)(pL%La$POC%tWM)>6G*)SuOo@!S`4i#Bq##cdfyiToP1&|# z9@%EFbq}ZG-CmmRn!Kn5(d7&^A74=G!dn@bepP9j@O<UoT3F4UovAJ)LFt`^M=;WG zqS{N}THYusSzts;uElz!1N+dw;BxHs6*943gIRdD5V3y#qWFDr;P1Sd_SQ<3#%W5@ zjClfyQ``IDYGXn!1X_gJ;o`PJRa_sdV`$aF8R)l5l&!*57f9BZk}#d*SD^Mg_k~rW zd>b@ydCp#!h^I0>mctPBFW<d`g(LnQFQ=tDbUtYQojpewYn3~6mR-XE@LAVJS{Q56 z$<9X8vsS6FM<%qP7c%vZ>@leEAq@5rGnE=<RMaTolU|XS!Uaklt#%BgrIB3S{-cZ7 zwQjw_>eL)p-eE6sPPx3F`s|vK`9zeMl+iOo@G@6$Fi-V4-1cS>`aNg|JVwxT@$lp- zh@m%&J>g)yxf!@;X?t8iLkb}#FfC;&kDm&H-+?^DiA!Hi{*-rT5JU<emxat%SCY_O zZ>(k;nLJ?G@dnE$t1Jv2K6^|2x@b*Y5*<VFJ@sR#8%bK0>$07qKjC({tdiS9bKvh4 zrziSSH)}q1^1Xa%eU?L%zGh%UiYtd&Mw-49U~nKPycm7H_+)0Xb9;4YXJOtb^%DYc zj;iq`Q3*Q0sFZY_iuq39JU(??yVdlTm|Q2W*w=|achFik8IOA^d$+&noTkViT`;VS zKN2$WT9f$a>a?^45MNW&_(yTb6}hRb<^#PfwegD2ErB%CEMj>1$4U(;RS&7TD#~b8 zr*C@Ixc;Ojk6RHWBBhpp?JOM3bNZ^-Pvohbr`K)>#C2`v<Lwa!r2A0+4UCxn=As`@ zQLZOrFfw*zmULI(@e_Kkwi6mVOgbvPVQ;3VU?J>0!#i3_yqZ59`lWgUjvk$%C;PnF zBuv*ncT+BZRq8Onh$RYsN%6Tat9L)Hy&;FYz`cXqa)Q&03-{pPpto5HgP*DE6OV%b z)3ug=@C^DwK>@ne=8`9<&}L&WR9jbsp_C98vY%ACi<S>NDo^kzn<1E*!E+So4kXB# zS8`4Lp?Q3t(2oa~bE^A^mi{`uysfMDF~nxvd%TcCDy;K4xBWOP$3hrG3k{aVck>Ps zPN#-xF+VvjZ41qMA1*Ju|6A`=G(FV^*)@*WXHV^>OcC(FMUye)L1pm5Y`w)iP+Vij zl95MUbi1iHBijput2j^$)aZFKv7I#wmr~`61F6hPQ9dr{_WJ7lSQ&RK-~H4_RwWxu z{ubTE#flKj3bdcYIHIT`<pZ(wQjOuXg_TX{Au;oERGnkMq+0;s<Efsa2H`=hgkcFs zC$LI&CvP>1tF`9S>t}}4RndZc8Ho%Rr*bpT+?76lskVSCW*gD{K0K<a`PFbRjGK#` zu>Vm?lM=;A)FodVK`JJsOV$i`buI%EK^ue|Xxu~-#h%EZ3T18X1e^%W4fsfb-T@0x z$;RPHk7C64<{xD_<#EGd6974uG97hAj9wC#^*`{sY#fL1mB<R3Fs<8X;6({&vx+#X zaa!iG;k#@v07vRKPPFpgzkB?%zS*(UjI=lV>APo7gE~TA`n4`<cXd5<+y4=uj^H4B z|L>Q`OWQgG?Br9i>ddTN@IyOvQ`c?Zv7x}hPGYCs2oWTBJrlY677M&9mMc!2%^&x@ z6(RhPxBXfq6UuO0%LERLj`%Ecz2uwknom(VQBx`@UZ1PxEkO>q+sPj?;ZnZ2<2w*e zQ;QzoXw25j{a~x<^Od~)yG7{FkIqAd+>pUZIV5j;Dh0RE*JmQWU*U&`rIoT_8AECr zq6ZTKpcp?`69#va=wkkI?;CJ>*w2J%cZVXyit|WPe-RMO;CsSgZj{O;r?CSAr@K(@ zEer9(1Gbe;=n*QbJ*7JYn3>BuIR(YI(yNY?uR&(UO-rNqSBGXG4Xt1`9ldIjq{b0! z9g#Uq*qgpz`eG6y=n<R5>4dpX_|3z6O?56XJ>lmZ+b=J1iP_AAd2M6%^ENr|O_E1G z?2;{inUF!@vE86-$o}5uc)Y-528{$3S)Kb6aheSo5hXZKQ4;R{9sW_|W8+l#lT9|O z0OS;0|Jbq*kBcpdg4_I^l_rtjrBq|Z;K6@LHZEwwclaEX?NZdyg)-Nys88ib`i|R4 z<%bn%lC|`7SeByt(-n3<(eNF)B;Qu&E>SZg+7femKOH2kTG2BW5rJ(W9}&C(@_4<h z_M1(%gj5|TR_osYf)BGmu!iMKlE(h>1(Grm{B})De6KZc4p1|J76C1U-!G?GXN^|F z$(uBEeh)yj9OH`)-{|tB3#p*LW({1dnF2ZZC%#su;IERUREdk8&z8HTlXRy@5=<%h z)`hjUQ3181><-12w=b@u3k}glAND+mr6S3rJV9zQB7-!R8Uv#C;)2e$PN{TGom(tl zXad3InqbD2KIg&*40C~%&dUr6+#ThU`5qEKfa$!iF~zNkD5b-&|9n0>8∈=2{R& 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 Jw_N}L004YQQtbc$ literal 0 HcmV?d00001 diff --git a/src/mol-app/skin/icons.scss b/src/mol-app/skin/icons.scss new file mode 100644 index 000000000..3d1d6cfcb --- /dev/null +++ b/src/mol-app/skin/icons.scss @@ -0,0 +1,135 @@ + +[class^="molstar-icon-"]:before, [class*=" molstar-icon-"]:before { + font-family: "fontello"; + font-style: normal; + font-weight: normal; + speak: none; + + display: inline-block; + text-decoration: inherit; + width: 1em; + margin-right: .2em; + text-align: center; + /* opacity: .8; */ + + /* For safety - reset parent styles, that can break glyph codes*/ + font-variant: normal; + text-transform: none; + + /* fix buttons height, for twitter bootstrap */ + line-height: 1em; + + /* Animation center compensation - margins should be symmetric */ + /* remove if not needed */ + margin-left: .2em; + + /* you can be more comfortable with increased icons size */ + /* font-size: 120%; */ + + /* Font smoothing. That was taken from TWBS */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + /* Uncomment for 3D effect */ + /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ + } + + .molstar-icon-expand-layout:before { + content: "\e84a"; + } + + .molstar-icon-plus:before { + content: "\e816"; + } + + .molstar-icon-minus:before { + content: "\e819"; + } + + .molstar-icon-reset-scene:before { + content: "\e891"; + } + + .molstar-icon-ok:before { + content: "\e812"; + } + + .molstar-icon-cross:before { + content: "\e868"; + } + + .molstar-icon-off:before { + content: "\e813"; + } + + .molstar-icon-expand:before { + content: "\e885"; + } + + .molstar-icon-collapse:before { + content: "\e883"; + } + + .molstar-icon-visual-visibility:before { + content: "\e826"; + } + + .molstar-icon-abort:before { + content: "\e814"; + } + + .molstar-icon-focus-on-visual:before { + content: "\e8a3"; + } + + .molstar-icon-settings:before { + content: "\e855"; + } + + .molstar-icon-tools:before { + content: "\e856"; + } + + .molstar-icon-log:before { + content: "\e8a5"; + } + + .molstar-icon-remove:before { + content: "\e847"; + } + + .molstar-icon-help:before { + content: '\e81c' + } + + .molstar-icon-info:before { + content: '\e81e' + } + + .molstar-icon-left-open-big:before { + content: '\e87c' + } + + .molstar-icon-right-open-big:before { + content: '\e87d' + } + + .molstar-icon-left-open:before { + content: '\e874' + } + + .molstar-icon-right-open:before { + content: '\e875' + } + + .molstar-icon-screenshot:before { + content: "\e80f"; + } + + .molstar-icon-help:before { + content: "\e81c"; + } + + .molstar-icon-help-circle:before { + content: "\e81d"; + } \ No newline at end of file diff --git a/src/mol-app/skin/layout.scss b/src/mol-app/skin/layout.scss new file mode 100644 index 000000000..ded62b221 --- /dev/null +++ b/src/mol-app/skin/layout.scss @@ -0,0 +1,29 @@ + +@import 'layout/common'; + +.molstar-layout-standard-outside { + position: absolute; + @import 'layout/outside'; +} + +.molstar-layout-standard-landscape { + position: absolute; + @import 'layout/landscape'; +} + +.molstar-layout-standard-portrait { + position: absolute; + @import 'layout/portrait'; +} + +.molstar-layout-expanded { + position: fixed; + + @media (orientation:landscape) { + @import 'layout/landscape'; + }; + + @media (orientation:portrait) { + @import 'layout/portrait'; + }; +} \ No newline at end of file diff --git a/src/mol-app/skin/layout/common.scss b/src/mol-app/skin/layout/common.scss new file mode 100644 index 000000000..b84b69214 --- /dev/null +++ b/src/mol-app/skin/layout/common.scss @@ -0,0 +1,60 @@ + +.molstar-layout-expanded, .molstar-layout-standard { + left: 0; + right: 0; + top: 0; + bottom: 0; +} + +.molstar-layout-region { + overflow: hidden; + background: $default-background; +} + +.molstar-layout-static, .molstar-layout-scrollable { + position: absolute; +} + +.molstar-layout-scrollable { + overflow-y: auto; +} + +.molstar-layout-static { + overflow: hidden; +} + +.molstar-layout-main, .molstar-layout-bottom { + .molstar-layout-static { + left: 0; + right: 0; + top: 0; + bottom: 0; + } +} + +.molstar-layout-right { + + .molstar-layout-static { + left: 0; + right: 0; + top: 0; + height: $row-height + $control-spacing; + } + + .molstar-layout-scrollable { + left: 0; + right: 0; + top: $row-height + $control-spacing + 1; + bottom: 0; + } + +} + +.molstar-layout-left { + .molstar-layout-static { + left: 0; + right: 0; + bottom: 0; + top: 0; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/layout/landscape.scss b/src/mol-app/skin/layout/landscape.scss new file mode 100644 index 000000000..418a7cd4b --- /dev/null +++ b/src/mol-app/skin/layout/landscape.scss @@ -0,0 +1,81 @@ + +.molstar-layout-main { + position: absolute; + left: $expanded-left-width; + right: $expanded-right-width; + bottom: $expanded-bottom-height; + top: $expanded-top-height; +} + +.molstar-layout-top { + position: absolute; + left: $expanded-left-width; + right: $expanded-right-width; + height: $expanded-top-height; + top: 0; + border-bottom: 1px solid $border-color; +} + +.molstar-layout-bottom { + position: absolute; + left: $expanded-left-width; + right: $expanded-right-width; + height: $expanded-bottom-height; + bottom: 0; + border-top: 1px solid $border-color; +} + +.molstar-layout-right { + position: absolute; + width: $expanded-right-width; + right: 0; + bottom: 0; + top: 0; + border-left: 1px solid $border-color; +} + +.molstar-layout-left { + position: absolute; + width: $expanded-left-width; + left: 0; + bottom: 0; + top: 0; + border-right: 1px solid $border-color; +} + +.molstar-layout-hide-right { + .molstar-layout-right { + display: none; + } + .molstar-layout-main, .molstar-layout-top, .molstar-layout-bottom { + right: 0; + } +} + + +.molstar-layout-hide-left { + .molstar-layout-left { + display: none; + } + .molstar-layout-main, .molstar-layout-top, .molstar-layout-bottom { + left: 0; + } +} + +.molstar-layout-hide-bottom { + .molstar-layout-bottom { + display: none; + } + .molstar-layout-main { + bottom: 0; + } +} + +.molstar-layout-hide-top { + .molstar-layout-top { + display: none; + } + .molstar-layout-main { + top: 0; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/layout/outside.scss b/src/mol-app/skin/layout/outside.scss new file mode 100644 index 000000000..74cac3e42 --- /dev/null +++ b/src/mol-app/skin/layout/outside.scss @@ -0,0 +1,89 @@ + +.molstar-layout-main { + position: absolute; + left: 0; + right: 0; + bottom: 0; + top: 0; +} + +.molstar-layout-top { + position: absolute; + right: 0; + height: $standard-top-height; + top: -$standard-top-height; + width: 50%; + border-left: 1px solid $border-color; + border-bottom: 1px solid $border-color; +} + +.molstar-layout-bottom { + position: absolute; + left: 0; + right: 0; + height: $standard-top-height; + top: -$standard-top-height; + width: 50%; + border-bottom: 1px solid $border-color; +} + +.molstar-layout-right { + position: absolute; + width: 50%; + right: 0; + bottom: -$standard-bottom-height; + height: $standard-bottom-height; + border-left: 1px solid $border-color; + border-top: 1px solid $border-color; +} + +.molstar-layout-left { + position: absolute; + width: 50%; + left: 0; + bottom: 0; + bottom: -$standard-bottom-height; + height: $standard-bottom-height; + border-top: 1px solid $border-color; +} + +///////////////////////////////////////// +.molstar-layout-hide-right { + .molstar-layout-right { + display: none; + } + .molstar-layout-left { + width: 100%; + } +} + +.molstar-layout-hide-left { + .molstar-layout-left { + display: none; + } + .molstar-layout-right { + width: 100%; + border-left: none; + } +} + +/////////////////////////////////// +.molstar-layout-hide-top { + .molstar-layout-top { + display: none; + } + .molstar-layout-bottom { + width: 100%; + border-left: none; + } +} + +.molstar-layout-hide-bottom { + .molstar-layout-bottom { + display: none; + } + .molstar-layout-top { + width: 100%; + border-left: none; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/layout/portrait.scss b/src/mol-app/skin/layout/portrait.scss new file mode 100644 index 000000000..efaf39113 --- /dev/null +++ b/src/mol-app/skin/layout/portrait.scss @@ -0,0 +1,99 @@ + +.molstar-layout-main { + position: absolute; + left: 0; + right: 0; + bottom: $expanded-portrait-bottom-height; + top: $expanded-portrait-top-height; +} + +.molstar-layout-top { + position: absolute; + right: 0; + height: $expanded-portrait-top-height; + top: 0; + width: 50%; + border-left: 1px solid $border-color; + border-bottom: 1px solid $border-color; +} + +.molstar-layout-bottom { + position: absolute; + left: 0; + right: 0; + height: $expanded-portrait-top-height; + width: 50%; + border-bottom: 1px solid $border-color; +} + +.molstar-layout-right { + position: absolute; + width: 50%; + right: 0; + bottom: 0; + height: $expanded-portrait-bottom-height; + border-left: 1px solid $border-color; + border-top: 1px solid $border-color; +} + +.molstar-layout-left { + position: absolute; + width: 50%; + left: 0; + bottom: 0; + height: $expanded-portrait-bottom-height; + border-top: 1px solid $border-color; +} + +///////////////////////////////////////// +.molstar-layout-hide-right { + .molstar-layout-right { + display: none; + } + .molstar-layout-left { + width: 100%; + } +} + +.molstar-layout-hide-left { + .molstar-layout-left { + display: none; + } + .molstar-layout-right { + width: 100%; + border-left: none; + } +} + +.molstar-layout-hide-right.molstar-layout-hide-left { + .molstar-layout-main { + bottom: 0; + } +} + +/////////////////////////////////// +.molstar-layout-hide-top { + .molstar-layout-top { + display: none; + } + .molstar-layout-bottom { + width: 100%; + border-left: none; + } +} + +.molstar-layout-hide-bottom { + .molstar-layout-bottom { + display: none; + } + .molstar-layout-top { + width: 100%; + border-left: none; + } +} + +.molstar-layout-hide-top.molstar-layout-hide-bottom { + .molstar-layout-main { + top: 0; + } +} \ No newline at end of file diff --git a/src/mol-app/skin/logo.scss b/src/mol-app/skin/logo.scss new file mode 100644 index 000000000..7bd02b7d3 --- /dev/null +++ b/src/mol-app/skin/logo.scss @@ -0,0 +1,47 @@ +.molstar-logo { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + + display: table; + width: 100%; + height: 100%; + + > div { + display: table-cell; + vertical-align: middle; + text-align: center; + + > div { + display: inline-block; + position: relative; + width: 50%; + max-width: 390px; + height: 130px; + + > div { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + } + + > div:first-child { + //border-radius: 8px; + background: rgba(0,0,0,0.75) + } + } + } +} + +.molstar-logo-image { +@include non-selectable; + +background-repeat: no-repeat; +background-position: center; +background-size: 90%; +background-image: url('data:image/png;base64,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'); +} \ No newline at end of file diff --git a/src/mol-app/skin/molstar-blue.scss b/src/mol-app/skin/molstar-blue.scss new file mode 100644 index 000000000..0d870a8c6 --- /dev/null +++ b/src/mol-app/skin/molstar-blue.scss @@ -0,0 +1,2 @@ +@import 'colors/blue'; +@import 'base'; \ No newline at end of file diff --git a/src/mol-app/skin/molstar-dark.scss b/src/mol-app/skin/molstar-dark.scss new file mode 100644 index 000000000..cdf80ffd9 --- /dev/null +++ b/src/mol-app/skin/molstar-dark.scss @@ -0,0 +1,2 @@ +@import 'colors/dark'; +@import 'base'; \ No newline at end of file diff --git a/src/mol-app/skin/molstar-light.scss b/src/mol-app/skin/molstar-light.scss new file mode 100644 index 000000000..d45b6bc81 --- /dev/null +++ b/src/mol-app/skin/molstar-light.scss @@ -0,0 +1,2 @@ +@import 'colors/light'; +@import 'base'; \ No newline at end of file diff --git a/src/mol-app/skin/ui.scss b/src/mol-app/skin/ui.scss new file mode 100644 index 000000000..41e78fc74 --- /dev/null +++ b/src/mol-app/skin/ui.scss @@ -0,0 +1,38 @@ +@mixin non-selectable { + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ + /* Rules below not implemented in browsers yet */ + -o-user-select: none; + user-select: none; + + cursor: default; +} + +::-webkit-scrollbar { + width: 10px; + height:10px; +} + +::-webkit-scrollbar-track { + //-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.8); + border-radius: 0; + background-color: color-lower-contrast($control-background, 4%); +} + +::-webkit-scrollbar-thumb { + border-radius: 0; + //-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.9); + background-color: color-lower-contrast($control-background, 8%); +} + +@import 'components/controls-base'; +@import 'components/controls'; +@import 'components/entity'; +@import 'components/help'; +@import 'components/jobs'; +@import 'components/log'; +@import 'components/misc'; +@import 'components/panel'; +@import 'components/slider'; +@import 'components/viewport'; \ No newline at end of file diff --git a/src/mol-app/skin/variables.scss b/src/mol-app/skin/variables.scss new file mode 100644 index 000000000..ddbf57aab --- /dev/null +++ b/src/mol-app/skin/variables.scss @@ -0,0 +1,78 @@ + +// measures + +$control-label-width: 110px; +$row-height: 32px; +$control-spacing: 10px; +$entity-subtree-offset: 8px; +$info-vertical-padding: 6px; +$slider-border-radius-base: 6px; + +// layout +$expanded-top-height: 100px; +$expanded-bottom-height: 3 * $row-height + 2; +$expanded-right-width: 290px; +$expanded-left-width: 290px; + +$expanded-portrait-bottom-height: 10 * ($row-height + 1) + 3 * $control-spacing + 1; +$expanded-portrait-top-height: 2 * $row-height + 1; + +$standard-bottom-height: 8 * ($row-height + 1) + 3 * $control-spacing + 1; +$standard-top-height: 2 * $row-height + 1; + +////////////////////////////////////////////////// +// ENTITY COLORS + + +// entity colors are "somewhat orthogonal" on the RGB cube +// TypeClass = 'Root' | 'Group' | 'Data' | 'Object' | 'Visual' | 'Selection' | 'Action' | 'Behaviour' + +// DO NOT CHANGE THESE!! +$entity-color-Root: $default-background; +$entity-color-Data: color-lower-contrast(#95a5a6, 15%); +$entity-color-Selection: color-lower-contrast(#e74c3c, 15%); +$entity-color-Action: color-lower-contrast(#34495e, 10%); +$entity-color-Object: color-lower-contrast(#2ecc71, 10%); +$entity-color-Behaviour: color-lower-contrast(#9b59b6, 10%); +$entity-color-Visual: color-lower-contrast(#3498db, 5%); +$entity-color-Group: color-lower-contrast(#e67e22, 5%); + +////////////////////////////////////////////////// +// COLORS and COMPUTED COLORS + +$slider-disabledColor: #ccc; + +$control-background: color-increase-contrast($default-background, 6.5%); +$border-color: color-increase-contrast($default-background, 15%); +$molstar-form-control-background: color-lower-contrast($default-background, 2.5%); + +// buttons +$molstar-btn-link-font-color: $font-color; +$molstar-btn-link-toggle-on-font-color: $font-color; +$molstar-btn-link-toggle-off-font-color: color-lower-contrast($font-color, 33%); + +// used for "actions" -- i.e. + in selection +$molstar-btn-remove-font-color: $font-color; + +$molstar-btn-action-background: $molstar-form-control-background; + +// update selection etc +//!! $molstar-btn-commit-on-font-color: $entity-current-font-color; +$molstar-btn-commit-on-hover-font-color: color-lower-contrast($molstar-btn-commit-on-font-color, 20%); //!!Change +$molstar-btn-commit-on-background: color-lower-contrast($default-background, 2%); +$molstar-btn-commit-off-background: color-lower-contrast($default-background, 4%); //$control-background; +$molstar-btn-commit-off-font-color: $font-color; + +// log +$log-font-color: color-lower-contrast($font-color, 5%); +$log-timestamp-font-color: color-lower-contrast($font-color, 20%); + +// highlight +$highlight-info-font-color: $hover-font-color; +$highlight-info-additional-font-color: color-lower-contrast($hover-font-color, 20%); + +// entity state +$entity-color-fully-visible: $font-color; +$entity-color-not-visible: color-lower-contrast($font-color, 66%); +$entity-color-partialy-visible: color-lower-contrast($font-color, 33%); +$entity-tag-color: color-lower-contrast($font-color, 20%); \ No newline at end of file diff --git a/src/mol-app/ui/controls/common.tsx b/src/mol-app/ui/controls/common.tsx new file mode 100644 index 000000000..695273e18 --- /dev/null +++ b/src/mol-app/ui/controls/common.tsx @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { shallowEqual } from 'mol-util' + +export type ButtonSize = 'xs' | 'sm' | 'normal' | 'lg' + +export type ButtonStyle = 'link' | 'remove' | 'default' + +export abstract class Pure<Props> extends React.Component<Props, {}> { + shouldComponentUpdate(nextProps: any, nextState: any) { + return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); + } +} + +export class Button extends Pure<{ + onClick: (e: React.MouseEvent<HTMLButtonElement> | React.TouchEvent<HTMLButtonElement>) => void, + size?: ButtonSize, + style?: ButtonStyle, + active?: boolean, + activeStyle?: ButtonStyle, + icon?: string, + activeIcon?: string, + disabled?: boolean, + disabledStyle?: ButtonStyle, + asBlock?: boolean, + title?: string, + customClass?: string, + customStyle?: any +}> { + render() { + + let props = this.props; + + let className = 'molstar-btn'; + if (props.size && props.size !== 'normal') className += ' molstar-btn-' + props.size; + if (props.asBlock) className += ' molstar-btn-block'; + + if (props.disabled) className += ' molstar-btn-' + (props.disabledStyle || props.style || 'default'); + else if (props.active) className += ' molstar-btn-' + (props.activeStyle || props.style || 'default'); + else className += ' molstar-btn-' + (props.style || 'default'); + + if (props.customClass) className += ' ' + props.customClass; + + let icon: any = void 0; + + if (props.icon) { + if (props.active && props.activeIcon) icon = <span className={ `molstar-icon molstar-icon-${props.activeIcon}` }></span> + else icon = <span className={ `molstar-icon molstar-icon-${props.icon}` }></span> + } + //onTouchEnd={(e) => { (e.target as HTMLElement).blur() } } + + return <button + title={props.title} + className={className} + style={props.customStyle} + disabled={props.disabled} + onClick={(e) => { props.onClick.call(null, e); (e.target as HTMLElement).blur() } } + > + {icon}{props.children} + </button> + } +} + +export const TextBox = (props: { + onChange: (v: string) => void, + value?: string, + defaultValue?: string, + onKeyPress?: (e: React.KeyboardEvent<HTMLInputElement>) => void, + onBlur?: (e: React.FormEvent<HTMLInputElement>) => void, + placeholder?: string +}) => <input type='text' className='molstar-form-control' placeholder={props.placeholder} value={props.value} defaultValue={props.defaultValue} + onBlur={e => { if (props.onBlur) props.onBlur.call(null, e) } } + onChange={e => props.onChange.call(null, (e.target as HTMLInputElement).value)} onKeyPress={props.onKeyPress} />; + +export function isEnter(e: React.KeyboardEvent<HTMLInputElement>) { + if ((e.keyCode === 13 || e.charCode === 13)) { + return true; + } + return false; +} + +export function TextBoxGroup(props: { + value: string, + onChange: (v: string) => void, + placeholder?:string, + label: string, + onEnter?: (e: React.KeyboardEvent<HTMLInputElement>) => void + title?: string +}) { + return <div className='molstar-control-row molstar-options-group' title={props.title}> + <span>{props.label}</span> + <div> + <TextBox placeholder={props.placeholder} onChange={props.onChange} value={props.value} onKeyPress={(e) => { + if (isEnter(e) && props.onEnter) props.onEnter.call(null, e) + } } /> + </div> + </div>; +} + +export const CommitButton = (props: { + action: () => void, + isOn: boolean, + on: string, + off?: string, + title?: string +}) => <div style={{ marginTop: '1px' }}><button onClick={e => { props.action(); (e.target as HTMLElement).blur(); }} + className={'molstar-btn molstar-btn-block molstar-btn-commit molstar-btn-commit-' + (props.isOn ? 'on' : 'off')} + disabled={!props.isOn} title={props.title}> + <span className={ `molstar-icon molstar-icon-${props.isOn ? 'ok' : 'cross'}` }></span> + {props.isOn ? <b>{props.on}</b> : (props.off ? props.off : props.on) } + </button></div> ; + +export const Toggle = (props: { + onChange: (v: boolean) => void, + value: boolean, + label: string, + title?: string +}) => <div className='molstar-control-row molstar-toggle-button' title={props.title}> + <span>{props.label}</span> + <div> + <button onClick={e => { props.onChange.call(null, !props.value); (e.target as HTMLElement).blur(); }}> + <span className={ `molstar-icon molstar-icon-${props.value ? 'ok' : 'off'}` }></span> {props.value ? 'On' : 'Off'} + </button> + </div> + </div> + +export const ControlGroupExpander = (props: { onChange: (e: boolean) => void, isExpanded: boolean }) => + <Button style='link' title={`${props.isExpanded ? 'Less' : 'More'} options`} onClick={() => props.onChange.call(null, !props.isExpanded) } + icon={props.isExpanded ? 'minus' : 'plus'} customClass='molstar-conrol-group-expander' /> + + +export const RowText = (props: { + value: any, + label: string, + title?: string +}) => <div className='molstar-control-row molstar-row-text' title={props.title}> + <span>{props.label}</span> + <div> + {props.value} + </div> + </div> + +export const HelpBox = (props: { + title: string, + content: JSX.Element | string +}) => <div className='molstar-help-row'> + <span>{props.title}</span> + <div>{props.content}</div> + </div> + +export function FileInput (props: { + accept: string + onChange: (v: FileList | null) => void, +}) { + return <input + accept={props.accept || '*.*'} + type='file' + className='molstar-form-control' + onChange={e => props.onChange.call(null, e.target.files)} + /> +} \ No newline at end of file diff --git a/src/mol-app/ui/controls/slider.tsx b/src/mol-app/ui/controls/slider.tsx new file mode 100644 index 000000000..ce78033fb --- /dev/null +++ b/src/mol-app/ui/controls/slider.tsx @@ -0,0 +1,814 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { TextBox, isEnter } from './common' + +export class Slider extends React.Component<{ + label: any, + min: number, + max: number, + value: number, + step?: number, + title?: string, + onChange: (v: number) => void +}, { value: string }> { + + state = { value: '0' } + + private firedValue = NaN; + + componentWillMount() { + this.setState({ value: '' + this.props.value }); + } + + componentWillReceiveProps(nextProps: any) { + this.setState({ value: '' + nextProps.value }); + } + + private updateValue(s: string) { + let v = +s; + if (v < this.props.min) { v = this.props.min; s = '' + v; } + else if (v > this.props.max) { v = this.props.max; s = '' + v; } + this.setState({ value: s }) + } + + private fire() { + let v = +this.state.value; + if (isNaN(v)) { v = this.props.value; } + if (v !== this.props.value) { + if (this.firedValue !== v) { + this.firedValue = v; + this.props.onChange.call(null, v); + } + } + } + + render() { + let step = this.props.step; + if (step === void 0) step = 1; + return <div className='molstar-control-row molstar-slider' title={this.props.title}> + <span>{this.props.label}</span> + <div> + <div> + <div> + <SliderBase min={this.props.min} max={this.props.max} step={step} value={+this.state.value} + onChange={v => this.setState({ value: '' + v })} + onAfterChange={v => this.fire()} /> + </div> + </div> + <div> + <TextBox value={this.state.value} onChange={v => this.updateValue(v)} onBlur={() => this.fire()} onKeyPress={e => { + if (isEnter(e)) this.fire(); + } } /> + </div> + </div> + </div>; + } +} + +/** + * The following code was adapted from react-components/slider library. + * + * The MIT License (MIT) + * Copyright (c) 2015-present Alipay.com, https://www.alipay.com/ + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +function classNames(_classes: { [name: string]: boolean | number }) { + let classes = []; + let hasOwn = {}.hasOwnProperty; + + for (let i = 0; i < arguments.length; i++) { + let arg = arguments[i]; + if (!arg) continue; + + let argType = typeof arg; + + if (argType === 'string' || argType === 'number') { + classes.push(arg); + } else if (Array.isArray(arg)) { + classes.push(classNames.apply(null, arg)); + } else if (argType === 'object') { + for (let key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(' '); +} + +function noop() { +} + +function isNotTouchEvent(e: TouchEvent) { + return e.touches.length > 1 || (e.type.toLowerCase() === 'touchend' && e.touches.length > 0); +} + +function getTouchPosition(vertical: boolean, e: TouchEvent) { + return vertical ? e.touches[0].clientY : e.touches[0].pageX; +} + +function getMousePosition(vertical: boolean, e: MouseEvent) { + return vertical ? e.clientY : e.pageX; +} + +function getHandleCenterPosition(vertical: boolean, handle: HTMLElement) { + const coords = handle.getBoundingClientRect(); + return vertical ? + coords.top + (coords.height * 0.5) : + coords.left + (coords.width * 0.5); +} + +function pauseEvent(e: Event) { + e.stopPropagation(); + e.preventDefault(); +} + +export class Handle extends React.Component<Partial<HandleProps>, {}> { + render() { + const { + className, + tipFormatter, + vertical, + offset, + value, + index, + } = this.props as HandleProps; + + const style = vertical ? { bottom: `${offset}%` } : { left: `${offset}%` }; + return ( + <div className={className} style={style} title={tipFormatter(value, index)} + /> + ); + } +} + +export interface SliderBaseProps { + min: number, + max: number, + step?: number, + defaultValue?: number | number[], + value?: number | number[], + marks?: any, + included?: boolean, + className?: string, + prefixCls?: string, + disabled?: boolean, + children?: any, + onBeforeChange?: (value: number | number[]) => void, + onChange?: (value: number | number[]) => void, + onAfterChange?: (value: number | number[]) => void, + handle?: JSX.Element, + tipFormatter?: (value: number, index: number) => any, + dots?: boolean, + range?: boolean | number, + vertical?: boolean, + allowCross?: boolean, + pushable?: boolean | number, +} + +export interface SliderBaseState { + handle: number | null, + recent: number, + bounds: number[] +} + +export class SliderBase extends React.Component<SliderBaseProps, SliderBaseState> { + private sliderElement: HTMLElement | undefined = void 0; + private handleElements: (HTMLElement | undefined)[] = []; + + constructor(props: SliderBaseProps) { + super(props); + + const { range, min, max } = props; + const initialValue = range ? Array.apply(null, Array(+range + 1)).map(() => min) : min; + const defaultValue = ('defaultValue' in props ? props.defaultValue : initialValue); + const value = (props.value !== undefined ? props.value : defaultValue); + + const bounds = (range ? value : [min, value]).map((v: number) => this.trimAlignValue(v)); + + let recent; + if (range && bounds[0] === bounds[bounds.length - 1] && bounds[0] === max) { + recent = 0; + } else { + recent = bounds.length - 1; + } + + this.state = { + handle: null, + recent, + bounds, + }; + } + + public static defaultProps: SliderBaseProps = { + prefixCls: 'molstar-slider-base', + className: '', + min: 0, + max: 100, + step: 1, + marks: {}, + handle: <Handle className='' vertical={false} offset={0} tipFormatter={v => v} value={0} index={0} />, + onBeforeChange: noop, + onChange: noop, + onAfterChange: noop, + tipFormatter: (value, index) => value, + included: true, + disabled: false, + dots: false, + range: false, + vertical: false, + allowCross: true, + pushable: false, + }; + + private dragOffset = 0; + private startPosition = 0; + private startValue = 0; + private _getPointsCache: any = void 0; + + componentWillReceiveProps(nextProps: SliderBaseProps) { + if (!('value' in nextProps || 'min' in nextProps || 'max' in nextProps)) return; + + const { bounds } = this.state; + if (nextProps.range) { + const value = nextProps.value || bounds; + const nextBounds = (value as number[]).map((v: number) => this.trimAlignValue(v, nextProps)); + if (nextBounds.every((v: number, i: number) => v === bounds[i])) return; + + this.setState({ bounds: nextBounds } as SliderBaseState); + if (bounds.some(v => this.isValueOutOfBounds(v, nextProps))) { + this.props.onChange!(nextBounds); + } + } else { + const value = nextProps.value !== undefined ? nextProps.value : bounds[1]; + const nextValue = this.trimAlignValue(value as number, nextProps); + if (nextValue === bounds[1] && bounds[0] === nextProps.min) return; + + this.setState({ bounds: [nextProps.min, nextValue] } as SliderBaseState); + if (this.isValueOutOfBounds(bounds[1], nextProps)) { + this.props.onChange!(nextValue); + } + } + } + + onChange(state: this['state']) { + const props = this.props; + const isNotControlled = !('value' in props); + if (isNotControlled) { + this.setState(state); + } else if (state.handle !== undefined) { + this.setState({ handle: state.handle } as SliderBaseState); + } + + const data = { ...this.state, ...(state as any) }; + const changedValue = props.range ? data.bounds : data.bounds[1]; + props.onChange!(changedValue); + } + + onMouseDown(e: MouseEvent) { + if (e.button !== 0) { return; } + + let position = getMousePosition(this.props.vertical!, e); + if (!this.isEventFromHandle(e)) { + this.dragOffset = 0; + } else { + const handlePosition = getHandleCenterPosition(this.props.vertical!, e.target as HTMLElement); + this.dragOffset = position - handlePosition; + position = handlePosition; + } + this.onStart(position); + this.addDocumentEvents('mouse'); + pauseEvent(e); + } + + onMouseMove(e: MouseEvent) { + const position = getMousePosition(this.props.vertical!, e); + this.onMove(e, position - this.dragOffset); + } + + onMove(e: MouseEvent | TouchEvent, position: number) { + pauseEvent(e); + const props = this.props; + const state = this.state; + + let diffPosition = position - this.startPosition; + diffPosition = this.props.vertical ? -diffPosition : diffPosition; + const diffValue = diffPosition / this.getSliderLength() * (props.max - props.min); + + const value = this.trimAlignValue(this.startValue + diffValue); + const oldValue = state.bounds[state.handle!]; + if (value === oldValue) return; + + const nextBounds = [...state.bounds]; + nextBounds[state.handle!] = value; + let nextHandle = state.handle!; + if (props.pushable !== false) { + const originalValue = state.bounds[nextHandle]; + this.pushSurroundingHandles(nextBounds, nextHandle, originalValue); + } else if (props.allowCross) { + nextBounds.sort((a, b) => a - b); + nextHandle = nextBounds.indexOf(value); + } + this.onChange({ + handle: nextHandle, + bounds: nextBounds, + } as SliderBaseState); + } + + onStart(position: number) { + const props = this.props; + props.onBeforeChange!(this.getValue()); + + const value = this.calcValueByPos(position); + this.startValue = value; + this.startPosition = position; + + const state = this.state; + const { bounds } = state; + + let valueNeedChanging = 1; + if (this.props.range) { + let closestBound = 0; + for (let i = 1; i < bounds.length - 1; ++i) { + if (value > bounds[i]) { closestBound = i; } + } + if (Math.abs(bounds[closestBound + 1] - value) < Math.abs(bounds[closestBound] - value)) { + closestBound = closestBound + 1; + } + valueNeedChanging = closestBound; + + const isAtTheSamePoint = (bounds[closestBound + 1] === bounds[closestBound]); + if (isAtTheSamePoint) { + valueNeedChanging = state.recent; + } + + if (isAtTheSamePoint && (value !== bounds[closestBound + 1])) { + valueNeedChanging = value < bounds[closestBound + 1] ? closestBound : closestBound + 1; + } + } + + this.setState({ + handle: valueNeedChanging, + recent: valueNeedChanging, + } as SliderBaseState); + + const oldValue = state.bounds[valueNeedChanging]; + if (value === oldValue) return; + + const nextBounds = [...state.bounds]; + nextBounds[valueNeedChanging] = value; + this.onChange({ bounds: nextBounds } as SliderBaseState); + } + + onTouchMove(e: TouchEvent) { + if (isNotTouchEvent(e)) { + this.end('touch'); + return; + } + + const position = getTouchPosition(this.props.vertical!, e); + this.onMove(e, position - this.dragOffset); + } + + onTouchStart(e: TouchEvent) { + if (isNotTouchEvent(e)) return; + + let position = getTouchPosition(this.props.vertical!, e); + if (!this.isEventFromHandle(e)) { + this.dragOffset = 0; + } else { + const handlePosition = getHandleCenterPosition(this.props.vertical!, e.target as HTMLElement); + this.dragOffset = position - handlePosition; + position = handlePosition; + } + this.onStart(position); + this.addDocumentEvents('touch'); + pauseEvent(e); + } + + /** + * Returns an array of possible slider points, taking into account both + * `marks` and `step`. The result is cached. + */ + getPoints() { + const { marks, step, min, max } = this.props; + const cache = this._getPointsCache; + if (!cache || cache.marks !== marks || cache.step !== step) { + const pointsObject = { ...marks }; + if (step !== null) { + for (let point = min; point <= max; point += step!) { + pointsObject[point] = point; + } + } + const points = Object.keys(pointsObject).map(parseFloat); + points.sort((a, b) => a - b); + this._getPointsCache = { marks, step, points }; + } + return this._getPointsCache.points; + } + + getPrecision(step: number) { + const stepString = step.toString(); + let precision = 0; + if (stepString.indexOf('.') >= 0) { + precision = stepString.length - stepString.indexOf('.') - 1; + } + return precision; + } + + getSliderLength() { + const slider = this.sliderElement; + if (!slider) { + return 0; + } + + return this.props.vertical ? slider.clientHeight : slider.clientWidth; + } + + getSliderStart() { + const slider = this.sliderElement as HTMLElement; + const rect = slider.getBoundingClientRect(); + + return this.props.vertical ? rect.top : rect.left; + } + + getValue(): number { + const { bounds } = this.state; + return (this.props.range ? bounds : bounds[1]) as number; + } + + private eventHandlers = { + 'touchmove': (e: TouchEvent) => this.onTouchMove(e), + 'touchend': (e: TouchEvent) => this.end('touch'), + 'mousemove': (e: MouseEvent) => this.onMouseMove(e), + 'mouseup': (e: MouseEvent) => this.end('mouse'), + } + + addDocumentEvents(type: 'touch' | 'mouse') { + if (type === 'touch') { + document.addEventListener('touchmove', this.eventHandlers.touchmove); + document.addEventListener('touchend', this.eventHandlers.touchend); + } else if (type === 'mouse') { + document.addEventListener('mousemove', this.eventHandlers.mousemove); + document.addEventListener('mouseup', this.eventHandlers.mouseup); + } + } + + calcOffset(value: number) { + const { min, max } = this.props; + const ratio = (value - min) / (max - min); + return ratio * 100; + } + + calcValue(offset: number) { + const { vertical, min, max } = this.props; + const ratio = Math.abs(offset / this.getSliderLength()); + const value = vertical ? (1 - ratio) * (max - min) + min : ratio * (max - min) + min; + return value; + } + + calcValueByPos(position: number) { + const pixelOffset = position - this.getSliderStart(); + const nextValue = this.trimAlignValue(this.calcValue(pixelOffset)); + return nextValue; + } + + end(type: 'mouse' | 'touch') { + this.removeEvents(type); + this.props.onAfterChange!(this.getValue()); + this.setState({ handle: null } as SliderBaseState); + } + + isEventFromHandle(e: Event) { + for (const h of this.handleElements) { + if (h === e.target) return true; + } + return false; + + // return this.state.bounds.some((x, i) => e.target + + // ( + // //this.handleElements[i] && e.target === ReactDOM.findDOMNode(this.handleElements[i]) + // )); + } + + isValueOutOfBounds(value: number, props: SliderBaseProps) { + return value < props.min || value > props.max; + } + + pushHandle(bounds: number[], handle: number, direction: number, amount: number) { + const originalValue = bounds[handle]; + let currentValue = bounds[handle]; + while (direction * (currentValue - originalValue) < amount) { + if (!this.pushHandleOnePoint(bounds, handle, direction)) { + // can't push handle enough to create the needed `amount` gap, so we + // revert its position to the original value + bounds[handle] = originalValue; + return false; + } + currentValue = bounds[handle]; + } + // the handle was pushed enough to create the needed `amount` gap + return true; + } + + pushHandleOnePoint(bounds: number[], handle: number, direction: number) { + const points = this.getPoints(); + const pointIndex = points.indexOf(bounds[handle]); + const nextPointIndex = pointIndex + direction; + if (nextPointIndex >= points.length || nextPointIndex < 0) { + // reached the minimum or maximum available point, can't push anymore + return false; + } + const nextHandle = handle + direction; + const nextValue = points[nextPointIndex]; + const { pushable: threshold } = this.props; + const diffToNext = direction * (bounds[nextHandle] - nextValue); + if (!this.pushHandle(bounds, nextHandle, direction, +threshold! - diffToNext)) { + // couldn't push next handle, so we won't push this one either + return false; + } + // push the handle + bounds[handle] = nextValue; + return true; + } + + pushSurroundingHandles(bounds: number[], handle: number, originalValue: number) { + const { pushable: threshold } = this.props; + const value = bounds[handle]; + + let direction = 0; + if (bounds[handle + 1] - value < threshold!) { + direction = +1; + } else if (value - bounds[handle - 1] < threshold!) { + direction = -1; + } + + if (direction === 0) { return; } + + const nextHandle = handle + direction; + const diffToNext = direction * (bounds[nextHandle] - value); + if (!this.pushHandle(bounds, nextHandle, direction, +threshold! - diffToNext)) { + // revert to original value if pushing is impossible + bounds[handle] = originalValue; + } + } + + removeEvents(type: 'touch' | 'mouse') { + if (type === 'touch') { + document.removeEventListener('touchmove', this.eventHandlers.touchmove); + document.removeEventListener('touchend', this.eventHandlers.touchend); + } else if (type === 'mouse') { + document.removeEventListener('mousemove', this.eventHandlers.mousemove); + document.removeEventListener('mouseup', this.eventHandlers.mouseup); + } + } + + trimAlignValue(v: number, nextProps?: SliderBaseProps) { + const { handle, bounds } = (this.state || {}) as this['state']; + const { marks, step, min, max, allowCross } = { ...this.props, ...(nextProps || {}) } as SliderBaseProps; + + let val = v; + if (val <= min) { + val = min; + } + if (val >= max) { + val = max; + } + /* eslint-disable eqeqeq */ + if (!allowCross && handle != null && handle > 0 && val <= bounds[handle - 1]) { + val = bounds[handle - 1]; + } + if (!allowCross && handle != null && handle < bounds.length - 1 && val >= bounds[handle + 1]) { + val = bounds[handle + 1]; + } + /* eslint-enable eqeqeq */ + + const points = Object.keys(marks).map(parseFloat); + if (step !== null) { + const closestStep = (Math.round((val - min) / step!) * step!) + min; + points.push(closestStep); + } + + const diffs = points.map((point) => Math.abs(val - point)); + const closestPoint = points[diffs.indexOf(Math.min.apply(Math, diffs))]; + + return step !== null ? parseFloat(closestPoint.toFixed(this.getPrecision(step!))) : closestPoint; + } + + render() { + const { + handle, + bounds, + } = this.state; + const { + className, + prefixCls, + disabled, + vertical, + dots, + included, + range, + step, + marks, + max, min, + tipFormatter, + children, + } = this.props; + + const customHandle = this.props.handle; + + const offsets = bounds.map(v => this.calcOffset(v)); + + const handleClassName = `${prefixCls}-handle`; + + const handlesClassNames = bounds.map((v, i) => classNames({ + [handleClassName]: true, + [`${handleClassName}-${i + 1}`]: true, + [`${handleClassName}-lower`]: i === 0, + [`${handleClassName}-upper`]: i === bounds.length - 1, + })); + + const isNoTip = (step === null) || (tipFormatter === null); + + const commonHandleProps = { + prefixCls, + noTip: isNoTip, + tipFormatter, + vertical, + }; + + this.handleElements = []; + const handles = bounds.map((v, i) => React.cloneElement(customHandle!, { + ...commonHandleProps, + className: handlesClassNames[i], + value: v, + offset: offsets[i], + dragging: handle === i, + index: i, + key: i, + ref: (h: any) => this.handleElements.push(h) //`handle-${i}`, + })); + if (!range) { handles.shift(); } + + const isIncluded = included || range; + + const tracks: JSX.Element[] = []; + // for (let i = 1; i < bounds.length; ++i) { + // const trackClassName = classNames({ + // [`${prefixCls}-track`]: true, + // [`${prefixCls}-track-${i}`]: true, + // }); + // tracks.push( + // <Track className={trackClassName} vertical={vertical} included={isIncluded} + // offset={offsets[i - 1]} length={offsets[i] - offsets[i - 1]} key={i} + // /> + // ); + // } + + const sliderClassName = classNames({ + [prefixCls!]: true, + [`${prefixCls}-with-marks`]: Object.keys(marks).length, + [`${prefixCls}-disabled`]: disabled!, + [`${prefixCls}-vertical`]: this.props.vertical!, + [className!]: !!className, + }); + + return ( + <div ref={e => this.sliderElement = e!} className={sliderClassName} + onTouchStart={disabled ? noop : this.onTouchStart.bind(this)} + onMouseDown={disabled ? noop : this.onMouseDown.bind(this)} + > + <div className={`${prefixCls}-rail`} /> + {tracks} + <Steps prefixCls={prefixCls} vertical={vertical} marks={marks} dots={dots} step={step} + included={isIncluded} lowerBound={bounds[0]} + upperBound={bounds[bounds.length - 1]} max={max} min={min} + /> + {handles} + <Marks className={`${prefixCls}-mark`} vertical={vertical!} marks={marks} + included={isIncluded!} lowerBound={bounds[0]} + upperBound={bounds[bounds.length - 1]} max={max} min={min} + /> + {children} + </div> + ); + } +} + +export interface HandleProps { + className: string, + vertical: boolean, + offset: number, + tipFormatter: (v: number, index: number) => any, + value: number, + index: number, +} + +interface MarksProps { + className: string, + vertical: boolean, + marks: any, + included: boolean | number, + upperBound: number, + lowerBound: number, + max: number, + min: number +} +const Marks = ({ className, vertical, marks, included, upperBound, lowerBound, max, min }: MarksProps) => { + const marksKeys = Object.keys(marks); + const marksCount = marksKeys.length; + const unit = 100 / (marksCount - 1); + const markWidth = unit * 0.9; + + const range = max - min; + const elements = marksKeys.map(parseFloat).sort((a, b) => a - b).map((point) => { + const isActived = (!included && point === upperBound) || + (included && point <= upperBound && point >= lowerBound); + const markClassName = classNames({ + [`${className}-text`]: true, + [`${className}-text-active`]: isActived, + }); + + const bottomStyle = { + // height: markWidth + '%', + marginBottom: '-50%', + bottom: `${(point - min) / range * 100}%`, + }; + + const leftStyle = { + width: `${markWidth}%`, + marginLeft: `${-markWidth / 2}%`, + left: `${(point - min) / range * 100}%`, + }; + + const style = vertical ? bottomStyle : leftStyle; + + const markPoint = marks[point]; + const markPointIsObject = typeof markPoint === 'object' && !React.isValidElement(markPoint); + const markLabel = markPointIsObject ? markPoint.label : markPoint; + const markStyle = markPointIsObject ? { ...style, ...markPoint.style } : style; + return (<span className={markClassName} style={markStyle} key={point}> + {markLabel} + </span>); + }); + + return <div className={className}>{elements}</div>; +}; + +function calcPoints(vertical: boolean, marks: any, dots: boolean, step: number, min: number, max: number) { + const points = Object.keys(marks).map(parseFloat); + if (dots) { + for (let i = min; i <= max; i = i + step) { + if (points.indexOf(i) >= 0) continue; + points.push(i); + } + } + return points; +} + +const Steps = ({ prefixCls, vertical, marks, dots, step, included, + lowerBound, upperBound, max, min }: any) => { + const range = max - min; + const elements = calcPoints(vertical, marks, dots, step, min, max).map((point) => { + const offset = `${Math.abs(point - min) / range * 100}%`; + const style = vertical ? { bottom: offset } : { left: offset }; + + const isActived = (!included && point === upperBound) || + (included && point <= upperBound && point >= lowerBound); + const pointClassName = classNames({ + [`${prefixCls}-dot`]: true, + [`${prefixCls}-dot-active`]: isActived, + }); + + return <span className={pointClassName} style={style} key={point} />; + }); + + return <div className={`${prefixCls}-step`}>{elements}</div>; +}; \ No newline at end of file diff --git a/src/mol-app/ui/entity/tree.tsx b/src/mol-app/ui/entity/tree.tsx new file mode 100644 index 000000000..1d9afd1d9 --- /dev/null +++ b/src/mol-app/ui/entity/tree.tsx @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { EntityTreeController } from '../../controller/entity/tree'; +import { Controller } from '../../controller/controller'; +import { AnyEntity, RootEntity } from 'mol-view/state/entity'; +import { AnyTransform, SpacefillUpdate, UrlToData, DataToCif, FileToData, CifToMmcif, MmcifToModel, ModelToStructure, StructureToSpacefill, MmcifFileToSpacefill } from 'mol-view/state/transform'; + +function getTransforms(entity: AnyEntity): AnyTransform[] { + const transforms: AnyTransform[] = [] + switch (entity.kind) { + case 'root': + transforms.push(MmcifFileToSpacefill) + break; + case 'url': + transforms.push(UrlToData) + break; + case 'file': + transforms.push(FileToData) + break; + case 'data': + transforms.push(DataToCif) + break; + case 'cif': + transforms.push(CifToMmcif) + break; + case 'mmcif': + transforms.push(MmcifToModel) + break; + case 'model': + transforms.push(ModelToStructure) + break; + case 'structure': + transforms.push(StructureToSpacefill) + break; + case 'spacefill': + transforms.push(SpacefillUpdate) + break; + } + return transforms +} + +export class Entity extends View<Controller<any>, {}, { entity: AnyEntity}> { + render() { + const entity = this.props.entity + + return <div className='molstar-entity-tree-entry'> + <div className='molstar-entity-tree-entry-body'> + <div className='molstar-entity-tree-entry-label-wrap'> + <button + className='molstar-entity-tree-entry-label' + onClick={() => { + console.log(entity) + this.controller.context.currentEntity.next(entity) + this.controller.context.currentTransforms.next(getTransforms(entity)) + }} + > + <span>{entity.id} - {entity.kind}</span> + </button> + </div> + </div> + </div>; + } +} + +export class EntityTree extends View<EntityTreeController, {}, {}> { + render() { + const entities: JSX.Element[] = [] + const state = this.controller.state.getValue() + if (state) { + state.entities.forEach(e => { + entities.push( + <div key={e.id}> + <Entity controller={this.controller} entity={e}></Entity> + </div> + ) + }) + } + + return <div className='molstar-entity-tree'> + <div className='molstar-entity-tree-root'> + <Entity controller={this.controller} entity={RootEntity}></Entity> + </div> + <div className='molstar-entity-tree-children'> + <div>{entities}</div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/layout.tsx b/src/mol-app/ui/layout.tsx new file mode 100644 index 000000000..df5184a84 --- /dev/null +++ b/src/mol-app/ui/layout.tsx @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { LayoutController, LayoutTarget, LayoutRegion, CollapsedControlsLayout } from '../controller/layout'; +import { View } from './view'; + +export class Layout extends View<LayoutController, { }, { }> { + + private renderTarget(target: LayoutTarget) { + const statics: any[] = []; + const scrollable: any[] = []; + + for (let c of target.components) { + if (c.isStatic) statics.push(<c.view key={c.key} controller={c.controller} />); + else scrollable.push(<c.view key={c.key} controller={c.controller} />); + } + + return <div key={target.cssClass} className={'molstar-layout-region molstar-layout-' + target.cssClass}> + { statics.length ? <div className='molstar-layout-static'>{statics}</div> : void 0 } + { scrollable.length ? <div className='molstar-layout-scrollable'>{scrollable}</div> : void 0 } + </div>; + } + + private updateTarget(name: string, regionType: LayoutRegion, layout: { regions: any[], layoutClass: string }) { + const state = this.controller.latestState; + const regionStates = state.regionStates; + const region = this.controller.targets[regionType]; + let show: boolean; + + if (state.hideControls) { + show = regionStates !== void 0 && regionStates[regionType] === 'Sticky' && region.components.length > 0; + } else if (regionStates && regionStates[regionType] === 'Hidden') { + show = false; + } else { + show = region.components.length > 0; + } + + if (show) { + layout.regions.push(this.renderTarget(region)); + } else { + layout.layoutClass += ' molstar-layout-hide-' + name; + } + } + + render() { + let layoutClass = ''; + + const state = this.controller.latestState; + let layoutType: string; + + if (state.isExpanded) { + layoutType = 'molstar-layout-expanded'; + } else { + layoutType = 'molstar-layout-standard '; + switch (state.collapsedControlsLayout) { + case CollapsedControlsLayout.Outside: layoutType += 'molstar-layout-standard-outside'; break; + case CollapsedControlsLayout.Landscape: layoutType += 'molstar-layout-standard-landscape'; break; + case CollapsedControlsLayout.Portrait: layoutType += 'molstar-layout-standard-portrait'; break; + default: layoutType += 'molstar-layout-standard-outside'; break; + } + } + + const targets = this.controller.targets; + const regions = [this.renderTarget(targets[LayoutRegion.Main])]; + + const layout = { regions, layoutClass }; + this.updateTarget('top', LayoutRegion.Top, layout); + this.updateTarget('right', LayoutRegion.Right, layout); + this.updateTarget('bottom', LayoutRegion.Bottom, layout); + this.updateTarget('left', LayoutRegion.Left, layout); + layoutClass = layout.layoutClass; + + let root = targets[LayoutRegion.Root].components.map(c => <c.view key={c.key} controller={c.controller} />); + + return <div className='molstar-plugin'> + <div className={'molstar-plugin-content ' + layoutType}> + <div className={layoutClass}> + {regions} + {root} + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/misc/jobs.tsx b/src/mol-app/ui/misc/jobs.tsx new file mode 100644 index 000000000..c8a5fe557 --- /dev/null +++ b/src/mol-app/ui/misc/jobs.tsx @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { JobInfo, JobsController } from '../../controller/misc/jobs'; +import { Button } from '../controls/common'; +import { View } from '../view'; + +class JobState extends React.Component<{ info: JobInfo, isSmall?: boolean }, {}> { + render() { + const info = this.props.info; + return <div className='molstar-task-state'> + <div> + { info.abort ? <Button onClick={() => info.abort!.call(null) } style='remove' + icon='abort' title='Abort' customClass='molstar-btn-icon' + /> : void 0 } + <div> + {info.name}: {info.message} + </div> + </div> + </div>; + } +} + +export class Overlay extends View<JobsController, {}, {}> { + render() { + const state = this.controller.latestState; + + if (!state.jobs!.count()) return <div className='molstar-empty-control' /> + + const jobs: any[] = []; + state.jobs!.forEach((t, k) => jobs.push(<JobState key={k} info={t!} />)); + + return <div className='molstar-overlay'> + <div className='molstar-overlay-background' /> + <div className='molstar-overlay-content-wrap'> + <div className='molstar-overlay-content'> + <div> + {jobs} + </div> + </div> + </div> + </div>; + } +} + +export class BackgroundJobs extends View<JobsController, {}, {}> { + render() { + const state = this.controller.latestState; + + if (!state.jobs!.count()) return <div className='molstar-empty-control' /> + + const jobs: any[] = []; + state.jobs!.forEach((t, k) => jobs.push(<JobState key={k} info={t!} isSmall={true} />)); + + return <div className='molstar-background-jobs'> + {jobs} + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/misc/log.tsx b/src/mol-app/ui/misc/log.tsx new file mode 100644 index 000000000..803d86f70 --- /dev/null +++ b/src/mol-app/ui/misc/log.tsx @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { View } from '../view'; +import { LogController } from '../../controller/misc/log'; +import { CommonEvents } from '../../event/basic'; +import { formatTime } from 'mol-util'; +import { Logger } from '../../service/logger'; + +export class Log extends View<LogController, {}, {}> { + + private wrapper: HTMLDivElement | undefined = void 0; + + componentWillMount() { + super.componentWillMount(); + this.subscribe(CommonEvents.LayoutChanged.getStream(this.controller.context), () => this.scrollToBottom()); + } + + componentDidUpdate() { + this.scrollToBottom(); + } + + private scrollToBottom() { + const log = this.wrapper; + if (log) log.scrollTop = log.scrollHeight - log.clientHeight - 1; + } + + render() { + const entries = this.controller.latestState.entries; + + return <div className='molstar-log-wrap'> + <div className='molstar-log' ref={log => this.wrapper = log!}> + <ul className='molstar-list-unstyled'> + {entries.map((entry, i, arr) => { + + let label: JSX.Element; + let e = entry!; + switch (e.type) { + case Logger.EntryType.Error: + label = <span className='label label-danger'>Error</span>; + break; + case Logger.EntryType.Warning: + label = <span className='label label-warning'>Warning</span>; + break; + case Logger.EntryType.Info: + label = <span className='label label-info'>Info</span>; + break; + default: + label = <span></span> + } + + let t = formatTime(e.timestamp); + return <li key={i}> + <div className={'molstar-log-entry-badge molstar-log-entry-' + Logger.EntryType[e.type].toLowerCase()} /> + {label} + <div className='molstar-log-timestamp'>{t}</div> + <div className='molstar-log-entry'>{e.message}</div> + </li>; + }) } + </ul> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/file-loader.tsx b/src/mol-app/ui/transform/file-loader.tsx new file mode 100644 index 000000000..a88b13f9a --- /dev/null +++ b/src/mol-app/ui/transform/file-loader.tsx @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' +import { View } from '../view'; +import { FileInput } from '../controls/common'; +import { TransformListController } from '../../controller/transform/list'; +import { FileEntity } from 'mol-view/state/entity'; +import { MmcifFileToSpacefill } from 'mol-view/state/transform'; +import { StateContext } from 'mol-view/state/context'; + +export class FileLoader extends View<TransformListController, {}, { ctx: StateContext }> { + render() { + return <div className='molstar-file-loader'> + <FileInput + accept='*.cif' + onChange={files => { + if (files) { + const fileEntity = FileEntity.ofFile(this.props.ctx, files[0]) + MmcifFileToSpacefill.apply(this.props.ctx, fileEntity) + } + }} + /> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/list.tsx b/src/mol-app/ui/transform/list.tsx new file mode 100644 index 000000000..b72e8e6be --- /dev/null +++ b/src/mol-app/ui/transform/list.tsx @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { TransformListController } from '../../controller/transform/list'; +import { AnyTransform } from 'mol-view/state/transform'; +import { Spacefill } from './spacefill'; +import { AnyEntity } from 'mol-view/state/entity'; +import { FileLoader } from './file-loader'; +import { ModelToStructure } from './model'; + +function getTransformComponent(controller: TransformListController, entity: AnyEntity, transform: AnyTransform) { + switch (transform.kind) { + case 'file-to-spacefill': + return <FileLoader controller={controller} ctx={controller.context.stage.ctx}></FileLoader> + case 'model-to-structure': + return <ModelToStructure controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></ModelToStructure> + case 'spacefill-update': + return <Spacefill controller={controller} entity={entity} transform={transform} ctx={controller.context.stage.ctx}></Spacefill> + } + return <Transform controller={controller} entity={entity} transform={transform}></Transform> +} + +export class Transform extends View<Controller<any>, {}, { transform: AnyTransform, entity: AnyEntity }> { + render() { + const { transform, entity } = this.props + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={(e)=> { + console.log(transform, entity) + }} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </div> + </div> + </div>; + } +} + +export class TransformList extends View<TransformListController, {}, {}> { + render() { + const transforms: JSX.Element[] = [] + const state = this.controller.state.getValue() + if (state && state.entity) { + const entity = state.entity + if (entity) { + state.transforms.forEach(t => { + transforms.push( + <div + key={`${t.inputKind}|${t.outputKind}`} + children={getTransformComponent(this.controller, entity, t)} + /> + ) + }) + } + } + + return <div className='molstar-transform-view' children={transforms} />; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/model.tsx b/src/mol-app/ui/transform/model.tsx new file mode 100644 index 000000000..ebd4d2edb --- /dev/null +++ b/src/mol-app/ui/transform/model.tsx @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { ModelEntity } from 'mol-view/state/entity'; +import { StructureProps, ModelToStructure as ModelToStructureTransform } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; + +export class ModelToStructure extends View<Controller<any>, StructureProps, { transform: ModelToStructureTransform, entity: ModelEntity, ctx: StateContext }> { + state = { + assembly: '' + } + + create(state?: Partial<StructureProps>) { + const { transform, entity, ctx } = this.props + console.log('create structure', transform, entity) + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + render() { + const { transform, entity } = this.props + + const assemblyOptions = entity.value[0].symmetry.assemblies.map((value, idx) => { + return <option key={value.id} value={value.id}>{value.details}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.create()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </div> + <div className='molstar-panel-body'> + <div> + <div className='molstar-control-row molstar-options-group'> + <span>Details</span> + <div> + <select + className='molstar-form-control' + value={this.state.assembly} + onChange={(e) => this.create({ assembly: e.target.value })} + > + {assemblyOptions} + </select> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/transform/spacefill.tsx b/src/mol-app/ui/transform/spacefill.tsx new file mode 100644 index 000000000..5ee8d2959 --- /dev/null +++ b/src/mol-app/ui/transform/spacefill.tsx @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { View } from '../view'; +import { Controller } from '../../controller/controller'; +import { SpacefillEntity } from 'mol-view/state/entity'; +import { SpacefillUpdate } from 'mol-view/state/transform' +import { StateContext } from 'mol-view/state/context'; +import { ColorTheme } from 'mol-geo/theme'; +import { Color, ColorNames } from 'mol-util/color'; + +export const ColorThemeInfo = { + 'atom-index': {}, + 'chain-id': {}, + 'element-symbol': {}, + 'instance-index': {}, + 'uniform': {} +} +export type ColorThemeInfo = keyof typeof ColorThemeInfo + +interface SpacefillState { + doubleSided: boolean + detail: number + colorTheme: ColorTheme + colorValue: Color +} + +export class Spacefill extends View<Controller<any>, SpacefillState, { transform: SpacefillUpdate, entity: SpacefillEntity, ctx: StateContext }> { + state = { + doubleSided: true, + detail: 2, + colorTheme: { name: 'element-symbol' } as ColorTheme, + colorValue: 0x000000 + } + + update(state?: Partial<SpacefillState>) { + const { transform, entity, ctx } = this.props + console.log('update spacefill', transform, entity) + const newState = { ...this.state, ...state } + this.setState(newState) + transform.apply(ctx, entity, newState) + } + + render() { + const { transform } = this.props + + const sphereDetailOptions = [0, 1, 2, 3].map((value, idx) => { + return <option key={value} value={value}>{value.toString()}</option> + }) + + const colorThemeOptions = Object.keys(ColorThemeInfo).map((name, idx) => { + return <option key={name} value={name}>{name}</option> + }) + + const colorValueOptions = Object.keys(ColorNames).map((name, idx) => { + return <option key={name} value={(ColorNames as any)[name]}>{name}</option> + }) + + return <div className='molstar-transformer-wrapper'> + <div className='molstar-panel molstar-control molstar-transformer molstar-panel-expanded'> + <div className='molstar-panel-header'> + <button + className='molstar-btn molstar-btn-link molstar-panel-expander' + onClick={() => this.update()} + > + <span>[{transform.kind}] {transform.inputKind} -> {transform.outputKind}</span> + </button> + </div> + <div className='molstar-panel-body'> + <div> + <div className='molstar-control-row molstar-options-group'> + <span>Sphere detail</span> + <div> + <select + className='molstar-form-control' + value={this.state.detail} + onChange={(e) => this.update({ detail: parseInt(e.target.value) })} + > + {sphereDetailOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color theme</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorTheme.name} + onChange={(e) => { + const colorThemeName = e.target.value as ColorThemeInfo + if (colorThemeName === 'uniform') { + this.update({ + colorTheme: { + name: colorThemeName, + value: this.state.colorValue + } + }) + } else { + this.update({ + colorTheme: { name: colorThemeName } + }) + } + }} + > + {colorThemeOptions} + </select> + </div> + </div> + <div className='molstar-control-row molstar-options-group'> + <span>Color value</span> + <div> + <select + className='molstar-form-control' + value={this.state.colorValue} + onChange={(e) => { + const colorValue = parseInt(e.target.value) + this.update({ + colorTheme: { + name: 'uniform', + value: colorValue + }, + colorValue + }) + }} + > + {colorValueOptions} + </select> + </div> + </div> + </div> + </div> + </div> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-app/ui/view.tsx b/src/mol-app/ui/view.tsx new file mode 100644 index 000000000..d7374759f --- /dev/null +++ b/src/mol-app/ui/view.tsx @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import * as React from 'react' +import { Observable, Subscription } from 'rxjs'; +import { merge, shallowEqual } from 'mol-util' +import { Context } from '../context/context'; +import { Controller } from '../controller/controller'; + +export abstract class PureView<State, Props, ViewState> extends React.Component<{ + state: State + onChange: (s: State) => void +} & Props, ViewState> { + + protected update(s: State) { + let ns = merge<State>(this.props.state, s); + if (ns !== this.props.state as any) this.props.onChange(ns); + } + + shouldComponentUpdate(nextProps: any, nextState: any) { + return !shallowEqual(this.props, nextProps) || !shallowEqual(this.state, nextState); + } +} + +export abstract class ComponentView<Props> extends React.Component<{ context: Context } & Props, {}> { + + // shouldComponentUpdate(nextProps: any, nextState: any) { + // return !shallowEqual(this.props, nextProps); + // } + + private subs: Subscription[] = []; + protected subscribe<T>(stream: Observable<T>, obs: (n: T) => void) { + let sub = stream.subscribe(obs); + this.subs.push(sub); + return sub; + } + + protected unsubscribe(sub: Subscription) { + let idx = this.subs.indexOf(sub); + for (let i = idx; i < this.subs.length - 1; i++) { + this.subs[i] = this.subs[i + 1]; + } + sub.unsubscribe(); + this.subs.pop(); + } + + componentWillUnmount() { + for (let s of this.subs) s.unsubscribe(); + this.subs = []; + } +} + +export abstract class ObserverView<P, S> extends React.Component<P, S> { + private subs: Subscription[] = []; + + protected subscribe<T>(stream: Observable<T>, obs: (n: T) => void) { + let sub = stream.subscribe(obs); + this.subs.push(sub); + return sub; + } + + protected unsubscribe(sub: Subscription) { + let idx = this.subs.indexOf(sub); + for (let i = idx; i < this.subs.length - 1; i++) { + this.subs[i] = this.subs[i + 1]; + } + sub.unsubscribe(); + this.subs.pop(); + } + + componentWillUnmount() { + for (let s of this.subs) s.unsubscribe(); + this.subs = []; + } +} + +export abstract class View<T extends Controller<any>, State, CustomProps> + extends ObserverView<{ controller: T } & CustomProps, State> { + + public get controller(): T { + return this.props.controller as any; + } + + componentWillMount() { + this.subscribe(this.controller.state as any, (s) => { + this.forceUpdate() + }); + } +} \ No newline at end of file diff --git a/src/mol-app/ui/visualization/viewport.tsx b/src/mol-app/ui/visualization/viewport.tsx new file mode 100644 index 000000000..71b3afdeb --- /dev/null +++ b/src/mol-app/ui/visualization/viewport.tsx @@ -0,0 +1,141 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import * as React from 'react' + +import { ViewportController } from '../../controller/visualization/viewport' +import { View } from '../view'; +import { HelpBox, Toggle, Button } from '../controls/common' +import { Slider } from '../controls/slider' + +export class ViewportControls extends View<ViewportController, { showSceneOptions?: boolean, showHelp?: boolean }, {}> { + state = { showSceneOptions: false, showHelp: false }; + + private help() { + return <div className='molstar-viewport-controls-scene-options molstar-control'> + <HelpBox title='Rotate' content={<div><div>Left button</div><div>One finger touch</div></div>} /> + <HelpBox title='Zoom' content={<div><div>Right button</div><div>Pinch</div></div>} /> + <HelpBox title='Move' content={<div><div>Middle button</div><div>Two finger touch</div></div>} /> + <HelpBox title='Slab' content={<div><div>Mouse wheel</div><div>Three finger touch</div></div>} /> + </div> + } + + render() { + let state = this.controller.latestState; + + let options: any; + + let layoutController = this.controller.context.layout; + let layoutState = layoutController.latestState; + if (this.state.showSceneOptions) { + options = <div className='molstar-viewport-controls-scene-options molstar-control'> + <Toggle onChange={v => this.controller.setState({ enableFog: v })} value={state.enableFog!} label='Fog' /> + <Slider label='FOV' min={30} max={90} onChange={v => this.controller.setState({ cameraFOV: v }) } value={state.cameraFOV!} /> + <Slider label='Camera Speed' min={1} max={10} step={0.01} onChange={v => this.controller.setState({ cameraSpeed: v }) } value={state.cameraSpeed!} /> + </div>; + } else if (this.state.showHelp) { + options = this.help(); + } + + let controlsShown = !layoutState.hideControls; + return <div className='molstar-viewport-controls' onMouseLeave={() => this.setState({ showSceneOptions: false, showHelp: false })}> + <div className='molstar-viewport-controls-buttons'> + <Button + style='link' + active={this.state.showHelp} + customClass={'molstar-btn-link-toggle-' + (this.state.showHelp ? 'on' : 'off')} + icon='help-circle' + onClick={(e) => this.setState({ showHelp: !this.state.showHelp, showSceneOptions: false }) } title='Controls Help' /> + <Button + style='link' + active={this.state.showSceneOptions} + customClass={'molstar-btn-link-toggle-' + (this.state.showSceneOptions ? 'on' : 'off')} + icon='settings' + onClick={(e) => this.setState({ showSceneOptions: !this.state.showSceneOptions, showHelp: false }) } title='Scene Options' /> + <Button + style='link' + icon='screenshot' + onClick={(e) => this.controller.context.stage.viewer.downloadScreenshot()} + title='Screenshot' /> + <Button onClick={() => { layoutController.update({ hideControls: controlsShown }); this.forceUpdate(); } } + icon='tools' title={controlsShown ? 'Hide Controls' : 'Show Controls'} active={controlsShown } + customClass={'molstar-btn-link-toggle-' + (controlsShown ? 'on' : 'off')} + style='link' /> + <Button onClick={() => layoutController.update({ isExpanded: !layoutState.isExpanded }) } + icon='expand-layout' title={layoutState.isExpanded ? 'Collapse' : 'Expand'} active={layoutState.isExpanded } + customClass={'molstar-btn-link-toggle-' + (layoutState.isExpanded ? 'on' : 'off')} + style='link' /> + <Button + style='link' + icon='reset-scene' + onClick={(e) => this.controller.context.stage.viewer.resetCamera()} + title='Reset camera' /> + </div> + {options} + </div>; + } +} + +export const Logo = () => + <div className='molstar-logo'> + <div> + <div> + <div /> + <div className='molstar-logo-image' /> + </div> + </div> + </div> + + +export class Viewport extends View<ViewportController, {}, { noWebGl?: boolean, showLogo?: boolean }> { + private container: HTMLDivElement | null = null; + private canvas: HTMLCanvasElement | null = null; + private defaultBg = { r: 1, g: 1, b: 1 } + state = { noWebGl: false, showLogo: true }; + + componentDidMount() { + if (!this.canvas || !this.container || !this.controller.context.initStage(this.canvas, this.container)) { + this.setState({ noWebGl: true }); + } + this.controller.context.stage.viewer.reprCount.subscribe(count => { + this.setState({ + showLogo: false + // showLogo: count === 0 + }) + }) + } + + componentWillUnmount() { + super.componentWillUnmount(); + this.controller.context.destroy(); + } + + renderMissing() { + return <div className='molstar-no-webgl'> + <div> + <p><b>WebGL does not seem to be available.</b></p> + <p>This can be caused by an outdated browser, graphics card driver issue, or bad weather. Sometimes, just restarting the browser helps.</p> + <p>For a list of supported browsers, refer to <a href='http://caniuse.com/#feat=webgl' target='_blank'>http://caniuse.com/#feat=webgl</a>.</p> + </div> + </div> + } + + render() { + if (this.state.noWebGl) return this.renderMissing(); + + const color = this.controller.latestState.clearColor! || this.defaultBg; + return <div className='molstar-viewport' style={{ backgroundColor: `rgb(${255 * color.r}, ${255 * color.g}, ${255 * color.b})` }}> + <div ref={elm => this.container = elm} className='molstar-viewport-container'> + <canvas ref={elm => this.canvas = elm} className='molstar-viewport-canvas'></canvas> + </div> + {this.state.showLogo ? <Logo /> : void 0} + <ViewportControls controller={this.controller} /> + </div>; + } +} \ No newline at end of file diff --git a/src/mol-io/reader/_spec/cif.spec.ts b/src/mol-io/reader/_spec/cif.spec.ts index e7d619f28..a2fb03ed9 100644 --- a/src/mol-io/reader/_spec/cif.spec.ts +++ b/src/mol-io/reader/_spec/cif.spec.ts @@ -18,8 +18,8 @@ const strField = TextField({ data: columnData, indices: [3, 4, 4, 5, 5, 6], coun const strListField = TextField({ data: columnData, indices: [7, 12], count: 1 }, 1); const intListField = TextField({ data: columnData, indices: [14, 19], count: 1 }, 1); -const testBlock = Data.Block(['test'], { - test: Data.Category('test', 3, ['int', 'str', 'strList', 'intList'], { +const testBlock = Data.CifBlock(['test'], { + test: Data.CifCategory('test', 3, ['int', 'str', 'strList', 'intList'], { int: intField, str: strField, strList: strListField, diff --git a/src/mol-io/reader/cif.ts b/src/mol-io/reader/cif.ts index 440be3855..54aad885c 100644 --- a/src/mol-io/reader/cif.ts +++ b/src/mol-io/reader/cif.ts @@ -7,7 +7,7 @@ import parseText from './cif/text/parser' import parseBinary from './cif/binary/parser' -import { Frame } from './cif/data-model' +import { CifFrame } from './cif/data-model' import { toDatabaseCollection, toDatabase } from './cif/schema' import { mmCIF_Schema, mmCIF_Database } from './cif/schema/mmcif' import { CCD_Schema, CCD_Database } from './cif/schema/ccd' @@ -22,11 +22,11 @@ export default { toDatabaseCollection, toDatabase, schema: { - mmCIF: (frame: Frame) => toDatabase<mmCIF_Schema, mmCIF_Database>(mmCIF_Schema, frame), - CCD: (frame: Frame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame), - BIRD: (frame: Frame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame), - dic: (frame: Frame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame), - densityServer: (frame: Frame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame) + mmCIF: (frame: CifFrame) => toDatabase<mmCIF_Schema, mmCIF_Database>(mmCIF_Schema, frame), + CCD: (frame: CifFrame) => toDatabase<CCD_Schema, CCD_Database>(CCD_Schema, frame), + BIRD: (frame: CifFrame) => toDatabase<BIRD_Schema, BIRD_Database>(BIRD_Schema, frame), + dic: (frame: CifFrame) => toDatabase<dic_Schema, dic_Database>(dic_Schema, frame), + densityServer: (frame: CifFrame) => toDatabase<DensityServer_Data_Schema, DensityServer_Data_Database>(DensityServer_Data_Schema, frame) } } diff --git a/src/mol-io/reader/cif/binary/field.ts b/src/mol-io/reader/cif/binary/field.ts index dd5758f51..af596d70a 100644 --- a/src/mol-io/reader/cif/binary/field.ts +++ b/src/mol-io/reader/cif/binary/field.ts @@ -10,12 +10,12 @@ import * as Data from '../data-model' import { EncodedColumn, decode } from '../../../common/binary-cif' import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser' -export default function Field(column: EncodedColumn): Data.Field { +export default function Field(column: EncodedColumn): Data.CifField { const mask = column.mask ? decode(column.mask) as number[] : void 0; const data = decode(column.data); const isNumeric = ColumnHelpers.isTypedArray(data); - const str: Data.Field['str'] = isNumeric + const str: Data.CifField['str'] = isNumeric ? mask ? row => mask[row] === Column.ValueKind.Present ? '' + data[row] : '' : row => '' + data[row] @@ -23,15 +23,15 @@ export default function Field(column: EncodedColumn): Data.Field { ? row => mask[row] === Column.ValueKind.Present ? data[row] : '' : row => data[row]; - const int: Data.Field['int'] = isNumeric + const int: Data.CifField['int'] = isNumeric ? row => data[row] : row => { const v = data[row]; return fastParseInt(v, 0, v.length); }; - const float: Data.Field['float'] = isNumeric + const float: Data.CifField['float'] = isNumeric ? row => data[row] : row => { const v = data[row]; return fastParseFloat(v, 0, v.length); }; - const valueKind: Data.Field['valueKind'] = mask + const valueKind: Data.CifField['valueKind'] = mask ? row => mask[row] : row => Column.ValueKind.Present; diff --git a/src/mol-io/reader/cif/binary/parser.ts b/src/mol-io/reader/cif/binary/parser.ts index bd53cd0ad..8a5f0ea1a 100644 --- a/src/mol-io/reader/cif/binary/parser.ts +++ b/src/mol-io/reader/cif/binary/parser.ts @@ -18,7 +18,7 @@ function checkVersions(min: number[], current: number[]) { return true; } -function Category(data: EncodedCategory): Data.Category { +function Category(data: EncodedCategory): Data.CifCategory { const map = Object.create(null); const cache = Object.create(null); for (const col of data.columns) map[col.name] = col; @@ -37,22 +37,22 @@ function Category(data: EncodedCategory): Data.Category { } export default function parse(data: Uint8Array) { - return Task.create<Result<Data.File>>('Parse BinaryCIF', async ctx => { + return Task.create<Result<Data.CifFile>>('Parse BinaryCIF', async ctx => { const minVersion = [0, 3]; try { const unpacked = decodeMsgPack(data) as EncodedFile; if (!checkVersions(minVersion, unpacked.version.match(/(\d)\.(\d)\.\d/)!.slice(1).map(v => +v))) { - return Result.error<Data.File>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`); + return Result.error<Data.CifFile>(`Unsupported format version. Current ${unpacked.version}, required ${minVersion.join('.')}.`); } - const file = Data.File(unpacked.dataBlocks.map(block => { + const file = Data.CifFile(unpacked.dataBlocks.map(block => { const cats = Object.create(null); for (const cat of block.categories) cats[cat.name.substr(1)] = Category(cat); - return Data.Block(block.categories.map(c => c.name.substr(1)), cats, block.header); + return Data.CifBlock(block.categories.map(c => c.name.substr(1)), cats, block.header); })); return Result.success(file); } catch (e) { - return Result.error<Data.File>('' + e); + return Result.error<Data.CifFile>('' + e); } }) } \ No newline at end of file diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index 4a090d351..30bc1c43e 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -8,49 +8,49 @@ import { Column } from 'mol-data/db' import { Tensor } from 'mol-math/linear-algebra' -export interface File { +export interface CifFile { readonly name?: string, - readonly blocks: ReadonlyArray<Block> + readonly blocks: ReadonlyArray<CifBlock> } -export function File(blocks: ArrayLike<Block>, name?: string): File { +export function CifFile(blocks: ArrayLike<CifBlock>, name?: string): CifFile { return { name, blocks: blocks as any }; } -export interface Frame { +export interface CifFrame { readonly header: string, // Category names stored separately so that the ordering can be preserved. readonly categoryNames: ReadonlyArray<string>, - readonly categories: Categories + readonly categories: CifCategories } -export interface Block extends Frame { - readonly saveFrames: Frame[] +export interface CifBlock extends CifFrame { + readonly saveFrames: CifFrame[] } -export function Block(categoryNames: string[], categories: Categories, header: string, saveFrames: Frame[] = []): Block { +export function CifBlock(categoryNames: string[], categories: CifCategories, header: string, saveFrames: CifFrame[] = []): CifBlock { return { categoryNames, header, categories, saveFrames }; } -export function SafeFrame(categoryNames: string[], categories: Categories, header: string): Frame { +export function CifSafeFrame(categoryNames: string[], categories: CifCategories, header: string): CifFrame { return { categoryNames, header, categories }; } -export type Categories = { readonly [name: string]: Category } +export type CifCategories = { readonly [name: string]: CifCategory } -export interface Category { +export interface CifCategory { readonly rowCount: number, readonly name: string, readonly fieldNames: ReadonlyArray<string>, - getField(name: string): Field | undefined + getField(name: string): CifField | undefined } -export function Category(name: string, rowCount: number, fieldNames: string[], fields: { [name: string]: Field }): Category { +export function CifCategory(name: string, rowCount: number, fieldNames: string[], fields: { [name: string]: CifField }): CifCategory { return { rowCount, name, fieldNames: [...fieldNames], getField(name) { return fields[name]; } }; } -export namespace Category { - export function empty(name: string): Category { +export namespace CifCategory { + export function empty(name: string): CifCategory { return { rowCount: 0, name, fieldNames: [], getField(name: string) { return void 0; } }; }; } @@ -60,7 +60,7 @@ export namespace Category { * Always implement without using "this." in any of the interface functions. * This is to ensure that the functions can invoked without having to "bind" them. */ -export interface Field { +export interface CifField { readonly '@array': ArrayLike<any> | undefined readonly isDefined: boolean, readonly rowCount: number, @@ -78,7 +78,7 @@ export interface Field { toFloatArray(params?: Column.ToArrayParams<number>): ReadonlyArray<number> } -export function getTensor(category: Category, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data { +export function getTensor(category: CifCategory, field: string, space: Tensor.Space, row: number, zeroIndexed: boolean): Tensor.Data { const ret = space.create(); const offset = zeroIndexed ? 0 : 1; diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index c7710132f..8efa52ab4 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/src/mol-io/reader/cif/schema.ts @@ -10,7 +10,7 @@ import { Tensor } from 'mol-math/linear-algebra' import { arrayEqual } from 'mol-util' import * as Data from './data-model' -export function toDatabaseCollection<Schema extends Database.Schema>(schema: Schema, file: Data.File): DatabaseCollection<Schema> { +export function toDatabaseCollection<Schema extends Database.Schema>(schema: Schema, file: Data.CifFile): DatabaseCollection<Schema> { const dbc: DatabaseCollection<Schema> = {} for (const data of file.blocks) { dbc[data.header] = toDatabase(schema, data) @@ -18,15 +18,15 @@ export function toDatabaseCollection<Schema extends Database.Schema>(schema: Sch return dbc; } -export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.Frame): Frame { +export function toDatabase<Schema extends Database.Schema, Frame extends Database<Schema> = Database<Schema>>(schema: Schema, frame: Data.CifFrame): Frame { return createDatabase(schema, frame) as Frame; } -export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.Category): R { +export function toTable<Schema extends Table.Schema, R extends Table<Schema> = Table<Schema>>(schema: Schema, category: Data.CifCategory): R { return new CategoryTable(category, schema, true) as any; } -type ColumnCtor = (field: Data.Field, category: Data.Category, key: string) => Column<any> +type ColumnCtor = (field: Data.CifField, category: Data.CifCategory, key: string) => Column<any> function getColumnCtor(t: Column.Schema): ColumnCtor { switch (t.valueType) { @@ -38,7 +38,7 @@ function getColumnCtor(t: Column.Schema): ColumnCtor { } } -function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> { +function createColumn<T>(schema: Column.Schema, field: Data.CifField, value: (row: number) => T, toArray: Column<T>['toArray']): Column<T> { return { schema, '@array': field['@array'], @@ -51,7 +51,7 @@ function createColumn<T>(schema: Column.Schema, field: Data.Field, value: (row: }; } -function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.Category, key: string): Column<(number|string)[]> { +function createListColumn<T extends number|string>(schema: Column.Schema.List<T>, category: Data.CifCategory, key: string): Column<(number|string)[]> { const separator = schema.separator; const itemParse = schema.itemParse; @@ -71,7 +71,7 @@ function createListColumn<T extends number|string>(schema: Column.Schema.List<T> }; } -function createTensorColumn(schema: Column.Schema.Tensor, category: Data.Category, key: string): Column<Tensor.Data> { +function createTensorColumn(schema: Column.Schema.Tensor, category: Data.CifCategory, key: string): Column<Tensor.Data> { const space = schema.space; const zeroOffset = category.fieldNames.indexOf(`${key}[0]`) >= 0; const fst = zeroOffset ? 0 : 1; @@ -105,7 +105,7 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name _schema: any; [k: string]: any; - constructor(category: Data.Category, schema: Table.Schema, public _isDefined: boolean) { + constructor(category: Data.CifCategory, schema: Table.Schema, public _isDefined: boolean) { const fieldKeys = Object.keys(schema); this._rowCount = category.rowCount; this._columns = fieldKeys; @@ -134,7 +134,7 @@ class CategoryTable implements Table<any> { // tslint:disable-line:class-name } } -function createDatabase(schema: Database.Schema, frame: Data.Frame): Database<any> { +function createDatabase(schema: Database.Schema, frame: Data.CifFrame): Database<any> { const tables = Object.create(null); for (const k of Object.keys(schema)) { tables[k] = createTable(k, (schema as any)[k], frame); @@ -142,7 +142,7 @@ function createDatabase(schema: Database.Schema, frame: Data.Frame): Database<an return Database.ofTables(frame.header, schema, tables); } -function createTable(key: string, schema: Table.Schema, frame: Data.Frame) { +function createTable(key: string, schema: Table.Schema, frame: Data.CifFrame) { const cat = frame.categories[key]; - return new CategoryTable(cat || Data.Category.empty(key), schema, !!cat); + return new CategoryTable(cat || Data.CifCategory.empty(key), schema, !!cat); } \ No newline at end of file diff --git a/src/mol-io/reader/cif/text/field.ts b/src/mol-io/reader/cif/text/field.ts index 8b6daa2b8..32af80a85 100644 --- a/src/mol-io/reader/cif/text/field.ts +++ b/src/mol-io/reader/cif/text/field.ts @@ -11,24 +11,24 @@ import { Tokens } from '../../common/text/tokenizer' import * as Data from '../data-model' import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../common/text/number-parser' -export default function CifTextField(tokens: Tokens, rowCount: number): Data.Field { +export default function CifTextField(tokens: Tokens, rowCount: number): Data.CifField { const { data, indices } = tokens; - const str: Data.Field['str'] = row => { + const str: Data.CifField['str'] = row => { const ret = data.substring(indices[2 * row], indices[2 * row + 1]); if (ret === '.' || ret === '?') return ''; return ret; }; - const int: Data.Field['int'] = row => { + const int: Data.CifField['int'] = row => { return fastParseInt(data, indices[2 * row], indices[2 * row + 1]) || 0; }; - const float: Data.Field['float'] = row => { + const float: Data.CifField['float'] = row => { return fastParseFloat(data, indices[2 * row], indices[2 * row + 1]) || 0; }; - const valueKind: Data.Field['valueKind'] = row => { + const valueKind: Data.CifField['valueKind'] = row => { const s = indices[2 * row]; if (indices[2 * row + 1] - s !== 1) return Column.ValueKind.Present; const v = data.charCodeAt(s); diff --git a/src/mol-io/reader/cif/text/parser.ts b/src/mol-io/reader/cif/text/parser.ts index bbd4b39c8..610c7b0f8 100644 --- a/src/mol-io/reader/cif/text/parser.ts +++ b/src/mol-io/reader/cif/text/parser.ts @@ -413,7 +413,7 @@ interface CifCategoryResult { type FrameContext = { categoryNames: string[], - categories: { [name: string]: Data.Category } + categories: { [name: string]: Data.CifCategory } } function FrameContext(): FrameContext { @@ -451,7 +451,7 @@ function handleSingle(tokenizer: TokenizerState, ctx: FrameContext): CifCategory } const catName = name.substr(1); - ctx.categories[catName] = Data.Category(catName, 1, fieldNames, fields); + ctx.categories[catName] = Data.CifCategory(catName, 1, fieldNames, fields); ctx.categoryNames.push(catName); return { @@ -533,7 +533,7 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise } const catName = name.substr(1); - ctx.categories[catName] = Data.Category(catName, rowCount, fieldNames, fields); + ctx.categories[catName] = Data.CifCategory(catName, rowCount, fieldNames, fields); ctx.categoryNames.push(catName); return { @@ -547,13 +547,13 @@ async function handleLoop(tokenizer: TokenizerState, ctx: FrameContext): Promise * Creates an error result. */ function error(line: number, message: string) { - return Result.error<Data.File>(message, line); + return Result.error<Data.CifFile>(message, line); } /** * Creates a data result. */ -function result(data: Data.File) { +function result(data: Data.CifFile) { return Result.success(data); } @@ -563,7 +563,7 @@ function result(data: Data.File) { * @returns CifParserResult wrapper of the result. */ async function parseInternal(data: string, runtimeCtx: RuntimeContext) { - const dataBlocks: Data.Block[] = []; + const dataBlocks: Data.CifBlock[] = []; const tokenizer = createTokenizer(data, runtimeCtx); let blockHeader = ''; @@ -572,9 +572,9 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) { let inSaveFrame = false; // the next three initial values are never used in valid files - let saveFrames: Data.Frame[] = []; + let saveFrames: Data.CifFrame[] = []; let saveCtx = FrameContext(); - let saveFrame: Data.Frame = Data.SafeFrame(saveCtx.categoryNames, saveCtx.categories, ''); + let saveFrame: Data.CifFrame = Data.CifSafeFrame(saveCtx.categoryNames, saveCtx.categories, ''); runtimeCtx.update({ message: 'Parsing...', current: 0, max: data.length }); @@ -588,7 +588,7 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) { return error(tokenizer.lineNumber, 'Unexpected data block inside a save frame.'); } if (blockCtx.categoryNames.length > 0) { - dataBlocks.push(Data.Block(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames)); + dataBlocks.push(Data.CifBlock(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames)); } blockHeader = data.substring(tokenizer.tokenStart + 5, tokenizer.tokenEnd); blockCtx = FrameContext(); @@ -609,7 +609,7 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) { inSaveFrame = true; const safeHeader = data.substring(tokenizer.tokenStart + 5, tokenizer.tokenEnd); saveCtx = FrameContext(); - saveFrame = Data.SafeFrame(saveCtx.categoryNames, saveCtx.categories, safeHeader); + saveFrame = Data.CifSafeFrame(saveCtx.categoryNames, saveCtx.categories, safeHeader); } moveNext(tokenizer); // Loop @@ -636,14 +636,14 @@ async function parseInternal(data: string, runtimeCtx: RuntimeContext) { } if (blockCtx.categoryNames.length > 0) { - dataBlocks.push(Data.Block(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames)); + dataBlocks.push(Data.CifBlock(blockCtx.categoryNames, blockCtx.categories, blockHeader, saveFrames)); } - return result(Data.File(dataBlocks)); + return result(Data.CifFile(dataBlocks)); } export default function parse(data: string) { - return Task.create<Result<Data.File>>('Parse CIF', async ctx => { + return Task.create<Result<Data.CifFile>>('Parse CIF', async ctx => { return await parseInternal(data, ctx); }); } \ No newline at end of file diff --git a/src/mol-io/reader/csv/data-model.ts b/src/mol-io/reader/csv/data-model.ts index 86f50f077..401c7aa2d 100644 --- a/src/mol-io/reader/csv/data-model.ts +++ b/src/mol-io/reader/csv/data-model.ts @@ -4,32 +4,32 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { Field as Column } from '../cif/data-model' +import { CifField as CsvColumn } from '../cif/data-model' -export { Column } +export { CsvColumn } -export interface File { +export interface CsvFile { readonly name?: string, - readonly table: Table + readonly table: CsvTable } -export function File(table: Table, name?: string): File { +export function CsvFile(table: CsvTable, name?: string): CsvFile { return { name, table }; } -export interface Table { +export interface CsvTable { readonly rowCount: number, readonly columnNames: ReadonlyArray<string>, - getColumn(name: string): Column | undefined + getColumn(name: string): CsvColumn | undefined } -export function Table(rowCount: number, columnNames: string[], columns: Columns): Table { +export function CsvTable(rowCount: number, columnNames: string[], columns: CsvColumns): CsvTable { return { rowCount, columnNames: [...columnNames], getColumn(name) { return columns[name]; } }; } -export type Columns = { [name: string]: Column } +export type CsvColumns = { [name: string]: CsvColumn } -// export namespace Table { +// export namespace CsvTable { // export function empty(name: string): Table { // return { rowCount: 0, name, fieldNames: [], getColumn(name: string) { return void 0; } }; // }; diff --git a/src/mol-io/reader/csv/parser.ts b/src/mol-io/reader/csv/parser.ts index 66a7a9e17..d5bc68535 100644 --- a/src/mol-io/reader/csv/parser.ts +++ b/src/mol-io/reader/csv/parser.ts @@ -248,24 +248,24 @@ function init(state: State) { } } -async function handleRecords(state: State): Promise<Data.Table> { +async function handleRecords(state: State): Promise<Data.CsvTable> { init(state) await readRecordsChunks(state) - const columns: Data.Columns = Object.create(null); + const columns: Data.CsvColumns = Object.create(null); for (let i = 0; i < state.columnCount; ++i) { columns[state.columnNames[i]] = Field(state.tokens[i], state.recordCount); } - return Data.Table(state.recordCount, state.columnNames, columns) + return Data.CsvTable(state.recordCount, state.columnNames, columns) } -async function parseInternal(data: string, ctx: RuntimeContext, opts: CsvOptions): Promise<Result<Data.File>> { +async function parseInternal(data: string, ctx: RuntimeContext, opts: CsvOptions): Promise<Result<Data.CsvFile>> { const state = State(data, ctx, opts); ctx.update({ message: 'Parsing...', current: 0, max: data.length }); const table = await handleRecords(state) - const result = Data.File(table) + const result = Data.CsvFile(table) return Result.success(result); } @@ -278,7 +278,7 @@ interface CsvOptions { export function parse(data: string, opts?: Partial<CsvOptions>) { const completeOpts = Object.assign({}, { quote: '"', comment: '#', delimiter: ',', noColumnNames: false }, opts) - return Task.create<Result<Data.File>>('Parse CSV', async ctx => { + return Task.create<Result<Data.CsvFile>>('Parse CSV', async ctx => { return await parseInternal(data, ctx, completeOpts); }); } diff --git a/src/mol-io/reader/gro/parser.ts b/src/mol-io/reader/gro/parser.ts index eee90135b..afd469111 100644 --- a/src/mol-io/reader/gro/parser.ts +++ b/src/mol-io/reader/gro/parser.ts @@ -14,12 +14,12 @@ import { Task, RuntimeContext } from 'mol-task' interface State { tokenizer: Tokenizer, - header: Schema.Header, + header: Schema.GroHeader, numberOfAtoms: number, runtimeCtx: RuntimeContext } -function createEmptyHeader(): Schema.Header { +function createEmptyHeader(): Schema.GroHeader { return { title: '', timeInPs: 0, @@ -88,7 +88,7 @@ function handleNumberOfAtoms(state: State) { * position (in nm, x y z in 3 columns, each 8 positions with 3 decimal places) * velocity (in nm/ps (or km/s), x y z in 3 columns, each 8 positions with 4 decimal places) */ -async function handleAtoms(state: State): Promise<Schema.Atoms> { +async function handleAtoms(state: State): Promise<Schema.GroAtoms> { const { tokenizer, numberOfAtoms } = state; const lines = await Tokenizer.readLinesAsync(tokenizer, numberOfAtoms, state.runtimeCtx, 100000); @@ -137,11 +137,11 @@ function handleBoxVectors(state: State) { state.header.box = [+values[0], +values[1], +values[2]]; } -async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.File>> { +async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.GroFile>> { const tokenizer = Tokenizer(data); ctx.update({ message: 'Parsing...', current: 0, max: data.length }); - const structures: Schema.Structure[] = []; + const structures: Schema.GroStructure[] = []; while (tokenizer.position < data.length) { const state = State(tokenizer, ctx); handleTitleString(state); @@ -151,12 +151,12 @@ async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result< structures.push({ header: state.header, atoms }); } - const result: Schema.File = { structures }; + const result: Schema.GroFile = { structures }; return Result.success(result); } export function parse(data: string) { - return Task.create<Result<Schema.File>>('Parse GRO', async ctx => { + return Task.create<Result<Schema.GroFile>>('Parse GRO', async ctx => { return await parseInternal(data, ctx); }); } diff --git a/src/mol-io/reader/gro/schema.d.ts b/src/mol-io/reader/gro/schema.d.ts index 9a0b1ca55..ddc84303d 100644 --- a/src/mol-io/reader/gro/schema.d.ts +++ b/src/mol-io/reader/gro/schema.d.ts @@ -7,7 +7,7 @@ import { Column } from 'mol-data/db' -export interface Header { +export interface GroHeader { title: string, timeInPs: number, /** number of decimal places */ @@ -16,7 +16,7 @@ export interface Header { box: [number, number, number] } -export interface Atoms { +export interface GroAtoms { count: number, residueNumber: Column<number>, residueName: Column<string>, @@ -30,11 +30,11 @@ export interface Atoms { vz: Column<number> } -export interface Structure { - header: Readonly<Header>, - atoms: Readonly<Atoms> +export interface GroStructure { + header: Readonly<GroHeader>, + atoms: Readonly<GroAtoms> } -export interface File { - structures: Structure[] +export interface GroFile { + structures: GroStructure[] } \ No newline at end of file diff --git a/src/mol-io/reader/mol2/parser.ts b/src/mol-io/reader/mol2/parser.ts index 32126acb9..297e15026 100644 --- a/src/mol-io/reader/mol2/parser.ts +++ b/src/mol-io/reader/mol2/parser.ts @@ -22,11 +22,11 @@ const { skipWhitespace, eatValue, markLine, getTokenString, readLine } = Tokeniz interface State { tokenizer: Tokenizer, - molecule: Schema.Molecule, + molecule: Schema.Mol2Molecule, runtimeCtx: RuntimeContext } -function createEmptyMolecule(): Schema.Molecule { +function createEmptyMolecule(): Schema.Mol2Molecule { return { mol_name: '', num_atoms: 0, @@ -93,7 +93,7 @@ function isStatus_bit(aString: String): Boolean { return false; } -async function handleAtoms(state: State): Promise<Schema.Atoms> { +async function handleAtoms(state: State): Promise<Schema.Mol2Atoms> { const { tokenizer, molecule } = state; let hasSubst_id = false; let hasSubst_name = false; @@ -239,7 +239,7 @@ async function handleAtoms(state: State): Promise<Schema.Atoms> { return ret; } -async function handleBonds(state: State): Promise<Schema.Bonds> { +async function handleBonds(state: State): Promise<Schema.Mol2Bonds> { const { tokenizer, molecule } = state; let hasStatus_bit = false; @@ -324,11 +324,11 @@ async function handleBonds(state: State): Promise<Schema.Bonds> { return ret; } -async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.File>> { +async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result<Schema.Mol2File>> { const tokenizer = Tokenizer(data); ctx.update({ message: 'Parsing...', current: 0, max: data.length }); - const structures: Schema.Structure[] = []; + const structures: Schema.Mol2Structure[] = []; while (tokenizer.position < data.length) { const state = State(tokenizer, ctx); handleMolecule(state); @@ -337,12 +337,12 @@ async function parseInternal(data: string, ctx: RuntimeContext): Promise<Result< structures.push({ molecule: state.molecule, atoms, bonds }); } - const result: Schema.File = { structures }; + const result: Schema.Mol2File = { structures }; return Result.success(result); } export function parse(data: string) { - return Task.create<Result<Schema.File>>('Parse MOL2', async ctx => { + return Task.create<Result<Schema.Mol2File>>('Parse MOL2', async ctx => { return await parseInternal(data, ctx); }); } diff --git a/src/mol-io/reader/mol2/schema.d.ts b/src/mol-io/reader/mol2/schema.d.ts index 374b52d0d..21205a267 100644 --- a/src/mol-io/reader/mol2/schema.d.ts +++ b/src/mol-io/reader/mol2/schema.d.ts @@ -14,7 +14,7 @@ import { Column } from 'mol-data/db' // // note that the format is not a fixed column format but white space separated -export interface Molecule { +export interface Mol2Molecule { mol_name: string num_atoms: number num_bonds: number @@ -27,7 +27,7 @@ export interface Molecule { mol_comment: string } -export interface Atoms { +export interface Mol2Atoms { count: number, atom_id: Column<number>, @@ -44,7 +44,7 @@ export interface Atoms { status_bit: Column<string> } -export interface Bonds { +export interface Mol2Bonds { count: number, bond_id: Column<number>, @@ -56,12 +56,12 @@ export interface Bonds { status_bits: Column<string> } -export interface Structure { - molecule: Readonly<Molecule>, - atoms: Readonly<Atoms>, - bonds: Readonly<Bonds> +export interface Mol2Structure { + molecule: Readonly<Mol2Molecule>, + atoms: Readonly<Mol2Atoms>, + bonds: Readonly<Mol2Bonds> } -export interface File { - structures: Structure[] +export interface Mol2File { + structures: Mol2Structure[] } \ No newline at end of file diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts index d463c30d7..59f96c2a7 100644 --- a/src/mol-model/structure/model/format.ts +++ b/src/mol-model/structure/model/format.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { File as GroFile } from 'mol-io/reader/gro/schema' +import { GroFile as GroFile } from 'mol-io/reader/gro/schema' import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif' type Format = diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts index cb868ff96..6e365bb35 100644 --- a/src/mol-model/structure/model/formats/gro.ts +++ b/src/mol-model/structure/model/formats/gro.ts @@ -7,7 +7,7 @@ import UUID from 'mol-util/uuid' import { Column, Table } from 'mol-data/db' import { Interval, Segmentation } from 'mol-data/int' -import { Atoms } from 'mol-io/reader/gro/schema' +import { GroAtoms } from 'mol-io/reader/gro/schema' import Format from '../format' import Model from '../model' import * as Hierarchy from '../properties/hierarchy' @@ -24,7 +24,7 @@ import { Entities } from '../properties/common'; type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> } -function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) { +function findHierarchyOffsets(atomsData: GroAtoms, bounds: Interval) { const start = Interval.start(bounds), end = Interval.end(bounds); const residues = [start], chains = [start]; @@ -44,7 +44,7 @@ function guessElementSymbol (value: string) { return ElementSymbol(guessElement(value)); } -function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Hierarchy.Data { +function createHierarchyData(atomsData: GroAtoms, offsets: HierarchyOffsets): Hierarchy.Data { console.log(atomsData.atomName) const atoms = Table.ofColumns(Hierarchy.AtomsSchema, { type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }), @@ -77,7 +77,7 @@ function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Hiera return { atoms, residues, chains }; } -function getConformation(atoms: Atoms): AtomSiteConformation { +function getConformation(atoms: GroAtoms): AtomSiteConformation { return { id: UUID.create(), atomId: atoms.atomNumber, diff --git a/src/mol-util/download.ts b/src/mol-util/download.ts new file mode 100644 index 000000000..2e820378c --- /dev/null +++ b/src/mol-util/download.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +function openUrl (url: string) { + const opened = window.open(url, '_blank') + if (!opened) { + window.location.href = url + } +} + +export function download (data: Blob|string, downloadName = 'download') { + // using ideas from https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js + + if (!data) return + + const ua = window.navigator.userAgent + const isSafari = /Safari/i.test(ua) + const isChromeIos = /CriOS\/[\d]+/.test(ua) + + const a = document.createElement('a') + + function open (str: string) { + openUrl(isChromeIos ? str : str.replace(/^data:[^;]*;/, 'data:attachment/file;')) + } + + if (typeof navigator !== 'undefined' && navigator.msSaveOrOpenBlob) { + // native saveAs in IE 10+ + navigator.msSaveOrOpenBlob(data, downloadName) + } else if ((isSafari || isChromeIos) && FileReader) { + if (data instanceof Blob) { + // no downloading of blob urls in Safari + const reader = new FileReader() + reader.onloadend = () => open(reader.result) + reader.readAsDataURL(data) + } else { + open(data) + } + } else { + let objectUrlCreated = false + if (data instanceof Blob) { + data = URL.createObjectURL(data) + objectUrlCreated = true + } + + if ('download' in a) { + // download link available + a.style.display = 'hidden' + document.body.appendChild(a) + a.href = data + a.download = downloadName + a.target = '_blank' + a.click() + document.body.removeChild(a) + } else { + openUrl(data) + } + + if (objectUrlCreated) { + window.URL.revokeObjectURL(data) + } + } +} \ No newline at end of file diff --git a/src/mol-util/file-info.ts b/src/mol-util/file-info.ts new file mode 100644 index 000000000..193c2091e --- /dev/null +++ b/src/mol-util/file-info.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +/** A File or Blob object or a URL string */ +export type FileInput = File | Blob | string + +// TODO only support compressed files for which uncompression support is available??? +// TODO store globally with decompression plugins? +const compressedExtList = [ 'gz', 'zip' ] + +// TODO store globally with parser plugins? +const binaryExtList = [ 'bcif', 'ccp4', 'dcd' ] + +export interface FileInfo { + path: string + name: string + ext: string + base: string + dir: string + compressed: string | boolean + binary: boolean + protocol: string + query: string + src: FileInput +} + +export function getFileInfo (file: FileInput): FileInfo { + let path: string + let compressed: string|false + let protocol = '' + + if (file instanceof File) { + path = file.name + } else if (file instanceof Blob) { + path = '' + } else { + path = file + } + const queryIndex = path.lastIndexOf('?') + const query = queryIndex !== -1 ? path.substring(queryIndex) : '' + path = path.substring(0, queryIndex === -1 ? path.length : queryIndex) + + const name = path.replace(/^.*[\\/]/, '') + let base = name.substring(0, name.lastIndexOf('.')) + + const nameSplit = name.split('.') + let ext = nameSplit.length > 1 ? (nameSplit.pop() || '').toLowerCase() : '' + + const protocolMatch = path.match(/^(.+):\/\/(.+)$/) + if (protocolMatch) { + protocol = protocolMatch[ 1 ].toLowerCase() + path = protocolMatch[ 2 ] || '' + } + + const dir = path.substring(0, path.lastIndexOf('/') + 1) + + if (compressedExtList.includes(ext)) { + compressed = ext + const n = path.length - ext.length - 1 + ext = (path.substr(0, n).split('.').pop() || '').toLowerCase() + const m = base.length - ext.length - 1 + base = base.substr(0, m) + } else { + compressed = false + } + + const binary = binaryExtList.includes(ext) + + return { path, name, ext, base, dir, compressed, binary, protocol, query, src: file } +} \ No newline at end of file diff --git a/src/mol-util/id-factory.ts b/src/mol-util/id-factory.ts index 1f0a939fc..0de5654ff 100644 --- a/src/mol-util/id-factory.ts +++ b/src/mol-util/id-factory.ts @@ -4,7 +4,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -export function idFactory() { - let _nextId = 0 +export function idFactory(firstId = 0) { + let _nextId = firstId return () => _nextId++ } \ No newline at end of file diff --git a/src/mol-util/index.ts b/src/mol-util/index.ts index 8cb116ef3..94dcad374 100644 --- a/src/mol-util/index.ts +++ b/src/mol-util/index.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> @@ -9,10 +9,16 @@ import BitFlags from './bit-flags' import StringBuilder from './string-builder' import UUID from './uuid' import Mask from './mask' +import { Progress } from 'mol-task'; export * from './value-cell' export { BitFlags, StringBuilder, UUID, Mask } +export function round(n: number, d: number) { + let f = Math.pow(10, d) + return Math.round(f * n) / f +} + export function arrayEqual<T>(arr1: T[], arr2: T[]) { const length = arr1.length if (length !== arr2.length) return false @@ -69,6 +75,111 @@ export function deepEqual(a: any, b: any) { return false } +const hasOwnProperty = Object.prototype.hasOwnProperty; + +export function shallowEqual<T>(a: T, b: T) { + if (!a) { + if (!b) return true; + return false; + } + if (!b) return false; + + let keys = Object.keys(a); + if (Object.keys(b).length !== keys.length) return false; + for (let k of keys) { + if (!hasOwnProperty.call(a, k) || (a as any)[k] !== (b as any)[k]) return false; + } + + return true; +} + export function defaults(value: any, defaultValue: any) { return value !== undefined ? value : defaultValue +} + +export function extend<S, T, U>(object: S, source: T, guard?: U): S & T & U { + let v: any; + + let s = <any>source; + let o = <any>object; + let g = <any>guard; + for (let k of Object.keys(source)) { + v = s[k]; + if (v !== void 0) o[k] = v; + else if (guard) o[k] = g[k]; + } + + if (guard) { + for (let k of Object.keys(guard)) { + v = o[k]; + if (v === void 0) o[k] = g[k]; + } + } + + return <any>object; +} + +export function shallowClone<T>(o: T): T { + return extend({}, o) as T; +} + +function _assign<T>(target: T): T { + for (let s = 1; s < arguments.length; s++) { + let from = arguments[s]; + for (let key of Object.keys(from)) { + if (hasOwnProperty.call(from, key)) { + (target as any)[key] = from[key]; + } + } + } + return target; +} + +export declare function _assignType<T>(o: T, ...from: any[]): T; +export const assign: (<T>(o: T, ...from: any[]) => T) = (Object as any).assign || _assign; + +function _shallowMerge1<T>(source: T, update: T) { + let changed = false; + for (let k of Object.keys(update)) { + if (!hasOwnProperty.call(update, k)) continue; + + if ((update as any)[k] !== (source as any)[k]) { + changed = true; + break; + } + } + + if (!changed) return source; + return assign(shallowClone(source), update); +} + +function _shallowMerge<T>(source: T) { + let ret = source; + + for (let s = 1; s < arguments.length; s++) { + if (!arguments[s]) continue; + ret = _shallowMerge1(source, arguments[s]); + if (ret !== source) { + for (let i = s + 1; i < arguments.length; i++) { + ret = assign(ret, arguments[i]); + } + break; + } + } + return ret; +} + +export const merge: (<T>(source: T, ...rest: Partial<T>[]) => T)= _shallowMerge; + +function padTime(n: number) { return (n < 10 ? '0' : '') + n } +export function formatTime(d: Date) { + const h = d.getHours(), m = d.getMinutes(), s = d.getSeconds(); + return `${h}:${padTime(m)}:${padTime(s)}`; +} + +export function formatProgress(p: Progress) { + const tp = p.root.progress + if (tp.isIndeterminate) return tp.message; + const x = (100 * tp.current / tp.max).toFixed(2); + return `${tp.message} ${x}%`; } \ No newline at end of file diff --git a/src/mol-util/parse-unit.ts b/src/mol-util/parse-unit.ts index 4071eec01..e0e0652b1 100644 --- a/src/mol-util/parse-unit.ts +++ b/src/mol-util/parse-unit.ts @@ -11,6 +11,7 @@ const reUnit = /[\d.\-\+]*\s*(.*)/ +/** Parsing value of, for example, CSS unit strings */ export default function parseUnit(str: string, out: [number, string] = [ 0, '' ]) { str = String(str) const num = parseFloat(str) diff --git a/src/mol-util/performance-monitor.ts b/src/mol-util/performance-monitor.ts new file mode 100644 index 000000000..614b19a29 --- /dev/null +++ b/src/mol-util/performance-monitor.ts @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * Adapted from LiteMol + * Copyright (c) 2016 - now David Sehnal, licensed under Apache 2.0, See LICENSE file for more info. + */ + +import { now } from 'mol-task/util/now' + +export class PerformanceMonitor { + private starts = new Map<string, number>(); + private ends = new Map<string, number>(); + + static currentTime() { + return now(); + } + + start(name: string) { + this.starts.set(name, now()); + } + + end(name: string) { + this.ends.set(name, now()); + } + + static format(t: number) { + if (isNaN(t)) return 'n/a'; + + let h = Math.floor(t / (60 * 60 * 1000)), + m = Math.floor(t / (60 * 1000) % 60), + s = Math.floor(t / 1000 % 60), + ms = Math.floor(t % 1000).toString(); + + while (ms.length < 3) ms = '0' + ms; + + if (h > 0) return `${h}h${m}m${s}.${ms}s`; + if (m > 0) return `${m}m${s}.${ms}s`; + if (s > 0) return `${s}.${ms}s`; + return `${t.toFixed(0)}ms`; + } + + formatTime(name: string) { + return PerformanceMonitor.format(this.time(name)); + } + + formatTimeSum(...names: string[]) { + return PerformanceMonitor.format(this.timeSum(...names)); + } + + /** Returns the time in milliseconds and removes them from the cache. */ + time(name: string): number { + let start = this.starts.get(name)!, end = this.ends.get(name)!; + + this.starts.delete(name); + this.ends.delete(name); + + return end - start; + } + + timeSum(...names: string[]) { + let t = 0; + for (let m of names.map(n => this.ends.get(n)! - this.starts.get(n)!)) t += m; + return t; + } +} \ No newline at end of file diff --git a/src/mol-util/read.ts b/src/mol-util/read.ts new file mode 100644 index 000000000..b88abd9eb --- /dev/null +++ b/src/mol-util/read.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +export const readFileAs = (file: File, isBinary = false) => { + const fileReader = new FileReader() + return new Promise<string | Uint8Array>((resolve, reject) => { + fileReader.onerror = () => { + fileReader.abort() + reject(new DOMException('Error parsing file.')) + } + fileReader.onload = () => { + resolve(isBinary ? new Uint8Array(fileReader.result) : fileReader.result) + } + if (isBinary) { + fileReader.readAsArrayBuffer(file) + } else { + fileReader.readAsText(file) + } + }) +} + +export function readFileAsText(file: File) { + return readFileAs(file, false) as Promise<string> +} + +export function readFileAsBuffer(file: File) { + return readFileAs(file, true) as Promise<Uint8Array> +} + +export async function readUrlAs(url: string, isBinary: boolean) { + const response = await fetch(url); + return isBinary ? new Uint8Array(await response.arrayBuffer()) : await response.text(); +} + +export function readUrlAsText(url: string) { + return readUrlAs(url, false) as Promise<string> +} + +export function readUrlAsBuffer(url: string) { + return readUrlAs(url, true) as Promise<Uint8Array> +} \ No newline at end of file diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts new file mode 100644 index 000000000..fbc355664 --- /dev/null +++ b/src/mol-view/stage.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import Viewer from 'mol-view/viewer' +import { StateContext } from './state/context'; +import { Progress } from 'mol-task'; +import { MmcifUrlToSpacefill } from './state/transform'; +import { UrlEntity } from './state/entity'; +import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; + +// export const ColorTheme = { +// 'atom-index': {}, +// 'chain-id': {}, +// 'element-symbol': {}, +// 'instance-index': {}, +// 'uniform': {} +// } +// export type ColorTheme = keyof typeof ColorTheme + +const spacefillProps: SpacefillProps = { + doubleSided: true, + detail: 2, + colorTheme: { name: 'element-symbol' } +} + +export class Stage { + viewer: Viewer + ctx = new StateContext(Progress.format) + + constructor() { + + } + + async initRenderer (canvas: HTMLCanvasElement, container: HTMLDivElement) { + this.viewer = Viewer.create(canvas, container) + this.viewer.animate() + this.ctx.viewer = this.viewer + this.loadPdbid('1crn') + } + + async loadPdbid (pdbid: string) { + const urlEntity = UrlEntity.ofUrl(this.ctx, `https://files.rcsb.org/download/${pdbid}.cif`) + MmcifUrlToSpacefill.apply(this.ctx, urlEntity, spacefillProps) + } + + dispose () { + // TODO + } +} \ No newline at end of file diff --git a/src/mol-view/state/context.ts b/src/mol-view/state/context.ts new file mode 100644 index 000000000..2893a5e37 --- /dev/null +++ b/src/mol-view/state/context.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { BehaviorSubject } from 'rxjs'; +import { UUID } from 'mol-util' +import { AnyEntity } from './entity'; +import Viewer from '../viewer'; +import { Progress } from 'mol-task'; + +// TODO +export type StateTree = {} + +export class StateContext { + id = UUID.create() + change = new BehaviorSubject(0) + + tree: StateTree = {} + entities: Set<AnyEntity> = new Set() + + viewer: Viewer + + constructor(readonly log: (p: Progress) => void) { + + } +} diff --git a/src/mol-view/state/entity.ts b/src/mol-view/state/entity.ts new file mode 100644 index 000000000..b77e5b822 --- /dev/null +++ b/src/mol-view/state/entity.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { readFileAs, readUrlAs } from 'mol-util/read' +import { idFactory } from 'mol-util/id-factory' +import { StateContext } from './context'; +import { getFileInfo } from 'mol-util/file-info'; +import { CifFile } from 'mol-io/reader/cif'; +import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'; +import { Model, Structure } from 'mol-model/structure'; +import { StructureRepresentation } from 'mol-geo/representation/structure'; +import { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; + +const getNextId = idFactory(1) + +export interface StateEntity<T, K extends string> { + id: number + kind: K + value: T +} +export namespace StateEntity { + export function create<T, K extends string>(ctx: StateContext, kind: K, value: T): StateEntity<T, K> { + const entity = { id: getNextId(), kind, value } + ctx.entities.add(entity) + ctx.change.next(ctx.change.getValue() + 1) + return entity + } +} + +export type AnyEntity = StateEntity<any, any> + +export const RootEntity: StateEntity<null, 'root'> = { id: 0, kind: 'root', value: null } + +export interface UrlProps { + url: string + name: string + type: string + getData: () => Promise<string | Uint8Array> +} + +export type UrlEntity = StateEntity<UrlProps, 'url'> +export namespace UrlEntity { + export function ofUrl(ctx: StateContext, url: string, isBinary?: boolean): UrlEntity { + const { name, ext: type, compressed, binary } = getFileInfo(url) + return StateEntity.create(ctx, 'url', { + url, name, type, + getData: () => readUrlAs(url, isBinary || !!compressed || binary) + }) + } +} + +export interface FileProps { + name: string + type: string + getData: () => Promise<string | Uint8Array> +} + +export type FileEntity = StateEntity<FileProps, 'file'> +export namespace FileEntity { + export function ofFile(ctx: StateContext, file: File, isBinary?: boolean): FileEntity { + const { name, ext: type, compressed, binary } = getFileInfo(file) + return StateEntity.create(ctx, 'file', { + name, type, + getData: () => readFileAs(file, isBinary || !!compressed || binary) + }) + } +} + +export interface DataProps { + type: string + data: string | Uint8Array +} + +export type DataEntity = StateEntity<DataProps, 'data'> +export namespace DataEntity { + export function ofData<T>(ctx: StateContext, data: string | Uint8Array, type: string): DataEntity { + return StateEntity.create(ctx, 'data', { + type, + data + }) + } +} + +export type CifEntity = StateEntity<CifFile, 'cif'> +export namespace CifEntity { + export function ofCifFile(ctx: StateContext, file: CifFile): CifEntity { + return StateEntity.create(ctx, 'cif', file) + } +} + +export type MmcifEntity = StateEntity<mmCIF_Database, 'mmcif'> +export namespace MmcifEntity { + export function ofMmcifDb(ctx: StateContext, db: mmCIF_Database): MmcifEntity { + return StateEntity.create(ctx, 'mmcif', db) + } +} + +export type ModelEntity = StateEntity<ReadonlyArray<Model>, 'model'> +export namespace ModelEntity { + export function ofModels(ctx: StateContext, models: ReadonlyArray<Model>): ModelEntity { + return StateEntity.create(ctx, 'model', models) + } +} + +export type StructureEntity = StateEntity<Structure, 'structure'> +export namespace StructureEntity { + export function ofStructure(ctx: StateContext, structure: Structure): StructureEntity { + return StateEntity.create(ctx, 'structure', structure) + } +} + +export type SpacefillEntity = StateEntity<StructureRepresentation<SpacefillProps>, 'spacefill'> +export namespace SpacefillEntity { + export function ofRepr(ctx: StateContext, repr: StructureRepresentation<SpacefillProps>): SpacefillEntity { + return StateEntity.create(ctx, 'spacefill', repr ) + } +} \ No newline at end of file diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts new file mode 100644 index 000000000..fe0200790 --- /dev/null +++ b/src/mol-view/state/transform.ts @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import CIF from 'mol-io/reader/cif' +import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity } from './entity'; +import { Run } from 'mol-task'; +import { Model, Structure, Symmetry } from 'mol-model/structure'; + +import { StateContext } from './context'; +import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; +import { StructureRepresentation } from 'mol-geo/representation/structure'; + +type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O> + +export interface StateTransform<I extends AnyEntity, O extends AnyEntity, P extends {}> { + inputKind: I['kind'] + outputKind: O['kind'] + kind: string + apply: transformer<I, O, P> +} + +export namespace StateTransform { + export function create<I extends AnyEntity, O extends AnyEntity, P extends {}>(inputKind: I['kind'], outputKind: O['kind'], kind: string, transformer: transformer<I, O, P>) { + return { inputKind, outputKind, kind, apply: transformer } + } +} + +export type AnyTransform = StateTransform<AnyEntity, AnyEntity, {}> + +export type UrlToData = StateTransform<UrlEntity, DataEntity, {}> +export const UrlToData: UrlToData = StateTransform.create('url', 'data', 'url-to-data', + async function (ctx: StateContext, urlEntity: UrlEntity) { + return DataEntity.ofData(ctx, await urlEntity.value.getData(), urlEntity.value.type) + }) + +export type FileToData = StateTransform<FileEntity, DataEntity, {}> +export const FileToData: FileToData = StateTransform.create('file', 'data', 'file-to-data', + async function (ctx: StateContext, fileEntity: FileEntity) { + return DataEntity.ofData(ctx, await fileEntity.value.getData(), fileEntity.value.type) + }) + +export type DataToCif = StateTransform<DataEntity, CifEntity, {}> +export const DataToCif: DataToCif = StateTransform.create('data', 'cif', 'data-to-cif', + async function (ctx: StateContext, dataEntity: DataEntity) { + const comp = CIF.parse(dataEntity.value.data) + const parsed = await Run(comp, ctx.log) + if (parsed.isError) throw parsed + return CifEntity.ofCifFile(ctx, parsed.result) + }) + +export type CifToMmcif = StateTransform<CifEntity, MmcifEntity, {}> +export const CifToMmcif: CifToMmcif = StateTransform.create('cif', 'mmcif', 'cif-to-mmcif', + async function (ctx: StateContext, cifEntity: CifEntity) { + return MmcifEntity.ofMmcifDb(ctx, CIF.schema.mmCIF(cifEntity.value.blocks[0])) + }) + +export type MmcifToModel = StateTransform<MmcifEntity, ModelEntity, {}> +export const MmcifToModel: MmcifToModel = StateTransform.create('mmcif', 'model', 'mmcif-to-model', + async function (ctx: StateContext, mmcifEntity: MmcifEntity) { + return ModelEntity.ofModels(ctx, Model.create({ kind: 'mmCIF', data: mmcifEntity.value })) + }) + +export interface StructureProps { + assembly?: string +} + +export type ModelToStructure = StateTransform<ModelEntity, StructureEntity, StructureProps> +export const ModelToStructure: ModelToStructure = StateTransform.create('model', 'structure', 'model-to-structure', + async function (ctx: StateContext, modelEntity: ModelEntity, props: StructureProps = {}) { + const model = modelEntity.value[0] + const assembly = props.assembly + let structure: Structure + const assemblies = model.symmetry.assemblies + if (assemblies.length) { + structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), ctx.log) + } else { + structure = Structure.ofModel(model) + } + return StructureEntity.ofStructure(ctx, structure) + }) + +export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEntity, SpacefillProps> +export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill', + async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) { + const spacefillRepr = StructureRepresentation(Spacefill) + await Run(spacefillRepr.create(structureEntity.value, props), ctx.log) + ctx.viewer.add(spacefillRepr) + ctx.viewer.requestDraw() + console.log(ctx.viewer.stats) + return SpacefillEntity.ofRepr(ctx, spacefillRepr) + }) + +export type SpacefillUpdate = StateTransform<SpacefillEntity, SpacefillEntity, SpacefillProps> +export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill', 'spacefill', 'spacefill-update', + async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) { + console.log('fopbar') + const spacefillRepr = spacefillEntity.value + await Run(spacefillRepr.update(props), ctx.log) + ctx.viewer.add(spacefillRepr) + ctx.viewer.update() + ctx.viewer.requestDraw() + console.log(ctx.viewer.stats) + return spacefillEntity + }) + +// composed + +export type MmcifUrlToModel = StateTransform<UrlEntity, ModelEntity, {}> +export const MmcifUrlToModel: MmcifUrlToModel = StateTransform.create('url', 'model', 'url-to-model', + async function (ctx: StateContext, urlEntity: UrlEntity) { + const dataEntity = await UrlToData.apply(ctx, urlEntity) + return DataToModel.apply(ctx, dataEntity) + }) + +export type MmcifFileToModel = StateTransform<FileEntity, ModelEntity, {}> +export const MmcifFileToModel: MmcifFileToModel = StateTransform.create('file', 'model', 'file-to-model', + async function (ctx: StateContext, fileEntity: FileEntity) { + const dataEntity = await FileToData.apply(ctx, fileEntity) + return DataToModel.apply(ctx, dataEntity) + }) + +export type DataToModel = StateTransform<DataEntity, ModelEntity, {}> +export const DataToModel: DataToModel = StateTransform.create('data', 'model', 'data-to-model', + async function getModelFromData(ctx: StateContext, dataEntity: DataEntity) { + const cifEntity = await DataToCif.apply(ctx, dataEntity) + const mmcifEntity = await CifToMmcif.apply(ctx, cifEntity) + return MmcifToModel.apply(ctx, mmcifEntity) + }) + +export type ModelToSpacefill = StateTransform<ModelEntity, SpacefillEntity, SpacefillProps> +export const ModelToSpacefill: ModelToSpacefill = StateTransform.create('model', 'spacefill', 'model-to-spacefill', + async function (ctx: StateContext, modelEntity: ModelEntity, props: SpacefillProps = {}) { + const structureEntity = await ModelToStructure.apply(ctx, modelEntity) + return StructureToSpacefill.apply(ctx, structureEntity, props) + }) + +export type MmcifUrlToSpacefill = StateTransform<UrlEntity, SpacefillEntity, SpacefillProps> +export const MmcifUrlToSpacefill: MmcifUrlToSpacefill = StateTransform.create('url', 'spacefill', 'url-to-spacefill', + async function (ctx: StateContext, urlEntity: UrlEntity, props: SpacefillProps = {}) { + const modelEntity = await MmcifUrlToModel.apply(ctx, urlEntity) + return ModelToSpacefill.apply(ctx, modelEntity, props) + }) + +export type MmcifFileToSpacefill = StateTransform<FileEntity, SpacefillEntity, SpacefillProps> +export const MmcifFileToSpacefill: MmcifFileToSpacefill = StateTransform.create('file', 'spacefill', 'file-to-spacefill', + async function (ctx: StateContext, fileEntity: FileEntity, props: SpacefillProps = {}) { + const modelEntity = await MmcifFileToModel.apply(ctx, fileEntity) + return ModelToSpacefill.apply(ctx, modelEntity, props) + }) \ No newline at end of file diff --git a/src/mol-view/util.ts b/src/mol-view/util.ts index 294907ad3..cf9343d24 100644 --- a/src/mol-view/util.ts +++ b/src/mol-view/util.ts @@ -4,6 +4,31 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import CIF from 'mol-io/reader/cif' +import { Run, Progress } from 'mol-task' +import { VolumeData, parseDensityServerData } from 'mol-model/volume' +import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; + +export async function downloadCif(url: string, isBinary: boolean) { + const data = await fetch(url); + return parseCif(isBinary ? new Uint8Array(await data.arrayBuffer()) : await data.text()); +} + +export async function parseCif(data: string|Uint8Array) { + const comp = CIF.parse(data) + const parsed = await Run(comp, Progress.format); + if (parsed.isError) throw parsed; + return parsed.result +} + +export type Volume = { source: DensityServer_Data_Database, volume: VolumeData } + +export async function getVolumeFromEmdId(emdid: string): Promise<Volume> { + const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, true) + const data = CIF.schema.densityServer(cif.blocks[1]) + return { source: data, volume: await Run(parseDensityServerData(data)) } +} + export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) { let w = window.innerWidth let h = window.innerHeight diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 4e8d9c262..c4384837d 100644 --- a/src/mol-view/viewer.ts +++ b/src/mol-view/viewer.ts @@ -4,6 +4,8 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { BehaviorSubject } from 'rxjs'; + import { Vec3, Mat4, EPSILON } from 'mol-math/linear-algebra' import InputObserver from 'mol-util/input/input-observer' import * as SetUtils from 'mol-util/set' @@ -29,8 +31,11 @@ interface Viewer { draw: (force?: boolean) => void requestDraw: () => void animate: () => void + reprCount: BehaviorSubject<number> handleResize: () => void + resetCamera: () => void + downloadScreenshot: () => void stats: RendererStats dispose: () => void @@ -50,6 +55,7 @@ function getWebGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLCon namespace Viewer { export function create(canvas: HTMLCanvasElement, container: Element): Viewer { const reprMap = new Map<Representation<any>, Set<RenderObject>>() + const reprCount = new BehaviorSubject(0) const input = InputObserver.create(canvas) input.resize.subscribe(handleResize) @@ -124,10 +130,15 @@ namespace Viewer { repr.renderObjects.forEach(o => renderer.add(o)) } reprMap.set(repr, newRO) + reprCount.next(reprMap.size) }, remove: (repr: Representation<any>) => { const renderObjectSet = reprMap.get(repr) - if (renderObjectSet) renderObjectSet.forEach(o => renderer.remove(o)) + if (renderObjectSet) { + renderObjectSet.forEach(o => renderer.remove(o)) + reprMap.delete(repr) + reprCount.next(reprMap.size) + } }, update: () => renderer.update(), clear: () => { @@ -140,6 +151,13 @@ namespace Viewer { animate, handleResize, + resetCamera: () => { + // TODO + }, + downloadScreenshot: () => { + // TODO + }, + reprCount, get stats() { return renderer.stats diff --git a/tsconfig.json b/tsconfig.json index 7e025fd23..eedd1592a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "outDir": "build/node_modules", "baseUrl": "src", "paths": { + "mol-app": ["./mol-app"], "mol-data": ["./mol-data", "./mol-data/index.ts"], "mol-geo": ["./mol-geo"], "mol-gl": ["./mol-gl"], diff --git a/web/render-test/index.html b/web/render-test/index.html deleted file mode 100644 index 2e144b297..000000000 --- a/web/render-test/index.html +++ /dev/null @@ -1,12 +0,0 @@ -<!DOCTYPE html> -<html lang="en"> - <head> - <meta charset="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=1" /> - <title>Mol* Render Test</title> - </head> - <body> - <div id="app"></div> - <script src="./index.js"></script> - </body> -</html> \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 8ace2768d..0a6bc7e12 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,17 +1,34 @@ const path = require('path'); const ExtraWatchWebpackPlugin = require('extra-watch-webpack-plugin'); +const ExtractTextPlugin = require('extract-text-webpack-plugin'); module.exports = { module: { rules: [ { loader: 'raw-loader', test: /\.(glsl|frag|vert)$/, - include: [ path.resolve(__dirname, "build/node_modules/") ], + include: [ path.resolve(__dirname, 'build/node_modules/') ], }, { loader: 'glslify-loader', test: /\.(glsl|frag|vert)$/, - include: [ path.resolve(__dirname, "build/node_modules/") ] + include: [ path.resolve(__dirname, 'build/node_modules/') ] + }, + + { + loader: 'file-loader', + test: /\.(woff2?|ttf|otf|eot|svg|html)$/, + include: [ path.resolve(__dirname, 'build/node_modules/') ], + options: { + name: '[name].[ext]' + } + }, + { + test:/\.(s*)css$/, + use: ExtractTextPlugin.extract({ + fallback:'style-loader', + use:['css-loader', 'resolve-url-loader', 'sass-loader'], + }) } ] }, @@ -20,8 +37,11 @@ module.exports = { files: [ './build/node_modules/**/*.vert', './build/node_modules/**/*.frag', - './build/node_modules/**/*.glsl' + './build/node_modules/**/*.glsl', + './build/node_modules/**/*.scss', + './build/node_modules/**/*.html' ], }), + new ExtractTextPlugin({ filename:'app.css' }), ], } \ No newline at end of file -- GitLab