diff --git a/data/README.md b/data/README.md new file mode 100644 index 0000000000..94578c5680 --- /dev/null +++ b/data/README.md @@ -0,0 +1,305 @@ +# Color-Style System Documentation + +**TABLE OF CONTENTS** +- [Rapid Colors](#rapid-colors) + - [Initial Color Approach](#initial-color-approach) + - [1. Named Color Definition](#1-named-color-definition) + - [2. Direct HEX Color Definition](#2-direct-hex-color-definition) + - [3. Rapid Color Definition](#3-rapid-color-definition) + - [4. Map Colors](#4-map-colors) + - [Current Color Approach](#current-color-approach) + - [System Aims](#system-aims) + - [System Organization](#system-organization) + - [(Proposed) Color System](#proposed-color-system) + - [Relevant Refactoring](#relevant-refactoring) + - [Adding New Color Schemes](#adding-new-color-schemes) +- [Rapid Styling](#rapid-styling) + - [Overview](#overview) + - [Instance Variables](#instance-variables) + - [Usage](#usage) + - [Initial vs Current Structure](#initial-vs-current-structure) + - [Relevant Refactoring](#relevant-refactoring-1) +- [Future Possibilities](#future-possibilities) + + +## Rapid Colors + +### Initial Color Approach +As at [Rapid v2.2.5](https://github.com/facebook/Rapid/releases/tag/rapid-v2.2.5), there were four (4) classes of color definition: +1. Colors defined under a name in `StyleSystem.js` +2. Colors defined by direct HEX value in `StyleSystem.js` +3. Colors defined by direct HEX value in `RapidSystem.js` +4. Colors rendered from the map satellite imagery + +Ideally, there would be a central definition of each color which is the same across the Rapid application. +Before diving into the proposed solution to this problem, each class of color definition will be described to provide suitable context. + +#### 1. Named Color Definition +These are the colors defined as parts of key-value pairs in `this.STYLE_DECLATATIONS` object in `/modules/core/StyleSystem.js`, where the key is a named color e.g. 'red', 'blue', 'green' (see the code block below). + +```JS +red: { + fill: { color: 0xe06e5f, alpha: 0.3 } // rgb(224, 110, 95) +} +``` + +There are **12** defined "colors", which are referenced **63** times in total by `this.STYLE_SELECTORS`; this property of the `StyleSystem` class maps feature tags to style declarations (see the code block below). + +```JS +building: { + '*': 'red' +} +``` + +An issue with this particular definition system is that this color definition includes more than just the `color` property (see the first code block in this section). It is also specifies an `alpha` value and is encased in an object which specifies the `fill` property of a map feature. + +#### 2. Direct HEX Color Definition +These colors are also defined as parts of key-value pairs in `this.STYLE_DECLATATIONS` object in `/modules/core/StyleSystem.js`. The difference comes in the nonuse of named colors as keys; rather [OSM tags](https://wiki.openstreetmap.org/wiki/Tags) are used (see the code block below). + +```JS +primary: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xf99806 } +} +``` + +There are **30** distinct HEX codes, which are referenced **75** times in total by `this.STYLE_DECLARATIONS` AND the `styleMatch()` function. Usage in `this.STYLE_DECLARATIONS`, as seen in the code block above, is the + +With this color definition system, only the `color` property has anything to do with a color definition; thus, it does not have the same problem definition type #1 has of combining other style definition into color definition. However, there are two issues with the use of direct HEX code usage: +* Due to the repitition of very specific strings, it is error-prone and inefficient for refactoring. +* There are overlaps in the explicitly-defined HEX codes and `color` property values which have already been specified in named color definitions. For instance, the `green.fill.color` is assigned to `0x8cd05f` but this HEX code is also explicitly repeated in five (5) `this.STYLE_DECLARATION` objects such as `amenity: fountain` and `natural: water`. + +#### 3. Rapid Color Definition +The two defintions above cover the colors of *most* of [OpenStreetMap](https://en.wikipedia.org/wiki/OpenStreetMap)'s features - Points, Lines and Areas - which are rendered with [Pixi.js](https://pixijs.com/). Rapid colors are used when rendering Rapid features; these are the AI-generated OpenStreetMap features based on [Facebook Roads](https://github.com/facebookmicrosites/Open-Mapping-At-Facebook) and [Microsoft Buildings](https://github.com/microsoft/GlobalMLBuildingFootprints). + +Rapid colors are strings containing HEX codes which are elements in an array called `RAPID_COLORS`. An issue identified with this particular system is its redundancy; there are **10** Rapid colors but one of them is assigned to a constant again outside the array (specifically, the default Rapid color, `RAPID_MAGENTA`). This will be addressed in the "Future Works" section, as the proposed color system does NOT cover these colors due to the fact that they are already user-defined. + +#### 4. Map Colors +These are the colors which show in the satellite imagery rendered by WebGL which forms the basis of the map. These colors are NOT within the control of Rapid developers but are important to consider when thinking about topics like color vision deficiencies. + + +### Current Color Approach +#### System Aims +1. Centralize all color definitions used within the Rapid codebase +2. Enable efficient and error-resistant HEX code editing +3. Clearly separate color and style definition + +***NOTE**: This color system only takes care of the first two types of colors in Rapid only: named colors and directly-defined colors.* + +#### System Organization +* The colors are grouped by well-known color names such as 'red', 'green' +* There are **11** color groups; these color groups are arranged in ROYGBIV order followed by neutral tones: `brown`, `tan`, `white`, `black` and `grey` +* A color is defined in the format `-` e.g. `red-3`, `blue-4` + * If a color's number is `0`, that means that it is the only one in its group e.g. `white-0`, `black-0` + * For numbers `1` and above, the lower the number, the brighter/paler the color is + +#### (Proposed) Color System + + + + + + + + + +
+ +![#e06e5f](https://placehold.co/20x20/e06e5f/e06e5f.png) `red-1`
+![#e06d5f](https://placehold.co/15x15/e06d5f/e06d5f.png) `red-2`
+![#dd2f22](https://placehold.co/15x15/dd2f22/dd2f22.png) `red-3`
+![#70372f](https://placehold.co/15x15/70372f/70372f.png) `red-4`
+![#d6881a](https://placehold.co/15x15/d6881a/d6881a.png) `orange-1`
+![#f99806](https://placehold.co/15x15/f99806/f99806.png) `orange-2`
+![#fc6c14](https://placehold.co/15x15/fc6c14/fc6c14.png) `orange-3`
+![#fff9b3](https://placehold.co/15x15/fff9b3/fff9b3.png) `yellow-1`
+![#ffff94](https://placehold.co/15x15/ffff94/ffff94.png) `yellow-2`
+![#ffff00](https://placehold.co/15x15/ffff00/ffff00.png) `yellow-3`
+![#f3f312](https://placehold.co/15x15/f3f312/f3f312.png) `yellow-4`
+![#c4be19](https://placehold.co/15x15/c4be19/c4be19.png) `yellow-5`
+![#99e1aa](https://placehold.co/15x15/99e1aa/99e1aa.png) `green-1`
+ +
+ +![#b0e298](https://placehold.co/15x15/b0e298/b0e298.png) `green-2`
+![#8cd05f](https://placehold.co/15x15/8cd05f/8cd05f.png) `green-3`
+![#81d25c](https://placehold.co/15x15/81d25c/81d25c.png) `green-4`
+![#bee83f](https://placehold.co/15x15/bee83f/bee83f.png) `green-5`
+![#77dddd](https://placehold.co/15x15/77dddd/77dddd.png) `blue-1`
+![#77d4de](https://placehold.co/15x15/77d4de/77d4de.png) `blue-2`
+![#82b5fe](https://placehold.co/15x15/82b5fe/82b5fe.png) `blue-3`
+![#58a9ed](https://placehold.co/15x15/58a9ed/58a9ed.png) `blue-4`
+![#e3a4f5](https://placehold.co/15x15/e3a4f5/e3a4f5.png) `pink-1`
+![#cf2081](https://placehold.co/15x15/cf2081/cf2081.png) `pink-2`
+![#998888](https://placehold.co/15x15/998888/998888.png) `brown-1`
+![#776a6a](https://placehold.co/15x15/776a6a/776a6a.png) `brown-2`
+![#746f6f](https://placehold.co/15x15/746f6f/746f6f.png) `brown-3`
+ +
+ + +![#4c4444](https://placehold.co/15x15/4c4444/4c4444.png) `brown-4`
+![#f5dcba](https://placehold.co/15x15/f5dcba/f5dcba.png) `tan-1`
+![#ddccaa](https://placehold.co/15x15/ddccaa/ddccaa.png) `tan-2`
+![#c5b59f](https://placehold.co/15x15/c5b59f/c5b59f.png) `tan-3`
+![#ffffff](https://placehold.co/15x15/ffffff/ffffff.png) `white-0`
+![#000000](https://placehold.co/15x15/000000/000000.png) `black-0`
+![#eeeeee](https://placehold.co/15x15/eeeeee/eeeeee.png) `gray-1`
+![#dddddd](https://placehold.co/15x15/dddddd/dddddd.png) `gray-2`
+![#cccccc](https://placehold.co/15x15/cccccc/cccccc.png) `gray-3`
+![#aaaaaa](https://placehold.co/15x15/aaaaaa/aaaaaa.png) `gray-4`
+![#8c8c8c](https://placehold.co/15x15/8c8c8c/8c8c8c.png) `gray-5`
+![#555555](https://placehold.co/15x15/555555/555555.png) `gray-6`
+![#444444](https://placehold.co/15x15/444444/444444.png) `gray-7`
+ +
+ +#### Relevant Refactoring +To accomodate for the changes to the application, certain things were changed: +1. The `/data/color_schemes.json` file was minified and added to the file system of Rapid in `/scripts/build_data.js` and `/modules/core/DataLoaderSystem.js`, respectively +2. `DataLoaderSystem` was added as a dependency of `StyleSystem` +3. Variables to hold the default color scheme, current color scheme and all color schemes were created and added the the constructor of `StyleSystem` +4. In the `startAsync()` function, `DataLoaderSystem` was used to fetch the color schemes from the `/data/` folder +5. A helper function called `getHexColorCode()` was added to `StyleSystem` to get the HEX color code based on the current color scheme selected. This function is used severally in the `styleMatch()` function +6. All references to colors through named and directly-defined colors in `this.STYLE_DECLARATIONS` were renamed to match the new system + +### Adding New Color Schemes +There are several situations in which a developer may want to change the specific colors used to render features on the map e.g. to ensure colorblind accessibility. The proposed system also takes into consideration the ease with which developers can add a new color scheme. + +1. Identify the specific colors you want to redefine + +2. Identify the new HEX codes you want to assign to each new color + +3. Navigate to `/data/color_schemes.json` + +4. Add a key-value pair for the new color scheme to the document object in the following format: + ``` + "": {} + ``` + ***NOTE**: The convention for this system is to use [snake_case](https://developer.mozilla.org/en-US/docs/Glossary/Snake_case#:~:text=Snake%20case%20is%20a%20way,the%20reader%20of%20its%20appearance.) when naming your new color scheme.* + +5. Add the colors with new definitions (along with said definitions) in key-value pairs to the newly created object in the following format: + ``` + "": "" + ``` + +6. Navigate to `/data/core.yaml` + +7. In the `en` > `preferences` > `color_selection` section, add the name of the color scheme you've just created as well as how you want the color scheme to be named in the Preferences pane dropdown in the following format + ``` + : + ``` + * Ensure that the name you specified in `/data/color_schemes.json` matches the name you refer to it with here + * Ensure that neither `` and `` are NOT surrounded in quotation marks + * ``remember that is case-sensitive + +8. Run `npm run all` jn the terminal to re-build the application with the new color scheme you've just added + +9. Run `npm run quickstart` in the terminal to start the application and look at your new color scheme in action! + + +Here is an example of +```JSON +{ + "colorblind_ibm": { + "red-3": "0x648fff", + "red-4": "0x785ef0", + "orange-1": "0xfe6100", + "yellow-2": "0xffb000" + } +} +``` + +***NOTE**: You will need to rerun `npm run all` anytime you make changes to any file in the `/data/` folder to see the changes in the live application.* + + +## Rapid Styling +### Overview +Now that we have an understanding of how color is organised, let's zoom out to consider defining feature styling. For future reference, all styling is handled by `StyleSystem.js`, located in the `/modules/core/` folder, regardless of the approach. + +***NOTE**: In this section, we will be assuming the use of the proposed color system, to maintain focus on styling outside of color definition.* + +#### Instance Variables +The `StyleSystem` class has two instance variables in its constructor which are described in comments as follows: +1. `this.STYLE_DECLARATIONS`: A "Style Declaration" contains properties that describe how features should look. Each style declaration has the following structure: + ``` + : { + fill: { }, + casing: { }, + stroke: { } + } + ``` + One or more of three declaration properties - `fill`, `casing` and `stroke` - need to be defined. + +2. `this.STYLE_SELECTORS`: A "Style Selector" contains OSM key/value tags to match to a style declaration. Each style selector has the following structure: + ``` + : { + : styleID + } + ``` + +#### Usage +To illustrate usage, we will use this example of a style declaration and selector, respectively. +```JSON +"residential": { + "casing": { "width": 10, "color": "gray-7" }, + "stroke": { "width": 8, "color": "white-0" } +} +``` +```JS +highway: { + // Other key-value pairs... + residential: 'residential', + residential_link: 'residential' + // Other key-value pairs... +} +``` + +In order for Pixi to render features within the current map extent (see the `render()` function in `/modules/pixi/PixiLayerOsm.js` file), it follows this stripped process: +1. It loads the entities within the current map extent +2. It divides the entities into `polygons`, `lines`, `points` and `vertices` +3. Sub-functions to render each entity type are called e.g. `renderPolygons()` +4. In these sub-functions, the `styleMatch()` function from the `StyleSystem` class is called on the tags of the entity. An example of such a group of tags is as follows: + ```JS + { + // Other feature properties... + tags: { + highway: 'residential', + oneway: 'no' + }, + // Other feature properties... + } + ``` + Let's continue with the OSM tag key `highway` and OSM tag value `residential` +5. The `styleMatch()` function checks if the OSM tag key is in the keys `this.STYLE_SELECTORS` e.g. we can see that `highway` is a part of these keys and `oneway` is not +6. The object which is set to the value of that OSM tag key is fetched and queried for the OSM tag value. In this case, the object assigned to the `highway` key is fetched and then the value of the `residential` key within that object is fetched; in this case, that is `residential`. What we have fetched is the style ID which is defined in `this.STYLE_DECLARATIONS`. Thus, style ID for this use case is set to: + ```JSON + "residential": { + "casing": { "width": 10, "color": "gray-7" }, + "stroke": { "width": 8, "color": "white-0" } + } + ``` +7. We have now successfully connected the OSM tag and value to its assigned style definition! + +There is more to the style matching and rendering process but this is all that is relevant to this explanation. + +### Initial vs Current Structure +Originally, style declarations are defined directly in `StyleSystem.js`. Now, the style declarations have been extracted to a file called `styles.json` which is located in the `/data/` folder. + +This was done with the aim to: +1. Reduce the amount of information in `StyleSystem.js` +2. Abstracting style declaration away from style manipulation + +#### Relevant Refactoring +To accomodate for the changes to the application, certain things were changed: +1. Because the `this.STYLE_DECLARATIONS` now started as a JSON object instead of a JS object, all the keys were surrounded in quotation marks +2. The `/data/styles.json` file was minified and added to the file system of Rapid in `/scripts/build_data.js` and `/modules/core/DataLoaderSystem.js`, respectively +3. The `this.STYLE_DECLARATIONS` variable was initialised to `None` +4. In the `startAsync()` function, `DataLoaderSystem` was used to fetch the style declarations from the `/data/` folder + +## Future Possibilities +1. Consider direct HEX color code usage outside of `this.STYLE_DECLARATIONS` e.g. + * `styleMatch()` in `/modules/core/StyleSystem.js` + * `renderPolygons()` in `/modules/pixi/PixiLayerOsm.js` +2. Incorporate Rapid colors into the color system (as new color entries, NOT a new color scheme) +3. Carry out dedicated research to determine the most intuitive method to order colors \ No newline at end of file diff --git a/data/color_schemes.json b/data/color_schemes.json new file mode 100644 index 0000000000..10d2514627 --- /dev/null +++ b/data/color_schemes.json @@ -0,0 +1,87 @@ +{ + "default": { + "red-1": "0xe06e5f", + "red-2": "0xe06d5f", + "red-3": "0xdd2f22", + "red-4": "0x70372f", + + "orange-1": "0xd6881a", + "orange-2": "0xf99806", + "orange-3": "0xfc6c14", + + "yellow-1": "0xfff9b3", + "yellow-2": "0xffff94", + "yellow-3": "0xffff00", + "yellow-4": "0xf3f312", + "yellow-5": "0xc4be19", + + "green-1": "0x99e1aa", + "green-2": "0xb0e298", + "green-3": "0x8cd05f", + "green-4": "0x81d25c", + "green-5": "0xbee83f", + + "blue-1": "0x77dddd", + "blue-2": "0x77d4de", + "blue-3": "0x82b5fe", + "blue-4": "0x58a9ed", + + "pink-1": "0xe3a4f5", + "pink-2": "0xcf2081", + + "brown-1": "0x998888", + "brown-2": "0x776a6a", + "brown-3": "0x746f6f", + "brown-4": "0x4c4444", + + "tan-1": "0xf5dcba", + "tan-2": "0xddccaa", + "tan-3": "0xc5b59f", + + "white-0": "0xffffff", + + "black-0": "0x000000", + + "gray-1": "0xeeeeee", + "gray-2": "0xdddddd", + "gray-3": "0xcccccc", + "gray-4": "0xaaaaaa", + "gray-5": "0x8c8c8c", + "gray-6": "0x555555", + "gray-7": "0x444444" + }, + "high_contrast": { + "red-1": "0xff0e41", + "green-3": "0x09f04a", + "blue-2": "0x0cbcff", + "yellow-2": "0xffca09", + "yellow-5": "0xff0ebc", + "orange-1": "0xff510b", + "pink-1": "0xf1c0e8", + "green-1": "0x12ffd1", + "green-5": "0x540fff", + "tan-1": "0xaffa1f", + "gray-5": "0x0000fe", + "gray-4": "0xe9af1e" + }, + "colorblind_general": { + "red-1": "0x648fff", + "red-3": "0x530000", + "orange-1": "0x179714", + "green-3": "0x09f04a", + "green-4": "0x09e04a" + }, + "colorblind_ibm": { + "red-3": "0x648fff", + "red-4": "0x785ef0", + "orange-1": "0xfe6100", + "yellow-2": "0xffb000" + }, + "colorblind_paul_tol_vibrant": { + "red-3": "0xcc3311", + "red-4": "0xee3377", + "orange-1": "0xee7733", + "green-3": "0x009988", + "blue-2": "0x33bbee" + } +} \ No newline at end of file diff --git a/data/core.yaml b/data/core.yaml index e0c5b98073..1f4f1358fe 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -999,19 +999,24 @@ en: title: Pan the Map tooltip: Scrolling with the wheel will pan the map. Holding down Shift will zoom and unzoom the map. color_selection: - title: Map Color Scheme + title: Color Scheme tooltip: Switch between color schemes default: Default - example: Example (High Contrast) + high_contrast: High Contrast + colorblind_general: Colorblind Palette - General + colorblind_ibm: Colorblind Palette - IBM + colorblind_paul_tol_vibrant: Colorblind Palette - Paul Tol (Vibrant) placeholder: Select color scheme + switch_color_scheme: + key: C colorblind_options: - title: Colorblind Mode - tooltip: Switch between colorblind modes + title: Colorblind Simulator + tooltip: Switch between colorblind simulators default: Default (None) protanopia: Protanopia deuteranopia: Deuteranopia tritanopia: Tritanopia - placeholder: Select colorblind mode + placeholder: Select colorblind simulator restore: heading: You have unsaved changes description: "Do you wish to restore unsaved changes from a previous editing session?" @@ -1164,6 +1169,9 @@ en: allowLargeEdits: label: Allow large edits description: Remove the limitations about editing features that are not currently visible + showColorblindSimulators: + label: Enable colorblind simulators + description: Enable the colorblind simulator combo box on the Preference pane for simulating three color vision deficiencies version: whats_new: "What's new in Rapid {version}" tag_reference: @@ -2549,6 +2557,7 @@ en: map_data: "Toggle map data pane" issues: "Toggle validation issues pane" preferences: "Toggle user preferences pane" + switch_color_scheme: "Switch to next color scheme in list" fullscreen: "Toggle full screen mode" sidebar: "Toggle sidebar" wireframe: "Toggle wireframe mode" diff --git a/data/shortcuts.json b/data/shortcuts.json index 13e795dcce..02c5f64402 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -79,6 +79,11 @@ "shortcuts": ["preferences.key"], "text": "shortcuts.browsing.display_options.preferences" }, + { + "modifiers": ["⌥", "⇧"], + "shortcuts": ["preferences.color_selection.switch_color_scheme.key"], + "text": "shortcuts.browsing.display_options.switch_color_scheme" + }, { "modifiers": ["⌃", "⌘"], "shortcuts": ["F"], diff --git a/data/styles.json b/data/styles.json new file mode 100644 index 0000000000..d752ba6481 --- /dev/null +++ b/data/styles.json @@ -0,0 +1,196 @@ +{ + "DEFAULTS": { + "fill": { "width": 2, "color": "gray-4", "alpha": 0.3 }, + "casing": { "width": 5, "color": "gray-7", "alpha": 1, "cap": "round", "join": "round" }, + "stroke": { "width": 3, "color": "gray-3", "alpha": 1, "cap": "round", "join": "round" } + }, + + "LIFECYCLE": { + "casing": { "alpha": 0 }, + "stroke": { "dash": [7, 3], "cap": "butt" } + }, + + "red-fill": { + "fill": { "color": "red-1", "alpha": 0.3 } + }, + "green-fill": { + "fill": { "color": "green-3", "alpha": 0.3 } + }, + "blue-fill": { + "fill": { "color": "blue-2", "alpha": 0.3 } + }, + "yellow-fill": { + "fill": { "color": "yellow-2", "alpha": 0.25 } + }, + "gold-fill": { + "fill": { "color": "yellow-5", "alpha": 0.3 } + }, + "orange-fill": { + "fill": { "color": "orange-1", "alpha": 0.3 } + }, + "pink-fill": { + "fill": { "color": "pink-1", "alpha": 0.3 } + }, + "teal-fill": { + "fill": { "color": "green-1", "alpha": 0.3 } + }, + "lightgreen-fill": { + "fill": { "color": "green-5", "alpha": 0.3 } + }, + "tan-fill": { + "fill": { "color": "tan-1", "alpha": 0.3 } + }, + "darkgray-fill": { + "fill": { "color": "gray-6", "alpha": 0.5 } + }, + "lightgray-fill": { + "fill": { "color": "gray-7", "alpha": 0.3 } + }, + + "motorway": { + "casing": { "width": 10, "color": "red-4" }, + "stroke": { "width": 8, "color": "pink-2" } + }, + "trunk": { + "casing": { "width": 10, "color": "red-4" }, + "stroke": { "width": 8, "color": "red-3" } + }, + "primary": { + "casing": { "width": 10, "color": "red-4" }, + "stroke": { "width": 8, "color": "orange-2" } + }, + "secondary": { + "casing": { "width": 10, "color": "red-4" }, + "stroke": { "width": 8, "color": "yellow-4" } + }, + "tertiary": { + "casing": { "width": 10, "color": "red-4" }, + "stroke": { "width": 8, "color": "yellow-1" } + }, + "unclassified": { + "casing": { "width": 10, "color": "gray-7" }, + "stroke": { "width": 8, "color": "tan-2" } + }, + "residential": { + "casing": { "width": 10, "color": "gray-7" }, + "stroke": { "width": 8, "color": "white-0" } + }, + "living_street": { + "casing": { "width": 7, "color": "white-0" }, + "stroke": { "width": 5, "color": "gray-3" } + }, + "service": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "white-0" } + }, + "special_service": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "tan-2" } + }, + "track": { + "casing": { "width": 7, "color": "brown-3" }, + "stroke": { "width": 5, "color": "tan-3" } + }, + "pedestrian": { + "casing": { "width": 7, "color": "white-0" }, + "stroke": { "width": 5, "color": "brown-1", "dash": [8, 8], "cap": "butt" } + }, + "path": { + "casing": { "width": 5, "color": "tan-2" }, + "stroke": { "width": 3, "color": "brown-1", "dash": [6, 6], "cap": "butt" } + }, + "footway": { + "casing": { "width": 5, "color": "white-0" }, + "stroke": { "width": 3, "color": "brown-1", "dash": [6, 6], "cap": "butt" } + }, + "crossing_marked": { + "casing": { "width": 5, "color": "tan-2" }, + "stroke": { "width": 3, "color": "brown-4", "dash": [6, 3], "cap": "butt" } + }, + "crossing_unmarked": { + "casing": { "width": 5, "color": "tan-2" }, + "stroke": { "width": 3, "color": "brown-2", "dash": [6, 4], "cap": "butt" } + }, + "cycleway": { + "casing": { "width": 5, "color": "white-0" }, + "stroke": { "width": 3, "color": "blue-4", "dash": [6, 6], "cap": "butt" } + }, + "bridleway": { + "casing": { "width": 5, "color": "white-0" }, + "stroke": { "width": 3, "color": "red-2", "dash": [6, 6], "cap": "butt" } + }, + "corridor": { + "casing": { "width": 5, "color": "white-0" }, + "stroke": { "width": 3, "color": "green-3", "dash": [2, 8], "cap": "round" } + }, + "steps": { + "casing": { "width": 5, "color": "white-0" }, + "stroke": { "width": 3, "color": "green-4", "dash": [3, 3], "cap": "butt" } + }, + "river": { + "fill": { "color": "blue-2", "alpha": 0.3 }, + "casing": { "width": 10, "color": "gray-7" }, + "stroke": { "width": 8, "color": "blue-1" } + }, + "stream": { + "fill": { "color": "blue-2", "alpha": 0.3 }, + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "blue-1" } + }, + "ridge": { + "stroke": { "width": 2, "color": "green-3" } + }, + "runway": { + "casing": { "width": 10, "color": "black-0", "cap": "butt" }, + "stroke": { "width": 8, "color": "white-0", "dash": [24, 48], "cap": "butt" } + }, + "taxiway": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "yellow-3" } + }, + "railway": { + "casing": { "width": 7, "color": "gray-6", "cap": "butt" }, + "stroke": { "width": 2, "color": "gray-1", "dash": [12, 12], "cap": "butt" } + }, + "ferry": { + "casing": { "alpha": 0 }, + "stroke": { "width": 3, "color": "blue-4", "dash": [12, 8], "cap": "butt" } + }, + "boundary": { + "casing": { "width": 6, "color": "blue-3", "cap": "butt" }, + "stroke": { "width": 2, "color": "white-0", "dash": [20, 5, 5, 5], "cap": "butt" } + }, + "boundary_park": { + "casing": { "width": 6, "color": "blue-3", "cap": "butt" }, + "stroke": { "width": 2, "color": "green-2", "dash": [20, 5, 5, 5], "cap": "butt" } + }, + "barrier": { + "casing": { "alpha": 0 }, + "stroke": { "width": 3, "color": "gray-2", "dash": [10, 5, 2, 5], "cap": "round" } + }, + "barrier_wall": { + "casing": { "alpha": 0 }, + "stroke": { "width": 3, "color": "gray-2", "dash": [10, 5, 2, 5], "cap": "round" } + }, + "barrier_hedge": { + "fill": { "color": "green-3", "alpha": 0.3 }, + "casing": { "alpha": 0 }, + "stroke": { "width": 3, "color": "green-3", "dash": [10, 5, 2, 5], "cap": "round" } + }, + "tree_row": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "green-3" } + }, + "construction": { + "casing": { "width": 10, "color": "white-0" }, + "stroke": { "width": 8, "color": "orange-3", "dash": [10, 10], "cap": "butt" } + }, + "pipeline": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "gray-2", "dash": [80, 2], "cap": "butt" } + }, + "roller_coaster": { + "casing": { "width": 7, "color": "gray-7" }, + "stroke": { "width": 5, "color": "gray-2", "dash": [10, 1], "cap": "butt" } + } +} \ No newline at end of file diff --git a/modules/core/DataLoaderSystem.js b/modules/core/DataLoaderSystem.js index 810beb269a..b25a9ff792 100644 --- a/modules/core/DataLoaderSystem.js +++ b/modules/core/DataLoaderSystem.js @@ -23,6 +23,7 @@ export class DataLoaderSystem extends AbstractSystem { const fileMap = new Map(); fileMap.set('address_formats', 'data/address_formats.min.json'); + fileMap.set('color_schemes', 'data/color_schemes.min.json'); fileMap.set('deprecated', 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@6.6/dist/deprecated.min.json'); fileMap.set('discarded', 'https://cdn.jsdelivr.net/npm/@openstreetmap/id-tagging-schema@6.6/dist/discarded.min.json'); fileMap.set('imagery', 'data/imagery.min.json'); @@ -42,6 +43,7 @@ export class DataLoaderSystem extends AbstractSystem { fileMap.set('preset_overrides', 'data/preset_overrides.min.json'); fileMap.set('qa_data', 'data/qa_data.min.json'); fileMap.set('shortcuts', 'data/shortcuts.min.json'); + fileMap.set('styles', 'data/styles.min.json'); fileMap.set('territory_languages', 'data/territory_languages.min.json'); fileMap.set('wmf_sitematrix', 'https://cdn.jsdelivr.net/npm/wmf-sitematrix@0.1/wikipedia.min.json'); diff --git a/modules/core/StyleSystem.js b/modules/core/StyleSystem.js index 4beb1a4671..a24b5c1c53 100644 --- a/modules/core/StyleSystem.js +++ b/modules/core/StyleSystem.js @@ -35,28 +35,31 @@ export class StyleSystem extends AbstractSystem { this.context = context; this.dependencies = new Set(['dataloader']); this.autoStart = true; + this.defaultColorScheme = null; + this.colorSchemes = null; + this.currentColorScheme = null; // Experiment, see Rapid#1230 // matrix values from https://github.com/maputnik/editor this.protanopiaMatrix = [ - 0.567, 0.433, 0, 0, 0, - 0.558, 0.442, 0, 0, 0, - 0, 0.242, 0.758, 0, 0, - 0, 0, 0, 1, 0 + 0.567, 0.433, 0, 0, 0, + 0.558, 0.442, 0, 0, 0, + 0, 0.242, 0.758, 0, 0, + 0, 0, 0, 1, 0 ]; this.deuteranopiaMatrix = [ - 0.625, 0.375, 0, 0, 0, - 0.7, 0.3, 0, 0, 0, - 0, 0.3, 0.7, 0, 0, - 0, 0, 0, 1, 0 + 0.625, 0.375, 0, 0, 0, + 0.7, 0.3, 0, 0, 0, + 0, 0.3, 0.7, 0, 0, + 0, 0, 0, 1, 0 ]; this.tritanopiaMatrix = [ - 0.95, 0.05, 0, 0, 0, - 0, 0.433, 0.567, 0, 0, - 0, 0.475, 0.525, 0, 0, - 0, 0, 0, 1, 0 + 0.95, 0.05, 0, 0, 0, + 0, 0.433, 0.567, 0, 0, + 0, 0.475, 0.525, 0, 0, + 0, 0, 0, 1, 0 ]; @@ -86,202 +89,7 @@ export class StyleSystem extends AbstractSystem { // `pattern` - supported pattern (see dist/img/pattern/* for these) // - this.STYLE_DECLARATIONS = { - DEFAULTS: { - fill: { width: 2, color: 0xaaaaaa, alpha: 0.3 }, - casing: { width: 5, color: 0x444444, alpha: 1, cap: 'round', join: 'round' }, - stroke: { width: 3, color: 0xcccccc, alpha: 1, cap: 'round', join: 'round' } - }, - - LIFECYCLE: { // e.g. planned, proposed, abandoned, disused, razed - casing: { alpha: 0 }, // disable - stroke: { dash: [7, 3], cap: 'butt' } - }, - - red: { - fill: { color: 0xe06e5f, alpha: 0.3 } // rgb(224, 110, 95) - }, - green: { - fill: { color: 0x8cd05f, alpha: 0.3 } // rgb(140, 208, 95) - }, - blue: { - fill: { color: 0x77d4de, alpha: 0.3 } // rgb(119, 211, 222) - }, - yellow: { - fill: { color: 0xffff94, alpha: 0.25 } // rgb(255, 255, 148) - }, - gold: { - fill: { color: 0xc4be19, alpha: 0.3 } // rgb(196, 189, 25) - }, - orange: { - fill: { color: 0xd6881a, alpha: 0.3 } // rgb(214, 136, 26) - }, - pink: { - fill: { color: 0xe3a4f5, alpha: 0.3 } // rgb(228, 164, 245) - }, - teal: { - fill: { color: 0x99e1aa, alpha: 0.3 } // rgb(153, 225, 170) - }, - lightgreen: { - fill: { color: 0xbee83f, alpha: 0.3 } // rgb(191, 232, 63) - }, - tan: { - fill: { color: 0xf5dcba, alpha: 0.3 } // rgb(245, 220, 186) - }, - darkgray: { - fill: { color: 0x8c8c8c, alpha: 0.5 } // rgb(140, 140, 140) - }, - lightgray: { - fill: { color: 0xaaaaaa, alpha: 0.3 } // rgb(170, 170, 170) - }, - - motorway: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xcf2081 } - }, - trunk: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xdd2f22 } - }, - primary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xf99806 } - }, - secondary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xf3f312 } - }, - tertiary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xfff9b3 } - }, - unclassified: { - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0xddccaa } - }, - residential: { - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0xffffff } - }, - living_street: { - casing: { width: 7, color: 0xffffff }, - stroke: { width: 5, color: 0xcccccc } - }, - service: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xffffff } - }, - special_service: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xddccaa } - }, - track: { - casing: { width: 7, color: 0x746f6f }, - stroke: { width: 5, color: 0xc5b59f } - }, - pedestrian: { - casing: { width: 7, color: 0xffffff }, - stroke: { width: 5, color: 0x998888, dash: [8, 8], cap: 'butt' } - }, - path: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: 'butt' } - }, - footway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: 'butt' } - }, - crossing_marked: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x4c4444, dash: [6, 3], cap: 'butt' } - }, - crossing_unmarked: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x776a6a, dash: [6, 4], cap: 'butt' } - }, - cycleway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x58a9ed, dash: [6, 6], cap: 'butt' } - }, - bridleway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0xe06d5f, dash: [6, 6], cap: 'butt' } - }, - corridor: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x8cd05f, dash: [2, 8], cap: 'round' } - }, - steps: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x81d25c, dash: [3, 3], cap: 'butt' } - }, - river: { - fill: { color: 0x77d4de, alpha: 0.3 }, // rgb(119, 211, 222) - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0x77dddd } - }, - stream: { - fill: { color: 0x77d4de, alpha: 0.3 }, // rgb(119, 211, 222) - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0x77dddd } - }, - ridge: { - stroke: { width: 2, color: 0x8cd05f} // rgb(140, 208, 95) - }, - runway: { - casing: { width: 10, color: 0x000000, cap: 'butt' }, - stroke: { width: 8, color: 0xffffff, dash: [24, 48], cap: 'butt' } - }, - taxiway: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xffff00 } - }, - railway: { - casing: { width: 7, color: 0x555555, cap: 'butt' }, - stroke: { width: 2, color: 0xeeeeee, dash: [12, 12], cap: 'butt' } - }, - ferry: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0x58a9ed, dash: [12, 8], cap: 'butt' } - }, - boundary: { - casing: { width: 6, color: 0x82b5fe, cap: 'butt' }, - stroke: { width: 2, color: 0xffffff, dash: [20, 5, 5, 5], cap: 'butt' } - }, - boundary_park: { - casing: { width: 6, color: 0x82b5fe, cap: 'butt' }, - stroke: { width: 2, color: 0xb0e298, dash: [20, 5, 5, 5], cap: 'butt' } - }, - barrier: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 2, 5], cap: 'round' } - }, - barrier_wall: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 2, 5], cap: 'round' } - }, - barrier_hedge: { - fill: { color: 0x8cd05f, alpha: 0.3 }, // rgb(140, 208, 95) - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0x8cd05f, dash: [10, 5, 2, 5], cap: 'round' } - }, - tree_row: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0x8cd05f } - }, - construction: { - casing: { width: 10, color: 0xffffff}, - stroke: { width: 8, color: 0xfc6c14, dash: [10, 10], cap: 'butt' } - }, - pipeline: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xdddddd, dash: [80, 2], cap: 'butt' } - }, - roller_coaster: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xdddddd, dash: [10, 1], cap: 'butt' } - } - }; + this.STYLE_DECLARATIONS = {}; // // A "Style Selector" contains OSM key/value tags to match to a style declaration. @@ -308,17 +116,17 @@ export class StyleSystem extends AbstractSystem { taxiway: 'taxiway' }, amenity: { - childcare: 'yellow', - college: 'yellow', - fountain: 'blue', - kindergarten: 'yellow', - parking: 'darkgray', - research_institute: 'yellow', - school: 'yellow', - university: 'yellow' + childcare: 'yellow-fill', + college: 'yellow-fill', + fountain: 'blue-fill', + kindergarten: 'yellow-fill', + parking: 'darkgray-fill', + research_institute: 'yellow-fill', + school: 'yellow-fill', + university: 'yellow-fill' }, building: { - '*': 'red' + '*': 'red-fill' }, barrier: { city_wall: 'barrier_wall', @@ -340,7 +148,7 @@ export class StyleSystem extends AbstractSystem { '*': 'crossing_unmarked' }, golf: { - green: 'lightgreen' + green: 'lightgreen-fill' }, highway: { bridleway: 'bridleway', @@ -374,67 +182,67 @@ export class StyleSystem extends AbstractSystem { unclassified_link: 'unclassified' }, landuse: { - cemetery: 'lightgreen', - commercial: 'orange', - construction: 'gold', - farmland: 'lightgreen', - farmyard: 'tan', - flowerbed: 'green', - forest: 'green', - grass: 'green', - industrial: 'pink', - landfill: 'orange', - meadow: 'lightgreen', - military: 'orange', - orchard: 'lightgreen', - quarry: 'darkgray', - railway: 'darkgray', - recreation_ground: 'green', - residential: 'gold', - retail: 'orange', - village_green: 'green', - vineyard: 'lightgreen' + cemetery: 'lightgreen-fill', + commercial: 'orange-fill', + construction: 'gold-fill', + farmland: 'lightgreen-fill', + farmyard: 'tan-fill', + flowerbed: 'green-fill', + forest: 'green-fill', + grass: 'green-fill', + industrial: 'pink-fill', + landfill: 'orange-fill', + meadow: 'lightgreen-fill', + military: 'orange-fill', + orchard: 'lightgreen-fill', + quarry: 'darkgray-fill', + railway: 'darkgray-fill', + recreation_ground: 'green-fill', + residential: 'gold-fill', + retail: 'orange-fill', + village_green: 'green-fill', + vineyard: 'lightgreen-fill' }, leisure: { - garden: 'green', - golf_course: 'green', - nature_reserve: 'green', - park: 'green', - pitch: 'green', - swimming_pool: 'blue', - track: 'yellow' + garden: 'green-fill', + golf_course: 'green-fill', + nature_reserve: 'green-fill', + park: 'green-fill', + pitch: 'green-fill', + swimming_pool: 'blue-fill', + track: 'yellow-fill' }, man_made: { - adit: 'darkgray', + adit: 'darkgray-fill', breakwater: 'barrier_wall', groyne: 'barrier_wall', pipeline: 'pipeline' }, military: { - '*': 'orange' + '*': 'orange-fill' }, natural: { - bare_rock: 'darkgray', - bay: 'blue', - beach: 'yellow', - cave_entrance: 'darkgray', - cliff: 'darkgray', - glacier: 'lightgray', + bare_rock: 'darkgray-fill', + bay: 'blue-fill', + beach: 'yellow-fill', + cave_entrance: 'darkgray-fill', + cliff: 'darkgray-fill', + glacier: 'lightgray-fill', ridge: 'ridge', - rock: 'darkgray', - sand: 'yellow', - scree: 'darkgray', - scrub: 'yellow', - shingle: 'darkgray', - stone: 'darkgray', - strait: 'blue', + rock: 'darkgray-fill', + sand: 'yellow-fill', + scree: 'darkgray-fill', + scrub: 'yellow-fill', + shingle: 'darkgray-fill', + stone: 'darkgray-fill', + strait: 'blue-fill', tree_row: 'tree_row', - water: 'blue', - wetland: 'teal', - '*': 'green' + water: 'blue-fill', + wetland: 'teal-fill', + '*': 'green-fill' }, power: { - plant: 'pink' + plant: 'pink-fill' }, railway: { platform: 'footway', @@ -447,11 +255,11 @@ export class StyleSystem extends AbstractSystem { ferry: 'ferry' }, sport: { - baseball: 'yellow', - basketball: 'darkgray', - beachvolleyball: 'yellow', - skateboard: 'darkgray', - softball: 'yellow' + baseball: 'yellow-fill', + basketball: 'darkgray-fill', + beachvolleyball: 'yellow-fill', + skateboard: 'darkgray-fill', + softball: 'yellow-fill' }, type: { waterway: 'river' @@ -553,6 +361,10 @@ export class StyleSystem extends AbstractSystem { this.styleMatch = this.styleMatch.bind(this); + this.getColorScheme = this.getColorScheme.bind(this); + this.getAllColorSchemes = this.getAllColorSchemes.bind(this); + this.setColorScheme = this.setColorScheme.bind(this); + this.getHexColorCode = this.getHexColorCode.bind(this); } @@ -561,10 +373,10 @@ export class StyleSystem extends AbstractSystem { * Called after all core objects have been constructed. * @return {Promise} Promise resolved when this component has completed initialization */ - initAsync(){ + initAsync() { for (const id of this.dependencies) { if (!this.context.systems[id]) { - return Promise.reject(`Cannot init: ${this.id} requires ${id}`); + return Promise.reject(`Cannot init: ${this.id} requires ${id}`); } } return Promise.resolve(); @@ -577,6 +389,26 @@ export class StyleSystem extends AbstractSystem { */ startAsync() { this._started = true; + + // Fetch the color scheme objects from color_schemes.json + const context = this.context; + const dataloader = context.systems.dataloader; + + dataloader.getDataAsync('color_schemes') + .then((data) => { + this.colorSchemes = data; + // Set the current color scheme to default + this.defaultColorScheme = data.default; + this.currentColorScheme = data.default; + this.emit('colorsloaded'); // emit copies + }); + + // Fetch the style objects from styles.json + dataloader.getDataAsync('styles') + .then((data) => { + this.STYLE_DECLARATIONS = data; + }); + return Promise.resolve(); } @@ -590,6 +422,42 @@ export class StyleSystem extends AbstractSystem { return Promise.resolve(); } + /** + * getColorScheme + * @return {Object} Default color scheme object + */ + getColorScheme() { + return this.currentColorScheme; + } + + /** + * getAllColorSchemes + * @return {Object} All color scheme objects + */ + getAllColorSchemes() { + return this.colorSchemes; + } + + /** + * setColorScheme + * Assigns the currentColorScheme var to the new scheme, if the selected scheme is not the current scheme + * @param {Object} scheme - color scheme project + */ + setColorScheme(scheme) { + let currentScheme = this.colorSchemes[scheme]; + if (this.currentColorScheme !== currentScheme) { + this.currentColorScheme = scheme; + this.currentColorScheme = currentScheme; + } + } + + /** + * getHexColorCode + * @return {String} HEX color code + */ + getHexColorCode(colorName) { + return this.currentColorScheme[colorName] ?? this.defaultColorScheme[colorName]; + } /** * styleMatch @@ -648,12 +516,6 @@ export class StyleSystem extends AbstractSystem { } else if ((!styleKey || k === styleKey) && lifecycleVals.has(v)) { hasLifecycleTag = true; break; - - // Lifecycle key prefix, e.g. `demolished:railway=rail` - // (applies only if there is no styleKey controlling the styling) - } else if (!styleKey && lifecycleRegex.test(k) && v !== 'no') { - hasLifecycleTag = true; - break; } } @@ -663,14 +525,13 @@ export class StyleSystem extends AbstractSystem { for (const group of ['fill', 'casing', 'stroke']) { style[group] = {}; for (const prop of ['width', 'color', 'alpha', 'cap', 'dash']) { - const value = matched[group] && matched[group][prop]; - if (value !== undefined) { - style[group][prop] = value; - } else { - const fallback = defaults[group] && defaults[group][prop]; - if (fallback !== undefined) { - style[group][prop] = fallback; - } + // Get the style match OR the default if a style match does not exist + const value = matched[group]?.[prop] ?? defaults[group]?.[prop]; + + // Set the property to the fetched value if the fetched value exists + // NOTE: The actual color code has to be fetched from `this.currentColorScheme` + if (value) { + style[group][prop] = (prop !== 'color') ? value : this.getHexColorCode(value); } } } diff --git a/modules/ui/panes/preferences.js b/modules/ui/panes/preferences.js index b9ab5ce188..228b83ef57 100644 --- a/modules/ui/panes/preferences.js +++ b/modules/ui/panes/preferences.js @@ -1,8 +1,8 @@ import { uiPane } from '../pane.js'; import { uiSectionPrivacy } from '../sections/privacy.js'; -//import { uiSectionColorSelection } from '../sections/color_selection.js'; -//import { uiSectionColorblindModeOptions } from '../sections/colorblind_mode_options.js'; import { uiSectionMapInteractionOptions } from '../sections/map_interaction_options.js'; +import { uiSectionColorSelection } from '../sections/color_selection.js'; +import { uiSectionColorblindModeOptions } from '../sections/colorblind_mode_options.js'; export function uiPanePreferences(context) { @@ -16,7 +16,7 @@ export function uiPanePreferences(context) { .sections([ uiSectionPrivacy(context), uiSectionMapInteractionOptions(context), -// uiSectionColorSelection(context), -// uiSectionColorblindModeOptions(context) + uiSectionColorSelection(context), + uiSectionColorblindModeOptions(context) ]); } diff --git a/modules/ui/rapid_poweruser_features_dialog.js b/modules/ui/rapid_poweruser_features_dialog.js index dee7b72a82..0ca735932f 100644 --- a/modules/ui/rapid_poweruser_features_dialog.js +++ b/modules/ui/rapid_poweruser_features_dialog.js @@ -10,7 +10,7 @@ export function uiRapidPowerUserFeaturesDialog(context) { const urlhash = context.systems.urlhash; const featureFlags = [ - 'previewDatasets', 'tagnosticRoadCombine', 'tagSources', 'showAutoFix', 'allowLargeEdits' + 'previewDatasets', 'tagnosticRoadCombine', 'tagSources', 'showAutoFix', 'allowLargeEdits', 'showColorblindSimulators' ]; let _modalSelection = d3_select(null); @@ -74,6 +74,16 @@ export function uiRapidPowerUserFeaturesDialog(context) { context.enter('browse'); // return to browse mode (in case something was selected) context.systems.map.immediateRedraw(); } + + if (featureFlag === 'showColorblindSimulators') { + let colorblindSimulators = d3_select('.section-preferences-colorblind-mode-options'); + + if (enabled) { + colorblindSimulators.classed('hide', false); + } else { + colorblindSimulators.classed('hide', true); + } + } } diff --git a/modules/ui/section.js b/modules/ui/section.js index f3a4167581..d3feb80005 100644 --- a/modules/ui/section.js +++ b/modules/ui/section.js @@ -13,6 +13,7 @@ export function uiSection(context, sectionID) { let _classes = utilFunctor(''); let _container = d3_select(null); + let _initHide; let _shouldDisplay; let _content; let _disclosure; @@ -40,6 +41,12 @@ export function uiSection(context, sectionID) { return section; }; + section.initHide = function(val) { + if (!arguments.length) return _initHide; + _initHide = utilFunctor(val); + return section; + }; + section.shouldDisplay = function(val) { if (!arguments.length) return _shouldDisplay; _shouldDisplay = utilFunctor(val); @@ -102,6 +109,12 @@ export function uiSection(context, sectionID) { } } + // If there is a _initHide() function, we call it to determine if it should be hidden. + if (typeof _initHide === 'function') { + const initHide = _initHide(); + selection.classed('hide', initHide); + } + // Render the content inside a Disclosure if (_disclosureContent) { if (!_disclosure) { // create if needed diff --git a/modules/ui/sections/color_selection.js b/modules/ui/sections/color_selection.js index b7ca52679b..7f44ca6ed1 100644 --- a/modules/ui/sections/color_selection.js +++ b/modules/ui/sections/color_selection.js @@ -2,20 +2,20 @@ import { uiTooltip } from '../tooltip.js'; import { uiCombobox } from '../combobox.js'; import { uiSection } from '../section.js'; import { utilNoAuto } from '../../util/index.js'; - +import { uiCmd } from '../index.js'; export function uiSectionColorSelection(context) { const l10n = context.systems.l10n; - const colors = context.systems.colors; // todo: replace + const styles = context.systems.styles; // Add or replace event handlers - colors.off('colorsloaded', loadComboBoxData); - colors.on('colorsloaded', loadComboBoxData); + styles.off('colorsloaded', loadComboBoxData); + styles.on('colorsloaded', loadComboBoxData); let comboData = []; function loadComboBoxData(){ - let colorSchemeKeys = Object.keys(colors.getAllColorSchemes()); + let colorSchemeKeys = Object.keys(styles.getAllColorSchemes()); for (let i = 0; i < colorSchemeKeys.length; i++) { let colorObject = {}; @@ -27,9 +27,8 @@ export function uiSectionColorSelection(context) { return comboData; } - const section = uiSection(context, 'preferences-color-selection') - .label(l10n.tHtml('preferences.color_selection.title')) + .label(l10n.t('preferences.color_selection.title')) .disclosureContent(renderDisclosureContent); const colorCombo = uiCombobox(context, 'color-selection'); @@ -55,47 +54,72 @@ export function uiSectionColorSelection(context) { .attr('placeholder', l10n.t('preferences.color_selection.placeholder')) .call(utilNoAuto) .call(colorCombo) - .on('blur change', d3_event => { - const element = d3_event.currentTarget; - const val = (element && element.value) || ''; - const data = colorCombo.data(); - if (data.some(item => item.value === val)) { - _colorSelectedId = val; - let colorSchemeName = getColorSchemeName(_colorSelectedId); - - if (colors.currentColorScheme !== colorSchemeName) { - colors.setColorScheme(colorSchemeName); - context.scene().dirtyScene(); - context.systems.map.deferredRedraw(); - } - - } else { - d3_event.currentTarget.value = ''; - _colorSelectedId = null; - } - }); + .on('blur change', d3_event => updateColorScheme(d3_event) ); colorCombo.data(comboData); - update(); + update(selection); + + } - function update() { - selection.selectAll('.preferences-color-selection-item') - .classed('active', (_checkboxState === 'true')) - .select('input') - .property('checked', (_checkboxState === 'true')); + function update(selection) { + selection.selectAll('.preferences-color-selection-item') + .classed('active', (_checkboxState === 'true')) + .select('input') + .property('checked', (_checkboxState === 'true')); + } + + function getColorSchemeName(val) { + for (let i = 0; i < comboData.length; i++) { + let colorSchemeObj = comboData[i]; + if (colorSchemeObj.value === val) { + return colorSchemeObj.title; + } } + } - function getColorSchemeName(val) { - for (let i = 0; i < comboData.length; i++) { - let colorSchemeObj = comboData[i]; - if (colorSchemeObj.value === val) { - return colorSchemeObj.title; - } + function updateColorScheme(d3_event) { + const element = d3_event.currentTarget; + const val = (element && element.value) || ''; + const data = colorCombo.data(); + if (data.some(item => item.value === val)) { + _colorSelectedId = val; + let colorSchemeName = getColorSchemeName(_colorSelectedId); + + if (styles.currentColorScheme !== colorSchemeName) { + styles.setColorScheme(colorSchemeName); + context.scene().dirtyScene(); + context.systems.map.deferredRedraw(); } + + } else { + d3_event.currentTarget.value = ''; + _colorSelectedId = null; } + } + + function switchColorScheme() { + // Get all color schemes + const colorSchemesOrdered = ['default', 'high_contrast', 'colorblind_general', 'colorblind_ibm', 'colorblind_paul_tol_vibrant']; + const allColorSchemes = styles.getAllColorSchemes(); + // Identify the name and index of the current color scheme + const currentColorSchemeName = colorSchemesOrdered.find( item => allColorSchemes[item] === styles.getColorScheme() ); + const currentColorSchemeIndex = colorSchemesOrdered.findIndex( item => item === currentColorSchemeName); + + // Use the index of the current color scheme to identify the next one + const numColorSchemes = Object.keys(styles.getAllColorSchemes()).length; + const nextColorSchemeIndex = (currentColorSchemeIndex + 1) % numColorSchemes; + const nextColorSchemeName = colorSchemesOrdered[nextColorSchemeIndex]; + + // Update the color scheme + styles.setColorScheme(nextColorSchemeName); + context.scene().dirtyScene(); + context.systems.map.deferredRedraw(); } + context.keybinding() + .on(uiCmd('⌥⇧' + l10n.t('preferences.color_selection.switch_color_scheme.key')), switchColorScheme); + return section; } diff --git a/modules/ui/sections/colorblind_mode_options.js b/modules/ui/sections/colorblind_mode_options.js index a5cc99f5eb..7bbe6a7fe8 100644 --- a/modules/ui/sections/colorblind_mode_options.js +++ b/modules/ui/sections/colorblind_mode_options.js @@ -7,7 +7,7 @@ import { utilNoAuto } from '../../util/index.js'; export function uiSectionColorblindModeOptions(context) { const l10n = context.systems.l10n; - const colors = context.systems.colors; // todo: replace + const styles = context.systems.styles; let comboData = [{ title: 'default', value: l10n.t('preferences.colorblind_options.default') }]; @@ -20,16 +20,16 @@ export function uiSectionColorblindModeOptions(context) { const filtersObject = { 'Protanopia': protanopiaFilter, 'Deuteranopia': deuteranopiaFilter, 'Tritanopia': tritanopiaFilter }; // color matrices - const protanopiaMatrix = colors.protanopiaMatrix; - const deuteranopiaMatrix = colors.deuteranopiaMatrix; - const tritanopiaMatrix = colors.tritanopiaMatrix; + const protanopiaMatrix = styles.protanopiaMatrix; + const deuteranopiaMatrix = styles.deuteranopiaMatrix; + const tritanopiaMatrix = styles.tritanopiaMatrix; // apply color matrices to filters protanopiaFilter.matrix = protanopiaMatrix; deuteranopiaFilter.matrix = deuteranopiaMatrix; tritanopiaFilter.matrix = tritanopiaMatrix; - function loadComboBoxData(){ + function loadComboBoxData() { let colorblindModes = Object.keys(filtersObject); for (let i = 0; i < colorblindModes.length; i++) { @@ -45,7 +45,8 @@ export function uiSectionColorblindModeOptions(context) { loadComboBoxData(); const section = uiSection(context, 'preferences-colorblind-mode-options') - .label(l10n.tHtml('preferences.colorblind_options.title')) + .label(l10n.t('preferences.colorblind_options.title')) + .initHide(true) .disclosureContent(renderDisclosureContent); const colorblindCombo = uiCombobox(context, 'colorblind-mode-options'); diff --git a/scripts/build_data.js b/scripts/build_data.js index 4c28000ab7..40e5feb855 100644 --- a/scripts/build_data.js +++ b/scripts/build_data.js @@ -99,6 +99,8 @@ function buildData() { minifySync('data/qa_data.json', 'dist/data/qa_data.min.json'); minifySync('data/shortcuts.json', 'dist/data/shortcuts.min.json'); minifySync('data/territory_languages.json', 'dist/data/territory_languages.min.json'); + minifySync('data/color_schemes.json', 'dist/data/color_schemes.min.json'); + minifySync('data/styles.json', 'dist/data/styles.min.json'); return _currBuild = Promise.resolve(true) .then(() => {