diff --git a/CHANGELOG.md b/CHANGELOG.md index 41a97b2371a..b015668ad71 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -60,6 +60,7 @@ _Breaking developer changes, which may affect downstream projects or sites that ##### 2026-03-02 #### :sparkles: Usability & Accessibility +* Add support for multiple custom background layers with full CRUD operations - add, edit, name, and delete custom backgrounds ([#8874], [#11850], thanks [@JaiswalShivang]) * Show warning when attempting to paste but nothing has been copied ([#9401], thanks [@omsaraykar]) * Don't suggest values from Taginfo for `addr:*` tags ([#11733], thanks [@k-yle]) * Don't suggest values from Taginfo for tags with less than 100 uses, even if they're documented on the wiki ([#11794], thanks [@Kaushik4141]) @@ -108,6 +109,10 @@ _Breaking developer changes, which may affect downstream projects or sites that [#8464]: https://github.com/openstreetmap/iD/issues/8464 +[#8874]: https://github.com/openstreetmap/iD/issues/8874 +[#9401]: https://github.com/openstreetmap/iD/issues/9401 +[#10935]: https://github.com/openstreetmap/iD/issues/10935 +[#10999]: https://github.com/openstreetmap/iD/issues/10999 [#9226]: https://github.com/openstreetmap/iD/pull/9226 [#9401]: https://github.com/openstreetmap/iD/issues/9401 [#10935]: https://github.com/openstreetmap/iD/issues/10935 @@ -129,12 +134,14 @@ _Breaking developer changes, which may affect downstream projects or sites that [#11759]: https://github.com/openstreetmap/iD/pull/11759 [#11773]:https://github.com/openstreetmap/iD/pull/11773 [#11774]: https://github.com/openstreetmap/iD/pull/11774 +[#11783]: https://github.com/openstreetmap/iD/pull/11783 [#11784]: https://github.com/openstreetmap/iD/pull/11784 [#11794]: https://github.com/openstreetmap/iD/pull/11794 [#11799]: https://github.com/openstreetmap/iD/issues/11799 [#11783]: https://github.com/openstreetmap/iD/pull/11783 [#11804]: https://github.com/openstreetmap/iD/pull/11804 [#11819]: https://github.com/openstreetmap/iD/pull/11819 +[#11850]: https://github.com/openstreetmap/iD/pull/11850 [#11861]: https://github.com/openstreetmap/iD/pull/11861 [#11862]: https://github.com/openstreetmap/iD/pull/11862 [#11869]: https://github.com/openstreetmap/iD/pull/11869 diff --git a/css/80_app.css b/css/80_app.css index 3a13c17acdd..baa986da224 100644 --- a/css/80_app.css +++ b/css/80_app.css @@ -1447,6 +1447,7 @@ button.preset-reset .label.flash-bg { .label-text .label-textannotation svg.icon { margin: 0 8px; + color: #333; opacity: 0.5; width: 14px; height: 14px; @@ -6086,3 +6087,87 @@ textarea:focus:dir(rtl)::-webkit-resizer { width: 100px; color: var(--link-color); } + +.custom-backgrounds-header { + font-weight: bold; + font-size: 12px; + padding: 10px 0 6px 0; + margin-top: 12px; + color: var(--text-color); +} + +.layer-custom-background-list { + margin: 0; + padding: 0; +} + +.layer-custom-background-list li { + display: flex; + align-items: center; + padding: 4px 0; +} + +.layer-custom-background-list li label { + flex: 1; + display: flex; + align-items: center; + cursor: pointer; +} + +.layer-custom-background-list li label span { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.layer-custom-background-list .layer-browse, +.layer-custom-background-list .layer-delete { + flex-shrink: 0; + width: 28px; + height: 28px; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + background: transparent; + border: none; + border-radius: 4px; + opacity: 0.6; + transition: opacity 0.15s, background-color 0.15s; +} + +.layer-custom-background-list .layer-browse:hover, +.layer-custom-background-list .layer-delete:hover { + opacity: 1; + background: var(--bg-color-3); +} + +.layer-custom-background-list .layer-delete:hover { + background: rgba(234, 0, 0, 0.15); +} + +.layer-custom-background-list .layer-delete:hover .icon { + color: #ea0000; +} + +.add-custom-background { + width: 100%; + padding: 8px 12px; + margin-top: 8px; + text-align: center; + background: var(--bg-color-3); + border: none; + border-radius: 4px; + font-size: 12px; + cursor: pointer; + transition: background-color 0.15s; +} + +.add-custom-background:hover { + background: var(--passive-bg-color); +} + +.settings-custom-background .field-name { + width: 100%; + margin-top: 10px; +} diff --git a/data/core.yaml b/data/core.yaml index 72c436368c0..9c26a3dd4d3 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -828,6 +828,10 @@ en: best_imagery: Best known imagery source for this location switch: Switch back to this background custom: Custom + custom_backgrounds: Custom Backgrounds + add_custom: Add Custom... + delete_custom: Delete + edit_custom: Edit overlays: Overlays imagery_problem_faq: Report an Imagery Problem reset: reset @@ -1006,6 +1010,9 @@ en: example: "Example:" template: placeholder: Enter a url template + name: + placeholder: Enter a name for this background + label: Name custom_data: tooltip: Edit custom data layer header: Custom Map Data Settings @@ -1087,6 +1094,7 @@ en: confirm: okay: "OK" cancel: "Cancel" + delete_custom_background: "Delete this custom background?" splash: welcome: Welcome to the iD OpenStreetMap editor text: "iD is a friendly but powerful tool for contributing to the world's best free world map. This is version {version}. For more information see {website} and report bugs at {github}." diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 5f9c2de479e..be9c09fb7bf 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -7,6 +7,7 @@ import turf_bbox from '@turf/bbox'; import whichPolygon from 'which-polygon'; import { prefs } from '../core/preferences'; +import { t } from '../core/localizer'; import { fileFetcher } from '../core/file_fetcher'; import { geoMetersToOffset, geoOffsetToMeters, geoExtent } from '../geo'; import { rendererBackgroundSource } from './background_source'; @@ -14,6 +15,9 @@ import { rendererTileLayer } from './tile_layer'; import { utilQsString, utilStringQs } from '../util'; import { utilRebind } from '../util/rebind'; +const PREF_CUSTOM_TEMPLATE_OLD = 'background-custom-template'; +const PREF_CUSTOM_TEMPLATES = 'background-custom-templates'; +export const CUSTOM_ID_PREFIX = 'custom-'; let _imageryIndex = null; @@ -77,13 +81,37 @@ export function rendererBackground(context) { // Add 'None' _imageryIndex.backgrounds.unshift(rendererBackgroundSource.None()); - // Add 'Custom' - let template = prefs('background-custom-template') || ''; - const custom = rendererBackgroundSource.Custom(template); - _imageryIndex.backgrounds.unshift(custom); + function migrateCustomBackgrounds() { + const oldTemplate = prefs(PREF_CUSTOM_TEMPLATE_OLD); + let customTemplates; - return _imageryIndex; + try { + customTemplates = JSON.parse(prefs(PREF_CUSTOM_TEMPLATES) || '[]'); + } catch { + customTemplates = []; + } + + // Migrate from old single custom background + if (oldTemplate && customTemplates.length === 0) { + customTemplates = [{ + id: CUSTOM_ID_PREFIX+'1', + name: t('background.custom'), + template: oldTemplate + }]; + prefs(PREF_CUSTOM_TEMPLATES, JSON.stringify(customTemplates)); + prefs(PREF_CUSTOM_TEMPLATE_OLD, null); + } + + return customTemplates; + } + // Add custom + const customTemplates = migrateCustomBackgrounds(); + customTemplates.forEach(function(entry) { + const customSource = rendererBackgroundSource.Custom(entry.id, entry.name, entry.template); + _imageryIndex.backgrounds.unshift(customSource); }); + return _imageryIndex; + }); } @@ -205,6 +233,10 @@ export function rendererBackground(context) { let id = currSource.id; if (id === 'custom') { id = `custom:${currSource.template()}`; + } else if (id && id.startsWith(CUSTOM_ID_PREFIX)) { + // Named custom backgrounds use custom: