diff --git a/css/80_app.css b/css/80_app.css index 95c1219982..bedf5962fd 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -2943,23 +2943,22 @@ img.tag-reference-wiki-image { border-radius: 0; pointer-events: auto; } - +/* .map-control > button:not(.disabled):focus, .map-control > button:not(.disabled):active { background: rgba(0, 0, 0, .8); } .map-control > button.active, .map-control > button.active:active { - background: #7092ff; -} - -@media (hover: hover) { - .map-control > button:not(.disabled):hover { - background: rgba(0, 0, 0, .8); - } - .map-control > button.active:hover { background: #7092ff; - } +} */ +@media (hover: hover) { + /* .map-control > button:not(.disabled):hover { + background: rgba(0, 0, 0, .8); + } + .map-control > button.active:hover { + background: #7092ff; + } */ } .map-control > button.disabled .icon { @@ -4202,6 +4201,19 @@ li.issue-fix-item button:not(.actionable) .fix-icon { pointer-events: none; } +.snowflake-overlay { + position: absolute; + z-index: 3; + top: 0; + left: 0; + right: 0; + bottom: 0; + width: 100%; + height: 100%; + pointer-events: none; + touch-action: none; +} + .overlay * { pointer-events: auto; } diff --git a/css/80_app_fb.css b/css/80_app_fb.css index 3a1ed49622..c89b1853d6 100644 --- a/css/80_app_fb.css +++ b/css/80_app_fb.css @@ -1076,3 +1076,171 @@ img.rapid-catalog-dataset-thumbnail.inverted { margin: 4px; border-radius: 5px; } + + +/* Background layer list customizations */ +ul.layer-list.layer-background-list li button.background-favorite-button { + background-color: inherit; +} +button.background-favorite-button .icon { + fill-opacity: 0; + stroke-width: 1; +} +button.background-favorite-button.active .icon { + fill-opacity: inherit; +} +.background-pane .layer-list button.background-favorite-button { + border: 0; +} + +li.best > label > span.best > svg { + color: #e98733; + height: 19px; +} + + +/* 3D Map +------------------------------------------------------- */ +.three-d-map { + position: absolute; + overflow: hidden; + width: 300px; + height: 300px; + right: 0; + bottom: 0; + z-index: 1; + margin-right: 30px; + border: #aaa 1px solid; + border-radius: 6px; + box-shadow: rgb(0 0 0 / 42%) 0px 0px 0px 3px; +} + +.ideditor[dir='ltr'] .map-in-map { + left: 10px; +} +.ideditor[dir='rtl'] .map-in-map { + right: 10px; +} + +div.holiday-banner { + color: rgba(252, 251, 233, 0.85); + font-family: 'Great Vibes', cursive; + font-weight: 400; + margin: 0 0 .8em 0; + font-size: 3.3em; + width: 300px; + text-align: center; + margin-top: -15px; + text-shadow: 0 0 80px rgba(223, 221, 170, 0.85),0 0 30px rgba(252, 249, 233, 0.85),0 0 6px DarkRed; +} +div.card-container-yule_log { + padding-bottom: 0px; + width:400px; + height:250px; +} + +div.card-content.card-content-yule_log { + background:url('https://media.giphy.com/media/ZHXz9MZbJI1YA/giphy.gif'); + height: 100%; +} +div.holiday-banner.smaller { + margin: 0 0 .8em 0; + font-size: 2.5em; + width: 300px; + font-weight: 300; + margin-top: -5px; +} + +.bar-button::before { + content: ''; + display: block; + position: absolute; + top: -7px; + left: -3px; + right: 0; + height: 23px; + background: url('img/snowcover1.png') no-repeat 0 0, + url('img/snowcover2.png') no-repeat 50% 0, + url('img/snowcover3.png') no-repeat 100% 0; +} + +.bar-button:nth-child(odd) { + box-shadow: 0px 0px 10px 3px #f00; + background: #f00; + animation: red-light-glow 3s ease-in-out alternate infinite; +} + +.bar-button:nth-child(even), +.bar-button.ai-features-toggle { + box-shadow: 0px 0px 10px 3px #0f08; + background: #0f08; + animation: green-light-glow 3s 1.5s ease-in-out alternate infinite; +} + + +@keyframes red-light-glow { + 0% { + box-shadow: 0px 0px 10px 6px #f00; + background: #f00; + } + 100% { + box-shadow: 0px 0px 6px 3px #f008; + background: #f008; + } +} + + +@keyframes green-light-glow { + 0% { + box-shadow: 0px 0px 6px 3px #0f08; + background: #0f08; + } + 100% { + box-shadow: 0px 0px 10px 6px #0f0; + background: #0f0; + } +} + +div.map-controls .map-control { + background: #aaaaaa88; + border-radius: 4px; +} + +div.map-controls > .map-control > button > svg { + display:none; +} + +div.map-controls div.bearing_n { + display:none; +} + +div.map-control.bearing button { + background: url('img/holiday_reset_bearing.png') +} +div.map-control.help-control button { + background: url('img/holiday_help.svg') +} +div.map-control.preferences-control button { + background: url('img/holiday_preferences.svg') +} +div.map-control.issues-control button { + background: url('img/holiday_issues.svg') +} +div.map-control.map-data-control button { + background: url('img/holiday_map-data.svg') +} +div.map-control.background-control button { + background: url('img/holiday_background.svg') +} +div.map-control.geolocate button { + background: url('img/holiday_geolocate.svg') +} +div.map-control.zoom-to-selection button { + background: url('img/holiday_zoom-to-selection.svg') +} +div.map-control.zoombuttons button.zoom-in { + background: url('img/holiday_zoom-in.svg') +} +div.map-control.zoombuttons button.zoom-out { + background: url('img/holiday_zoom-out.svg') +} diff --git a/data/core.yaml b/data/core.yaml index 9aec08fbe8..2d2e9a3c7d 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -621,6 +621,9 @@ en: note_created_date: Created Date note_created_user: Created By note_link_text: Note on openstreetmap.org + yule_log: + title: Yule Log + key: Y location: title: Location unknown_location: Unknown Location @@ -2786,6 +2789,9 @@ en: toggle_measurement_card: label: Toggle measurement panel key: M # + + to toggle the Measurement Info panel + toggle_yule_log: + label: Toggle Yule Log panel + key: Y # + + to toggle the Yule Log panel units: feet: "{quantity} ft" diff --git a/data/shortcuts.json b/data/shortcuts.json index f8e47b237b..9c7c24451f 100644 --- a/data/shortcuts.json +++ b/data/shortcuts.json @@ -434,6 +434,11 @@ "modifiers": ["⌘", "⇧"], "shortcuts": ["shortcuts.command.toggle_measurement_card.key"], "text": "shortcuts.command.toggle_measurement_card.label" + }, + { + "modifiers": ["⌘", "⇧"], + "shortcuts": ["shortcuts.command.toggle_yule_log.key"], + "text": "shortcuts.command.toggle_yule_log.label" } ] } diff --git a/dist/img/holiday_candy_cane_cursor.png b/dist/img/holiday_candy_cane_cursor.png new file mode 100644 index 0000000000..824bd8fdec Binary files /dev/null and b/dist/img/holiday_candy_cane_cursor.png differ diff --git a/dist/img/holiday_christmas_tree_cursor.png b/dist/img/holiday_christmas_tree_cursor.png new file mode 100644 index 0000000000..e0c5e09241 Binary files /dev/null and b/dist/img/holiday_christmas_tree_cursor.png differ diff --git a/dist/img/holiday_gingerbread_man_cursor.png b/dist/img/holiday_gingerbread_man_cursor.png new file mode 100644 index 0000000000..7d2c54d34d Binary files /dev/null and b/dist/img/holiday_gingerbread_man_cursor.png differ diff --git a/dist/img/holiday_reset_bearing.png b/dist/img/holiday_reset_bearing.png new file mode 100644 index 0000000000..755544d961 Binary files /dev/null and b/dist/img/holiday_reset_bearing.png differ diff --git a/dist/img/holiday_santa_claus_cursor.png b/dist/img/holiday_santa_claus_cursor.png new file mode 100644 index 0000000000..935ed63933 Binary files /dev/null and b/dist/img/holiday_santa_claus_cursor.png differ diff --git a/dist/img/holiday_santa_sleigh_cursor.png b/dist/img/holiday_santa_sleigh_cursor.png new file mode 100644 index 0000000000..ebb1922a05 Binary files /dev/null and b/dist/img/holiday_santa_sleigh_cursor.png differ diff --git a/dist/img/icons/snowflake1.png b/dist/img/icons/snowflake1.png new file mode 100644 index 0000000000..95507dcb65 Binary files /dev/null and b/dist/img/icons/snowflake1.png differ diff --git a/dist/img/icons/snowflake2.png b/dist/img/icons/snowflake2.png new file mode 100644 index 0000000000..04ae5d8e74 Binary files /dev/null and b/dist/img/icons/snowflake2.png differ diff --git a/dist/img/icons/snowflake3.png b/dist/img/icons/snowflake3.png new file mode 100644 index 0000000000..b613e3f3ca Binary files /dev/null and b/dist/img/icons/snowflake3.png differ diff --git a/dist/img/icons/snowflake4.png b/dist/img/icons/snowflake4.png new file mode 100644 index 0000000000..5966ff7a3c Binary files /dev/null and b/dist/img/icons/snowflake4.png differ diff --git a/dist/img/icons/snowflake5.png b/dist/img/icons/snowflake5.png new file mode 100644 index 0000000000..03183564ef Binary files /dev/null and b/dist/img/icons/snowflake5.png differ diff --git a/dist/img/icons/snowflake6.png b/dist/img/icons/snowflake6.png new file mode 100644 index 0000000000..8256638581 Binary files /dev/null and b/dist/img/icons/snowflake6.png differ diff --git a/dist/img/icons/snowflake7.png b/dist/img/icons/snowflake7.png new file mode 100644 index 0000000000..9edf3e8ce0 Binary files /dev/null and b/dist/img/icons/snowflake7.png differ diff --git a/dist/img/icons/teapot.png b/dist/img/icons/teapot.png new file mode 100644 index 0000000000..c4051d2667 Binary files /dev/null and b/dist/img/icons/teapot.png differ diff --git a/holidays/holiday_background.svg b/holidays/holiday_background.svg new file mode 100644 index 0000000000..d5c713f691 --- /dev/null +++ b/holidays/holiday_background.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_geolocate.svg b/holidays/holiday_geolocate.svg new file mode 100644 index 0000000000..058aad6178 --- /dev/null +++ b/holidays/holiday_geolocate.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_help.svg b/holidays/holiday_help.svg new file mode 100644 index 0000000000..197f2a8a0c --- /dev/null +++ b/holidays/holiday_help.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_issues.svg b/holidays/holiday_issues.svg new file mode 100644 index 0000000000..82d4d8a0e1 --- /dev/null +++ b/holidays/holiday_issues.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_map-data.svg b/holidays/holiday_map-data.svg new file mode 100644 index 0000000000..b58b5deefd --- /dev/null +++ b/holidays/holiday_map-data.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_preferences.svg b/holidays/holiday_preferences.svg new file mode 100644 index 0000000000..8dbe55f1df --- /dev/null +++ b/holidays/holiday_preferences.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_zoom-in.svg b/holidays/holiday_zoom-in.svg new file mode 100644 index 0000000000..7f43419b6e --- /dev/null +++ b/holidays/holiday_zoom-in.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_zoom-out.svg b/holidays/holiday_zoom-out.svg new file mode 100644 index 0000000000..8601dd3067 --- /dev/null +++ b/holidays/holiday_zoom-out.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/holiday_zoom-to-selection.svg b/holidays/holiday_zoom-to-selection.svg new file mode 100644 index 0000000000..cc2a4a242a --- /dev/null +++ b/holidays/holiday_zoom-to-selection.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/holidays/santa-hand.svg b/holidays/santa-hand.svg new file mode 100644 index 0000000000..8c432cb146 --- /dev/null +++ b/holidays/santa-hand.svg @@ -0,0 +1,19 @@ + + santa-hand + + + + + + + + + + + + + + + + + diff --git a/holidays/snowcover1.png b/holidays/snowcover1.png new file mode 100644 index 0000000000..5adfe509c9 Binary files /dev/null and b/holidays/snowcover1.png differ diff --git a/holidays/snowcover2.png b/holidays/snowcover2.png new file mode 100644 index 0000000000..6ae5cd9452 Binary files /dev/null and b/holidays/snowcover2.png differ diff --git a/holidays/snowcover3.png b/holidays/snowcover3.png new file mode 100644 index 0000000000..367046101e Binary files /dev/null and b/holidays/snowcover3.png differ diff --git a/holidays/tree.svg b/holidays/tree.svg new file mode 100644 index 0000000000..61fc9266f0 --- /dev/null +++ b/holidays/tree.svg @@ -0,0 +1,426 @@ + + + + + + + + + + + + + + + + tree + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/core/MapSystem.js b/modules/core/MapSystem.js index 5ad4947a37..ed8aa1c2eb 100644 --- a/modules/core/MapSystem.js +++ b/modules/core/MapSystem.js @@ -3,7 +3,7 @@ import { DEG2RAD, RAD2DEG, TAU, Extent, Viewport, geoMetersToLon, geoZoomToScale, numClamp, numWrap, vecAdd, vecRotate, vecSubtract } from '@rapid-sdk/math'; - +import { PixiLayerSnowflakes } from '../pixi/PixiLayerSnowflakes.js'; import { AbstractSystem } from './AbstractSystem.js'; import { QAItem } from '../osm/index.js'; import { utilTotalExtent } from '../util/index.js'; @@ -279,6 +279,11 @@ export class MapSystem extends AbstractSystem { $$supersurface .append(() => gfx.overlay) .attr('class', 'overlay'); + + const $$snowOverlay = $$mainmap.append('div').attr('class', 'snowflake-overlay'); + $$snowOverlay.append('canvas').attr('class', 'snowflake-layer'); + + this.snowflakes = new PixiLayerSnowflakes(context, 'snowflakes'); } diff --git a/modules/pixi/PixiEvents.js b/modules/pixi/PixiEvents.js index 96a7501076..3b72ff289d 100644 --- a/modules/pixi/PixiEvents.js +++ b/modules/pixi/PixiEvents.js @@ -159,16 +159,21 @@ export class PixiEvents extends EventEmitter { const path = this.context.assetPath; const surface = this.gfx.surface; - const cursors = { - areaCursor: `url(${path}img/cursor-select-area.png), pointer`, - connectLineCursor: `url(${path}img/cursor-draw-connect-line.png) 9 9, crosshair`, - connectVertexCursor: `url(${path}img/cursor-draw-connect-vertex.png) 9 9, crosshair`, - lineCursor: `url(${path}img/cursor-select-line.png), pointer`, - pointCursor: `url(${path}img/cursor-select-point.png), pointer`, - selectSplitCursor: `url(${path}img/cursor-select-split.png), pointer`, - vertexCursor: `url(${path}img/cursor-select-vertex.png), pointer`, + // christmas tree https://www.vecteezy.com/png/9346313-hand-drawn-christmas-tree + // candy cane https://www.vecteezy.com/png/11016194-christmas-toy-lollipop-red-and-white-cane + // ginger bread man https://www.vecteezy.com/png/9399803-gingerbread-man-clipart-design-illustration + // santa https://www.vecteezy.com/png/15693545-santa-claus-christmas-cartoon-character + + const cursors = { + connectLineCursor: `url(${path}/img/holiday_candy_cane_cursor.png) 5 5, crosshair`, + connectVertexCursor: `url(${path}/img/holiday_santa_claus_cursor.png) 5 5, crosshair`, + lineCursor:`url(${path}/img/holiday_santa_sleigh_cursor.png) 5 5, pointer`, + vertexCursor: `url(${path}/img/holiday_gingerbread_man_cursor.png) 5 5, pointer`, + pointCursor:`url(${path}/img/holiday_candy_cane_cursor.png) 5 5, pointer`, + areaCursor:`url(${path}/img/holiday_christmas_tree_cursor.png) 9 9, pointer`, }; + switch (style) { case 'areaCursor': surface.style.cursor = cursors.areaCursor; diff --git a/modules/pixi/PixiLayerSnowflakes.js b/modules/pixi/PixiLayerSnowflakes.js new file mode 100644 index 0000000000..eeb5dcc71d --- /dev/null +++ b/modules/pixi/PixiLayerSnowflakes.js @@ -0,0 +1,152 @@ +import * as PIXI from 'pixi.js'; + +import { AbstractLayer } from './AbstractLayer'; +import * as particles from '@barvynkoa/particle-emitter'; +import { snowflakesConfig } from './PixiLayerSnowflakesConfig'; +import { config } from 'chai'; +/** + * PixiLayerSnowflakes + * This class has only one job- to fill the screen with snowflakes and holiday cheer. + * It consists of a snowflake particle emitter drawn in a container above everything else in the map. + * For performance, we use a ParticleContainer (this also makes it so the snowflakes don't intercept pointer events) + * + * @class + */ +export class PixiLayerSnowflakes { + + /** + * @constructor + * @param context top-level context + * @param layerID Unique string to use for the name of this Layer + */ + constructor(context, layerID) { + this.context = context; + // Disable mipmapping, we always want textures near the resolution they are at. + // PIXI.settings.MIPMAP_TEXTURES = PIXI.MIPMAP_MODES.OFF; + + // Prefer WebGL 2.0 for now, this is to workaround issue #493 for now. + // PIXI.settings.PREFER_ENV = PIXI.ENV.WEBGL2; + + const overlay = this.context.container().select('.snowflake-overlay'); + const surface = this.context.container().select('.snowflake-layer'); + + // Create a Pixi application rendering to the given surface `canvas` + // Which in this case is an entirely separate canvas from the main map supersurface + this.pixi = new PIXI.Application(); + + + this.pixi.init({ + antialias: true, + autoDensity: true, + autoStart: false, // don't start the ticker yet + resizeTo: overlay.node(), + resolution: window.devicePixelRatio, + backgroundAlpha: 0, + sharedLoader: true, + sharedTicker: true, + view: surface.node(), + }).then(() => { + this.pixi.renderer.resize(window.innerWidth, window.innerHeight); + + //Debug circle code + // const circle = new PIXI.Graphics() + // .circle(0, 0, 200) + // .fill({ color: 0xffffff, alpha: 1 }); + + // circle.position.y = 400; + // circle.position.x = 400; + + // this.pixi.stage.addChild(circle); + + const container = new PIXI.Container(); + // container.setProperties({ + // scale: true, + // position: true, + // rotation: true, + // uvs: true, + // alpha: true, + // }); + container.label = layerID; + this.container = container; + this.elapsed = Date.now(); + this.pixi.stage.addChild(container); + this.pixi.renderer.resize(window.innerWidth, window.innerHeight); + this._enabled = true; // this layer should always be enabled + this._oldk = 0; + + //Now we need to modify the particle emitter config at runtime, so that it includes the dist folder. + //Otherwise, once we deploy to AWS this won't work. + let distModifiedConfig = snowflakesConfig; + // The 'textureRandom' behavior contains the texture path we need to reference. + let snowflakeImagePath = distModifiedConfig.behaviors.filter(behavior => behavior.type === 'textureRandom')[0].config; + // Modify each path by prepending the asset path. + snowflakeImagePath.textures = snowflakeImagePath.textures.map(texture => texture = this.context.assetPath + texture); + + const snowflakepaths = [ + 'img/icons/snowflake1.png', + 'img/icons/snowflake2.png', + 'img/icons/snowflake3.png', + 'img/icons/snowflake4.png', + 'img/icons/snowflake5.png', + 'img/icons/snowflake6.png', + 'img/icons/snowflake7.png', + 'img/icons/teapot.png', + ]; + + let snowflakePromises = snowflakepaths.map(path => PIXI.Assets.load(this.context.assetPath + path)); + + // let s1Promise = PIXI.Assets.load(this.context.assetPath + 'img/icons/snowflake.png'); + // let s2Promise = PIXI.Assets.load(this.context.assetPath + 'img/icons/snowflake2.png'); + Promise.all(snowflakePromises) + .then(() => { + this.emitter = new particles.Emitter(container, distModifiedConfig); + this.emitter.parent = container; + + + const ticker = this.pixi.ticker; //Thank goodness for shared tickers + ticker.add(this.render, this); + }); + }); + } + + + /** + * enabled + * This layer should always be enabled - it contains important UI stuff + */ + get enabled() { + return this._enabled; + } + set enabled(val) { + // noop + } + + + /** + * render + * Render any of the child containers for UI that should float over the map. + * @param frame Integer frame being rendered + * @param projection Pixi projection to use for rendering + */ + render() { + const now = Date.now(); + if (this.emitter) { + // update emitter (convert to seconds) + this.emitter.update((now - this.elapsed) * 0.001); + } + + // call update hook for specialist examples + if (this.updateHook) { + this.updateHook(now - this.elapsed); + } + + + this.pixi.renderer.render(this.pixi.stage); + this.elapsed = now; +// let snowflakePosition = this.pixi.stage.children[0].children[0].position; + +// console.log(`snowflake position: [${snowflakePosition._x},${snowflakePosition._y}]`); + + } + +} diff --git a/modules/pixi/PixiLayerSnowflakesConfig.js b/modules/pixi/PixiLayerSnowflakesConfig.js new file mode 100644 index 0000000000..7da4a6d2ed --- /dev/null +++ b/modules/pixi/PixiLayerSnowflakesConfig.js @@ -0,0 +1,112 @@ +export const snowflakesConfig = { + 'lifetime': { + 'min': 8, + 'max': 8 + }, + 'ease': [ + { + 's': 0, + 'cp': 0.379, + 'e': 0.548 + }, + { + 's': 0.548, + 'cp': 0.717, + 'e': 0.676 + }, + { + 's': 0.676, + 'cp': 0.635, + 'e': 1 + } + ], + 'frequency': 0.004, + 'emitterLifetime': 0, + 'maxParticles': 3000, + 'addAtBack': true, + 'pos': { + 'x': 0, + 'y': 0 + }, + 'behaviors': [ + { + 'type': 'alpha', + 'config': { + 'alpha': { + 'list': [ + { + 'time': 0, + 'value': 0.73 + }, + { + 'time': 5, + 'value': 0.46 + } + ] + } + } + }, + { + 'type': 'moveSpeedStatic', + 'config': { + 'min': 100, + 'max': 200 + } + }, + { + 'type': 'scale', + 'config': { + 'scale': { + 'list': [ + { + 'time': 0, + 'value': 0.15 + }, + { + 'time': 1, + 'value': 0.2 + } + ] + }, + 'minMult': 0.5 + } + }, + { + 'type': 'rotation', + 'config': { + 'accel': 0, + 'minSpeed': 0, + 'maxSpeed': 200, + 'minStart': 50, + 'maxStart': 70 + } + }, + { + 'type': 'textureRandom', + 'config': { + 'textures': [ + 'img/icons/snowflake1.png', + 'img/icons/snowflake2.png', + 'img/icons/snowflake3.png', + 'img/icons/snowflake4.png', + 'img/icons/snowflake5.png', + 'img/icons/snowflake6.png', + 'img/icons/snowflake7.png', + 'img/icons/teapot.png', + ] + } + }, + { + 'type': 'spawnShape', + 'config': { + 'type': 'rect', + 'data': { + 'x': -100, + 'y': -100, + 'w': 1280, + 'h': 1024 + } + } + } + ] +}; diff --git a/modules/pixi/PixiScene.js b/modules/pixi/PixiScene.js index 86265c2bff..6c42a18ef3 100644 --- a/modules/pixi/PixiScene.js +++ b/modules/pixi/PixiScene.js @@ -110,7 +110,7 @@ export class PixiScene extends EventEmitter { new PixiLayerLabels(this, 'labels'), new PixiLayerEditBlocks(this, 'edit-blocks'), - new PixiLayerMapUI(this, 'map-ui') + new PixiLayerMapUI(this, 'map-ui'), ].forEach(layer => this.layers.set(layer.id, layer)); } diff --git a/modules/ui/UiInfoCards.js b/modules/ui/UiInfoCards.js index 42127fda59..2ff3d3a655 100644 --- a/modules/ui/UiInfoCards.js +++ b/modules/ui/UiInfoCards.js @@ -4,6 +4,7 @@ import { UiBackgroundCard } from './cards/UiBackgroundCard.js'; import { UiHistoryCard } from './cards/UiHistoryCard.js'; import { UiLocationCard } from './cards/UiLocationCard.js'; import { UiMeasurementCard } from './cards/UiMeasurementCard.js'; +import { UiYuleLogCard } from './cards/UiYuleLogCard.js'; import { utilCmd } from '../util/cmd.js'; @@ -30,13 +31,15 @@ export class UiInfoCards { this.HistoryCard = new UiHistoryCard(context); this.LocationCard = new UiLocationCard(context); this.MeasurementCard = new UiMeasurementCard(context); + this.YuleLogCard = new UiYuleLogCard(context); // Info Cards this.cards = [ this.BackgroundCard, this.HistoryCard, this.LocationCard, - this.MeasurementCard + this.MeasurementCard, + this.YuleLogCard ]; // D3 selections diff --git a/modules/ui/cards/UiYuleLogCard.js b/modules/ui/cards/UiYuleLogCard.js new file mode 100644 index 0000000000..9193a2f296 --- /dev/null +++ b/modules/ui/cards/UiYuleLogCard.js @@ -0,0 +1,112 @@ +import { selection } from 'd3-selection'; +import debounce from 'lodash-es/debounce'; + +import { AbstractUiCard } from './AbstractUiCard'; +import { uiIcon } from '../icon.js'; +import { utilCmd } from '../../util/cmd.js'; + + + +/** + * UiYuleLogCard + */ +export class UiYuleLogCard extends AbstractUiCard { + + /** + * @constructor + * @param `context` Global shared application context + */ + constructor(context) { + super(context); + this.id = 'yule_log'; + + const l10n = context.systems.l10n; + this.label = l10n.tHtml('info_panels.yule_log.title'); + this.key = l10n.t('info_panels.yule_log.key'); + + this._setupKeybinding = this._setupKeybinding.bind(this); + + // Ensure methods used as callbacks always have `this` bound correctly. + // (This is also necessary when using `d3-selection.call`) + this.render = this.render.bind(this); + this._deferredRender = debounce(this.render, 250); + + l10n + .on('localechange', this._setupKeybinding); + + this._setupKeybinding(); + } + + + /** + * render + * Accepts a parent selection, and renders the content under it. + * (The parent selection is required the first time, but can be inferred on subsequent renders) + * @param {d3-selection} $parent - A d3-selection to a HTMLElement that this component should render itself into + */ + render($parent = this.$parent) { + if ($parent instanceof selection) { + this.$parent = $parent; + } else { + return; // no parent - called too early? + } + + const context = this.context; + const l10n = context.systems.l10n; + + if (!this.visible) return; + + // .card-container + let $wrap = $parent.selectAll('.card-container') + .data([this.id], d => d); + + // enter + const $$wrap = $wrap.enter() + .append('div') + .attr('class', d => `fillD2 card-container card-container-${d}`); + + const $$title = $$wrap + .append('div') + .attr('class', 'fillD2 card-title'); + + $$title + .append('h3'); + + $$title + .append('button') + .attr('class', 'close') + .on('click', this.toggle) + .call(uiIcon('#rapid-icon-close')); + + $$wrap + .append('div') + .attr('class', d => `card-content card-content-${d}`) + .append('ul') + .attr('class', 'yule-log'); + + // update + this.$wrap = $wrap = $wrap.merge($$wrap); + + $wrap.selectAll('h3') + .text(l10n.t('info_panels.yule_log.title')); + + } + + + /** + * _setupKeybinding + * This sets up the keybinding, replacing existing if needed + */ + _setupKeybinding() { + const context = this.context; + const keybinding = context.keybinding(); + const l10n = context.systems.l10n; + + if (Array.isArray(this._keys)) { + keybinding.off(this._keys); + } + + this._keys = [utilCmd('⌘⇧' + l10n.t('shortcuts.command.toggle_yule_log.key'))]; + context.keybinding().on(this._keys, this.toggle); + } +} diff --git a/modules/ui/cards/index.js b/modules/ui/cards/index.js index e2c5fe27cb..b86434b6c1 100644 --- a/modules/ui/cards/index.js +++ b/modules/ui/cards/index.js @@ -3,3 +3,4 @@ export * from './UiBackgroundCard.js'; export * from './UiHistoryCard.js'; export * from './UiLocationCard.js'; export * from './UiMeasurementCard.js'; +export * from './UiYuleLogCard.js'; diff --git a/modules/ui/tools/fb_banner_1.js b/modules/ui/tools/fb_banner_1.js new file mode 100644 index 0000000000..e3c0813b7c --- /dev/null +++ b/modules/ui/tools/fb_banner_1.js @@ -0,0 +1,28 @@ + +export function uiToolFbBannerOne(context) { + + var tool = { + id: 'fb_banner_1', + }; + + + tool.install = function(selection) { + + var banner = selection.selectAll('div#worldai-holiday-banner-start').data([0]); + + banner.exit() + .remove(); + + var bannerEnter = banner.enter() + .append('div') + .attr('id', 'worldai-holiday-banner-start') + .attr('class', 'holiday-banner') + .text('Mappy Holidays'); + + banner + .merge(bannerEnter); + + }; + + return tool; +} diff --git a/modules/ui/tools/fb_banner_2.js b/modules/ui/tools/fb_banner_2.js new file mode 100644 index 0000000000..f112355107 --- /dev/null +++ b/modules/ui/tools/fb_banner_2.js @@ -0,0 +1,22 @@ +export function uiToolFbBannerTwo() { + + var tool = { + id: 'fb_banner_2' + }; + + + tool.install = function(selection) { + var banner = selection.selectAll('div#worldai-holiday-banner-end').data([0]); + + var bannerEnter = banner.enter() + .append('div') + .attr('id', 'worldai-holiday-banner-end') + .attr('class', 'holiday-banner smaller') + .text('from the Rapid team'); + + banner + .merge(bannerEnter); + }; + + return tool; +} diff --git a/modules/ui/tools/index.js b/modules/ui/tools/index.js index e76a5cfd22..eef81ee406 100644 --- a/modules/ui/tools/index.js +++ b/modules/ui/tools/index.js @@ -3,3 +3,5 @@ export * from './UiDrawModesTool.js'; export * from './UiRapidTool.js'; export * from './UiSaveTool.js'; export * from './UiUndoRedoTool.js'; +export * from './fb_banner_1'; +export * from './fb_banner_2'; diff --git a/modules/ui/top_toolbar.js b/modules/ui/top_toolbar.js new file mode 100644 index 0000000000..af35132e41 --- /dev/null +++ b/modules/ui/top_toolbar.js @@ -0,0 +1,85 @@ +import { select as d3_select } from 'd3-selection'; + +import { + uiToolRapidFeatures, uiToolDrawModes, uiToolNotes, uiToolSave, + uiToolSidebarToggle, uiToolUndoRedo, uiToolDownloadOsc, uiToolFbBannerOne, uiToolFbBannerTwo +} from './tools'; + + +export function uiTopToolbar(context) { + const sidebarToggle = uiToolSidebarToggle(context); + const rapidFeatures = uiToolRapidFeatures(context); + const bannerOne = uiToolFbBannerOne(context); + const bannerTwo = uiToolFbBannerTwo(context); + const modes = uiToolDrawModes(context); + const notes = uiToolNotes(context); + const undoRedo = uiToolUndoRedo(context); + const save = uiToolSave(context); + const downloadOsc = uiToolDownloadOsc(context); + + + function notesEnabled() { + return context.scene().layers.get('notes')?.enabled; + } + + + function update(selection) { + let tools = [sidebarToggle, 'spacer', bannerOne, modes, rapidFeatures, bannerTwo]; + + if (notesEnabled()) { + tools.push('spacer', notes); + } + + tools.push('spacer', undoRedo, save); + + // Undocumented feature 'support_download_osc' + const hash = context.systems.urlhash.initialHashParams; + if (hash.get('support_download_osc') === 'true') { + tools.push(downloadOsc); + } + + let toolbarItems = selection.selectAll('.toolbar-item') + .data(tools, d => d.id || d); + + // exit + toolbarItems.exit() + .each(d => { + if (d.uninstall) d.uninstall(); + }) + .remove(); + + // enter + let itemsEnter = toolbarItems + .enter() + .append('div') + .attr('class', d => { + let classes = 'toolbar-item ' + (d.id || d).replace('_', '-'); + if (d.klass) classes += ' ' + d.klass; + return classes; + }); + + let actionableEnter = itemsEnter + .filter(d => d !== 'spacer'); + + actionableEnter + .append('div') + .attr('class', 'item-content') + .each((d, i, nodes) => { + d3_select(nodes[i]) + .call(d.install, selection); + }); + + actionableEnter + .append('div') + .attr('class', 'item-label') + .html(d => d.label); + } + + + function init(selection) { + context.scene().on('layerchange', () => update(selection)); + update(selection); + } + + return init; +} diff --git a/package.json b/package.json index fc9db3fad8..92e655274c 100644 --- a/package.json +++ b/package.json @@ -25,9 +25,7 @@ "build:bundle:modern:dev": "dotenvx run --quiet -- node config/esbuild.config.modern-dev.js", "clean": "shx rm -f dist/esbuild.json dist/*.js dist/*.map dist/*.css dist/img/*-sprite.svg", "dist": "run-p dist:**", - "dist:bundle:legacy:dev": "dotenvx run --quiet -- node config/esbuild.config.legacy-dev.js", "dist:bundle:modern:prod": "dotenvx run --quiet -- node config/esbuild.config.modern-prod.js", - "dist:bundle:legacy:prod": "dotenvx run --quiet -- node config/esbuild.config.legacy-prod.js", "dist:svg:community": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"community-%s\" --symbol-sprite dist/img/community-sprite.svg node_modules/osm-community-index/dist/img/*.svg", "dist:svg:fa": "svg-sprite --symbol --symbol-dest . --symbol-sprite dist/img/fa-sprite.svg svg/fontawesome/*.svg", "dist:svg:maki": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"maki-%s\" --symbol-sprite dist/img/maki-sprite.svg node_modules/@mapbox/maki/icons/*.svg", @@ -36,6 +34,7 @@ "dist:svg:rapid": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"rapid-%s\" --symbol-sprite dist/img/rapid-sprite.svg \"svg/rapid-sprite/**/*.svg\"", "dist:svg:roentgen": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"roentgen-%s\" --symbol-sprite dist/img/roentgen-sprite.svg svg/roentgen/*.svg", "dist:svg:temaki": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"temaki-%s\" --symbol-sprite dist/img/temaki-sprite.svg node_modules/@rapideditor/temaki/icons/*.svg", + "dist:svg:christmas": "shx cp holidays/* dist/img", "docs": "jsdoc -c webdoc.conf.json -r", "imagery": "dotenvx run --quiet -- node scripts/update_imagery.js", "lint": "eslint modules test", @@ -55,6 +54,7 @@ "translations": "dotenvx run --quiet -- node scripts/tx_pull.js" }, "dependencies": { + "@barvynkoa/particle-emitter": "^0.0.1", "@mapbox/geojson-area": "^0.2.2", "@mapbox/geojson-rewind": "^0.5.2", "@mapbox/polylabel": "1.0.2",