@geogirafe/lib-geoportal 1.1.0-dev.2318696406 → 1.1.0-dev.2321641404

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -237,6 +237,8 @@ From:
237
237
  - https://www.svgrepo.com/svg/309943/select-all-off
238
238
  - https://www.svgrepo.com/svg/310303/arrow-forward
239
239
  - https://www.svgrepo.com/svg/310306/arrow-import
240
+ - https://www.svgrepo.com/svg/310006/star
241
+ - https://www.svgrepo.com/svg/310008/star-add
240
242
 
241
243
 
242
244
  The MIT License (MIT)
@@ -30,6 +30,7 @@ import WmsManager from '../tools/wms/wmsmanager.js';
30
30
  import IGirafeContext from '../tools/context/icontext.js';
31
31
  import ApiSessionManager from './apisessionmanager.js';
32
32
  import OnBoardingManager from '../tools/onboarding/onboardingmanager.js';
33
+ import ThemeFavoritesManager from '../tools/themes/themefavoritesmanager.js';
33
34
  export default class GirafeApiContext implements IGirafeContext {
34
35
  readonly userDataManager: UserDataManager;
35
36
  readonly configManager: ConfigManager;
@@ -62,6 +63,7 @@ export default class GirafeApiContext implements IGirafeContext {
62
63
  readonly ogcApiFeaturesManager: OgcApiFeaturesManager;
63
64
  readonly localFileManager: LocalFileManager;
64
65
  readonly onBoardingManager: OnBoardingManager;
66
+ readonly themeFavoritesManager: ThemeFavoritesManager;
65
67
  constructor();
66
68
  initialize(): Promise<void>;
67
69
  }
package/api/apicontext.js CHANGED
@@ -29,6 +29,7 @@ import WfsManager from '../tools/wfs/wfsmanager.js';
29
29
  import WmsManager from '../tools/wms/wmsmanager.js';
30
30
  import ApiSessionManager from './apisessionmanager.js';
31
31
  import OnBoardingManager from '../tools/onboarding/onboardingmanager.js';
32
+ import ThemeFavoritesManager from '../tools/themes/themefavoritesmanager.js';
32
33
  export default class GirafeApiContext {
33
34
  userDataManager;
34
35
  configManager;
@@ -61,6 +62,7 @@ export default class GirafeApiContext {
61
62
  ogcApiFeaturesManager;
62
63
  localFileManager;
63
64
  onBoardingManager;
65
+ themeFavoritesManager;
64
66
  constructor() {
65
67
  this.componentManager = new ComponentManager(this);
66
68
  this.userDataManager = new UserDataManager(this);
@@ -93,6 +95,7 @@ export default class GirafeApiContext {
93
95
  this.localFileManager = new LocalFileManager(this);
94
96
  this.ogcApiFeaturesManager = new OgcApiFeaturesManager(this);
95
97
  this.onBoardingManager = new OnBoardingManager(this);
98
+ this.themeFavoritesManager = new ThemeFavoritesManager(this);
96
99
  }
97
100
  async initialize() {
98
101
  // NOTE : This initialization order is important, because some singleton will need other ones !
@@ -129,5 +132,6 @@ export default class GirafeApiContext {
129
132
  this.localFileManager.initializeSingleton();
130
133
  this.ogcApiFeaturesManager.initializeSingleton();
131
134
  this.onBoardingManager.initializeSingleton();
135
+ this.themeFavoritesManager.initializeSingleton();
132
136
  }
133
137
  }
@@ -54,7 +54,7 @@
54
54
  "Control opacity": "Deckkraft steuern",
55
55
  "Copy ShortUrl": "Kurz-URL kopieren",
56
56
  "Copy value": "Wert kopieren",
57
- "Create custom theme": "Benutzerdefiniertes Thema erstellen",
57
+ "Create custom theme": "Thema erstellen",
58
58
  "cross-section-settings": "Querschnitt",
59
59
  "CSV export": "CSV-Export",
60
60
  "Current Feature": "Aktuelles Objekt",
@@ -415,6 +415,10 @@
415
415
  "Theme selection": "Themenauswahl",
416
416
  "Themes": "Themen",
417
417
  "themes": "themen",
418
+ "themes-add-remove-favorites": "Zu Favoriten hinzufügen/entfernen",
419
+ "themes-type-all": "Alle Themen",
420
+ "themes-type-custom": "Benutzerdefinierte",
421
+ "themes-type-favorites": "Favoriten",
418
422
  "This instance of GeoGirafe cannot be used at the moment.": "Diese Instanz von GeoGirafe kann momentan nicht verwendet werden.",
419
423
  "Toggle all legends": "Alle Legenden umschalten",
420
424
  "Toggle legend": "Legende umschalten",
@@ -417,6 +417,10 @@
417
417
  "Theme selection": "Theme selection",
418
418
  "Themes": "Themes",
419
419
  "themes": "themes",
420
+ "themes-add-remove-favorites": "Add/Remove to Favorites",
421
+ "themes-type-all": "All Themes",
422
+ "themes-type-custom": "Custom Themes",
423
+ "themes-type-favorites": "Favorite Themes",
420
424
  "This instance of GeoGirafe cannot be used at the moment.": "This instance of GeoGirafe cannot be used at the moment.",
421
425
  "Toggle all legends": "Toggle all legends",
422
426
  "Toggle legend": "Toggle legend",
@@ -415,6 +415,10 @@
415
415
  "Theme selection": "Sélection du thème",
416
416
  "Themes": "Thèmes",
417
417
  "themes": "thèmes",
418
+ "themes-add-remove-favorites": "Ajouter aux favoris / retirer des favoris",
419
+ "themes-type-all": "Tous les thèmes",
420
+ "themes-type-custom": "Personnalisés",
421
+ "themes-type-favorites": "Favoris",
418
422
  "This instance of GeoGirafe cannot be used at the moment.": "Cette instance de GeoGirafe ne peut pas être utilisée pour le moment.",
419
423
  "Toggle all legends": "Afficher / Masquer toutes les légendes",
420
424
  "Toggle legend": "Afficher / Masquer la légende",
@@ -415,6 +415,10 @@
415
415
  "Theme selection": "Selezione del tema",
416
416
  "Themes": "Temi",
417
417
  "themes": "temi",
418
+ "themes-add-remove-favorites": "Aggiungi ai preferiti / rimuovi dai preferiti",
419
+ "themes-type-all": "Tutti i temi",
420
+ "themes-type-custom": "Personalizzati",
421
+ "themes-type-favorites": "Preferiti",
418
422
  "This instance of GeoGirafe cannot be used at the moment.": "Questa istanza di GeoGirafe non può essere utilizzata al momento.",
419
423
  "Toggle all legends": "Attiva / Disattiva tutte le legende",
420
424
  "Toggle legend": "Attiva / Disattiva legenda",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 24 24"><title>ic_fluent_star_add_24_regular</title><path fill="#212121" fill-rule="nonzero" d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11m-4.827-9.24 2.683 5.448 6.011.869a.75.75 0 0 1 .416 1.279l-1.368 1.333a6.5 6.5 0 0 0-1.583-.552l.815-.793-4.896-.708a.75.75 0 0 1-.566-.41L12 4.787 9.815 9.225a.75.75 0 0 1-.566.411l-4.895.707 3.545 3.45a.75.75 0 0 1 .216.665l-.84 4.873L11 17.37a6.6 6.6 0 0 0 .167 1.607l-4.541 2.392a.75.75 0 0 1-1.09-.79l1.032-5.986-4.352-4.236a.75.75 0 0 1 .416-1.28l6.01-.868 2.684-5.448a.75.75 0 0 1 1.346 0M17.5 14l-.09.007a.5.5 0 0 0-.402.402L17 14.5V17L14.498 17l-.09.008a.5.5 0 0 0-.402.402l-.008.09.008.09a.5.5 0 0 0 .402.402l.09.008H17v2.503l.008.09a.5.5 0 0 0 .402.402l.09.008.09-.008a.5.5 0 0 0 .402-.402l.008-.09V18l2.504.001.09-.008a.5.5 0 0 0 .402-.402l.008-.09-.008-.09a.5.5 0 0 0-.403-.402l-.09-.008H18v-2.5l-.008-.09a.5.5 0 0 0-.402-.403z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 28 28"><title>ic_fluent_star_28_regular</title><path fill="#212121" fill-rule="nonzero" d="M14.437 3.103a1 1 0 0 1 .455.455l2.923 5.924 6.538.95a1 1 0 0 1 .555 1.706l-4.731 4.611 1.116 6.512a1 1 0 0 1-1.45 1.054l-5.848-3.074-5.848 3.074a1 1 0 0 1-1.45-1.054l1.116-6.512-4.731-4.611a1 1 0 0 1 .554-1.706l6.538-.95 2.924-5.924a1 1 0 0 1 1.34-.455m-3.267 7.75-6.316.918 4.57 4.455-1.078 6.29 5.649-2.97 5.649 2.97-1.08-6.29 4.571-4.455-6.316-.918-2.824-5.723z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" fill="#eab308" viewBox="0 0 24 24"><title>ic_fluent_star_add_24_regular</title><g fill="#212121" fill-rule="nonzero"><path d="M14.437 3.103a1 1 0 0 1 .455.455l2.923 5.924 6.538.95a1 1 0 0 1 .555 1.706l-4.731 4.611c-6.224 4.484-.045.03-6.182 4.492l-5.848 3.074a1 1 0 0 1-1.45-1.054l1.116-6.512-4.731-4.611a1 1 0 0 1 .554-1.706l6.538-.95 2.924-5.924a1 1 0 0 1 1.34-.455" class="UnoptimicedTransforms" style="display:inline;fill:#eab308;fill-opacity:1;stroke:none;stroke-opacity:1" transform="matrix(.82693 0 0 .81171 .481 .565)"/><path d="M17.5 12a5.5 5.5 0 1 1 0 11 5.5 5.5 0 0 1 0-11m-4.827-9.24 2.683 5.448 6.011.869a.75.75 0 0 1 .416 1.279l-1.368 1.333a6.5 6.5 0 0 0-1.583-.552l.815-.793-4.896-.708a.75.75 0 0 1-.566-.41L12 4.787 9.815 9.225a.75.75 0 0 1-.566.411l-4.895.707 3.545 3.45a.75.75 0 0 1 .216.665l-.84 4.873L11 17.37a6.6 6.6 0 0 0 .167 1.607l-4.541 2.392a.75.75 0 0 1-1.09-.79l1.032-5.986-4.352-4.236a.75.75 0 0 1 .416-1.28l6.01-.868 2.684-5.448a.75.75 0 0 1 1.346 0M14.5 17a.5.5 0 0 0 0 1h6a.5.5 0 0 0 0-1Z" style="display:inline"/></g></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="800" height="800" viewBox="0 0 28 28"><title>ic_fluent_star_28_regular</title><g fill="#212121" fill-rule="nonzero"><path d="M14.437 3.103a1 1 0 0 1 .455.455l2.923 5.924 6.538.95a1 1 0 0 1 .555 1.706l-4.731 4.611 1.116 6.512a1 1 0 0 1-1.45 1.054l-5.848-3.074-5.848 3.074a1 1 0 0 1-1.45-1.054l1.116-6.512-4.731-4.611a1 1 0 0 1 .554-1.706l6.538-.95 2.924-5.924a1 1 0 0 1 1.34-.455" class="UnoptimicedTransforms" style="display:inline;fill:#eab308;fill-opacity:1;stroke:none;stroke-opacity:1"/><path d="M14.437 3.103a1 1 0 0 1 .455.455l2.923 5.924 6.538.95a1 1 0 0 1 .555 1.706l-4.731 4.611 1.116 6.512a1 1 0 0 1-1.45 1.054l-5.848-3.074-5.848 3.074a1 1 0 0 1-1.45-1.054l1.116-6.512-4.731-4.611a1 1 0 0 1 .554-1.706l6.538-.95 2.924-5.924a1 1 0 0 1 1.34-.455m-3.267 7.75-6.316.918 4.57 4.455-1.078 6.29 5.649-2.97 5.649 2.97-1.08-6.29 4.571-4.455-6.316-.918-2.824-5.723z" class="UnoptimicedTransforms" style="display:inline"/><path d="M14.437 3.103a1 1 0 0 1 .455.455l2.923 5.924 6.538.95a1 1 0 0 1 .555 1.706l-4.731 4.611 1.116 6.512a1 1 0 0 1-1.45 1.054l-5.848-3.074-5.848 3.074a1 1 0 0 1-1.45-1.054l1.116-6.512-4.731-4.611a1 1 0 0 1 .554-1.706l6.538-.95 2.924-5.924a1 1 0 0 1 1.34-.455m-3.267 7.75-6.316.918 4.57 4.455-1.078 6.29 5.649-2.97 5.649 2.97-1.08-6.29 4.571-4.455-6.316-.918-2.824-5.723z"/></g></svg>
@@ -1,22 +1,34 @@
1
1
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
2
2
  import ThemeLayer from '../../models/layers/themelayer.js';
3
- import Theme from '../../models/theme.js';
4
3
  import CustomTheme from '../../models/customtheme.js';
4
+ declare const ThemeTypes: readonly ["all", "favorites", "custom"];
5
+ type ThemeType = (typeof ThemeTypes)[number];
5
6
  declare class ThemeComponent extends GirafeHTMLElement {
6
7
  template: () => import("uhtml").Hole;
7
8
  newIcon: string;
8
9
  menuOpen: boolean;
9
10
  openedOnce: boolean;
11
+ activeThemeType: ThemeType;
12
+ clickOutsideContainer: HTMLElement | null;
10
13
  get customThemes(): CustomTheme[];
14
+ get allThemes(): Record<number, ThemeLayer>;
15
+ get favoriteThemes(): (ThemeLayer | CustomTheme)[];
16
+ get activeThemes(): Record<number, ThemeLayer> | (ThemeLayer | CustomTheme)[];
11
17
  constructor();
12
18
  registerEvents(): void;
19
+ protected render(): void;
13
20
  onBlur(): void;
14
21
  toggleThemesList(): void;
15
- onThemeChanged(theme: ThemeLayer): void;
16
- isThemeActive(theme: Theme): boolean;
22
+ activateThemeType(themeType: ThemeType): void;
23
+ onThemeChanged(theme: ThemeLayer | CustomTheme): void;
24
+ isThemeActive(theme: ThemeLayer | CustomTheme): boolean;
25
+ isThemeFavorite(theme: ThemeLayer | CustomTheme): boolean;
26
+ onThemeFavoriteChanged(theme: ThemeLayer | CustomTheme, e: Event): void;
27
+ isThemeTypeActive(themeType: ThemeType): boolean;
28
+ isCustomTheme(theme: ThemeLayer | CustomTheme): theme is CustomTheme;
17
29
  onCustomThemeChanged(customTheme: CustomTheme): void;
18
30
  onAddCustomTheme(): Promise<void>;
19
- onDeleteCustomTheme(themelayer: CustomTheme, e: Event): Promise<void>;
31
+ onDeleteCustomTheme(customTheme: CustomTheme, e: Event): Promise<void>;
20
32
  protected connectedCallback(): void;
21
33
  }
22
34
  export default ThemeComponent;
@@ -1,48 +1,86 @@
1
1
  import { htmlFor as uHtmlFor } from 'uhtml/keyed';
2
2
  import { html as uHtml } from 'uhtml';
3
3
  import GirafeHTMLElement from '../../base/GirafeHTMLElement.js';
4
+ import ThemeLayer from '../../models/layers/themelayer.js';
4
5
  import NewIcon from './images/new.svg';
6
+ import CustomTheme from '../../models/customtheme.js';
7
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
8
+ const ThemeTypes = ['all', 'favorites', 'custom'];
5
9
  class ThemeComponent extends GirafeHTMLElement {
6
10
  template = () => {
7
11
  return uHtml `<style>
8
12
  *{font-family:Arial,sans-serif}.hidden{display:none!important}.gg-rotate90{transform:rotate(90deg)}.gg-rotate180{transform:rotate(180deg)}.gg-rotate270{transform:rotate(270deg)}img{filter:var(--svg-filter)}img.legend-image{filter:var(--svg-map-filter);background:var(--svg-legend-bkg)}div{scrollbar-width:thin}a,a:visited{color:var(--link-color)}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes spin-wait{0%{transform:rotate(0)}7%{transform:rotate(360deg)}to{transform:rotate(360deg)}}.gg-spin{animation-name:spin;animation-duration:2s;animation-timing-function:linear;animation-iteration-count:infinite}.gg-spin-wait{animation-name:spin-wait;animation-duration:10s;animation-timing-function:linear;animation-iteration-count:infinite}::-webkit-scrollbar{width:5px}::-webkit-scrollbar-thumb{background:#999}.gg-button,.gg-select,.gg-input,.gg-textarea{background-color:var(--bkg-color);color:var(--text-color);border:var(--app-standard-border);box-sizing:border-box;cursor:pointer;border-radius:3px;outline:0;margin:0;padding:0 0 0 .5rem;display:inline-block}.gg-label{background-color:var(--bkg-color);color:var(--text-color);border:none;align-items:center;margin:0;padding:0;display:flex}.gg-button,.gg-select,.gg-input,.gg-label{min-height:calc(var(--app-standard-height)/1.5)}.gg-textarea{max-height:initial;resize:vertical;height:6rem;padding:.5rem;line-height:1.3rem}.gg-input{cursor:text}.gg-checkbox{accent-color:var(--text-color);width:1.2rem}.gg-range{accent-color:var(--text-color)}.gg-button{padding:0 .5rem}.gg-button.active{border:solid 1px var(--text-color-grad2);background-color:var(--text-color-grad2);color:var(--bkg-color)}.gg-button:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3;border:none}.gg-input:disabled,.gg-select:disabled,.gg-textarea:disabled{color:gray;cursor:not-allowed;background-color:#d3d3d3}.gg-button>img{vertical-align:middle}.gg-icon-button{color:var(--text-color);cursor:pointer;background-color:#0000;border:none;flex-direction:column;justify-content:center;align-items:center;padding:0;display:flex}.gg-icon{justify-content:center;align-items:center;display:flex}.gg-big,.gg-big-withtext{min-width:var(--app-standard-height);min-height:var(--app-standard-height);max-height:var(--app-standard-height)}.gg-big img,.gg-big-withtext img{width:calc(var(--app-standard-height) - 1.5rem);margin:0}.gg-big-withtext span{font-variant:small-caps;padding:0 1rem;font-size:.9rem}.gg-medium,.gg-medium-withtext{min-width:calc(var(--app-standard-height)/1.2);min-height:calc(var(--app-standard-height)/1.2);max-height:calc(var(--app-standard-height)/1.2);flex-direction:row}.gg-medium img{width:calc(var(--app-standard-height)/2.4);margin:0}.gg-medium-withtext img{width:calc(var(--app-standard-height)/2.4);margin-left:.5rem}.gg-medium-withtext span{padding:0 1rem 0 .5rem;font-size:.9rem}.gg-small,.gg-small-withtext{min-width:calc(var(--app-standard-height)/2);min-height:calc(var(--app-standard-height)/2);max-height:calc(var(--app-standard-height)/2);flex-direction:row}.gg-small img{width:calc(var(--app-standard-height)/3);margin:0}.gg-small-withtext img{width:calc(var(--app-standard-height)/3);margin-left:.5rem}.gg-small-withtext span{padding:0 .5rem 0 .3rem;font-size:.9rem}.gg-button:hover,.gg-select:hover,.gg-input:hover,.gg-textarea:hover,.gg-icon-button:hover{background-color:var(--bkg-color-grad1)}.gg-opacity{opacity:.5}.gg-opacity:hover{opacity:1;background-color:#0000}.gg-tabs{cursor:pointer;grid-auto-flow:column;padding-bottom:1rem;font-size:1rem;display:grid}.gg-tab{border:none;border-bottom:var(--app-standard-border);cursor:pointer;color:var(--text-color);background:0 0;padding:.5rem}.gg-tab.active{border-bottom:solid 1px var(--text-color)}.girafe-button-big,.girafe-button-large,.girafe-button-small,.girafe-button-tiny{color:var(--text-color);background-color:#0000;border:none;flex-direction:column;display:flex}.girafe-button-big:hover,.girafe-button-large:hover,.girafe-button-small:hover,.girafe-button-tiny:hover{background-color:var(--bkg-color-grad1);cursor:pointer}.girafe-button-big.dark,.girafe-button-large.dark,.girafe-button-small.dark,.girafe-button-tiny.dark{background-color:var(--bkg-color);filter:invert()}.girafe-button-big{width:var(--app-standard-height);height:var(--app-standard-height);align-items:center;padding:1rem}.girafe-button-big img{overflow:hidden}.girafe-button-large{flex-direction:row}.girafe-button-large img{height:2rem;margin:.3rem}.girafe-button-large span{height:2rem;margin:.3rem;line-height:2rem}.girafe-button-small{min-width:calc(var(--app-standard-height)/2);height:calc(var(--app-standard-height)/2);align-items:center;padding:.5rem}.girafe-button-small img{overflow:hidden}.girafe-button-small span{text-align:left;text-overflow:ellipsis;width:100%;overflow:hidden}.girafe-button-tiny{align-items:center;width:1rem;height:1rem;padding:0}.girafe-button-tiny img{overflow:hidden}.girafe-onboarding-theme{background-color:var(--bkg-color)!important;color:var(--text-color)!important}.girafe-onboarding-theme button{background-color:var(--bkg-color)!important;color:var(--text-color)!important;text-shadow:none!important}.girafe-onboarding-theme button.driver-popover-close-btn{z-index:10000}
9
13
  </style><style>
10
- .themes{background-color:var(--bkg-color);border:1px solid #444;border-radius:3px;flex-wrap:wrap;width:55rem;max-height:70vh;margin-top:0;padding:1rem;display:flex;position:absolute;overflow-y:auto}.theme,.custom-theme,.new-theme{cursor:pointer;background-color:#0000;border:none;border-radius:.5rem;align-content:flex-end;width:8rem;margin:.5rem;padding:0;position:relative}.themes>button>span,.custom-theme>button>span,.new-theme>button>span{text-align:center;width:95%;color:var(--text-color);display:inline-block}.themes>button>span,.custom-theme>button>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.theme>img,.custom-theme>button>img,.new-theme>img{max-width:100%}button.select{cursor:pointer;background-color:#0000;border:none;width:100%;padding:0}button.select>img{height:3rem;margin-bottom:1rem}button.delete{position:absolute;top:0;right:0}.theme.selected,.custom-theme.selected{background-color:var(--bkg-color-grad2)}.theme-icon{--svg-filter:none}
14
+ .themes{background-color:var(--bkg-color);border:1px solid #444;border-radius:3px;flex-flow:column;width:58rem;max-height:70vh;margin-top:0;padding:1rem;display:flex;position:absolute;overflow-y:auto}#close-themes-menu{z-index:-1;background:0 0;width:100vw;height:100vh;display:none;position:absolute;top:0;left:0}#active-themes{flex-flow:wrap;flex:1;justify-content:flex-start;display:flex}.theme-card{border:var(--app-standard-border);background-color:#0000;border-radius:6px;flex-direction:column;align-items:center;width:7rem;height:9rem;margin:.25rem;padding:.25rem;display:flex;&:hover{cursor:pointer;background-color:var(--bkg-color-grad2)}& img.theme-favorite{content:url(icons/favorite-no.svg);align-self:flex-start;width:1.5rem;height:1.5rem;&:hover{content:url(icons/favorite-add.svg)}&.yes{content:url(icons/favorite-yes.svg);&:hover{content:url(icons/favorite-remove.svg)}}&.placeholder{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=)}}& img.delete-icon{content:url(icons/close.svg);align-self:flex-end;width:1.5rem;height:1.5rem;margin-top:-1.5rem;&.placeholder{content:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=)}}& img.theme-icon{max-width:5rem;max-height:2.5rem;margin-top:1.25rem;margin-bottom:1.25rem}&.selected{background-color:var(--bkg-color-grad1);border:solid 1px var(--text-color)}& span.theme-name{text-align:center;word-break:break-word;max-width:6rem;color:var(--text-color);max-height:2.2rem;line-height:1.1rem;overflow:hidden}}.theme,.custom-theme,.new-theme{cursor:pointer;background-color:#0000;border:none;border-radius:.5rem;align-content:flex-end;width:8rem;margin:.5rem;padding:0;position:relative}.themes>button>span,.custom-theme>button>span,.new-theme>button>span{text-align:center;width:95%;color:var(--text-color);display:inline-block}.themes>button>span,.custom-theme>button>span{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.theme>img,.custom-theme>button>img,.new-theme>img{max-width:100%}button.select{cursor:pointer;background-color:#0000;border:none;width:100%;padding:0}button.select>img{height:3rem;margin-bottom:1rem}button.delete{position:absolute;top:0;right:0}.theme.selected,.custom-theme.selected{background-color:var(--bkg-color-grad2)}.theme-icon{--svg-filter:none}#themes-type-chooser{flex:1;justify-content:space-between;display:flex}.gg-tabs{flex-grow:1;margin:0}
11
15
  </style>
12
- <button class="gg-icon-button gg-big-withtext" tip="Theme selection" onclick="${() => this.toggleThemesList()}" onblur="${() => this.onBlur()}"><img alt="menu-icon" src="icons/themes.svg" class="${(!this.state.loading ? (this.openedOnce ? '' : 'gg-spin-wait') : 'hidden')}"> <img alt="loading-icon" src="icons/loading.svg" class="${(this.state.loading ? 'gg-spin' : 'hidden')}"> <span i18n="Themes">Themes</span></button><div class="${(this.menuOpen ? 'themes' : 'hidden')}">${Object.values(this.state.themes._allThemes ?? {}).map(theme => uHtmlFor(theme, theme.id) ` <button class="${this.isThemeActive(theme) ? 'theme selected' : 'theme'}" onmousedown="${() => this.onThemeChanged(theme)}"><img class="theme-icon" alt="${'Icon for ' + theme.name}" src="${theme.icon}"> <span i18n="${theme.name}">${theme.name}</span></button> `)} ${Object.values(this.customThemes ?? {}).map(theme => uHtmlFor(theme, theme.id) `<div class="${this.isThemeActive(theme) ? 'custom-theme selected' : 'custom-theme'}"><button class="select" onmousedown="${() => this.onCustomThemeChanged(theme)}"><img alt="${'Icon for ' + theme.name}" src="${theme.icon}"> <span i18n="${theme.name}">${theme.name}</span></button> <button class="girafe-button-small delete" onmousedown="${(e) => this.onDeleteCustomTheme(theme, e)}"><img alt="delete-icon" src="icons/close.svg"></button></div>`)}<div class="new-theme"><button class="select" onmousedown="${() => this.onAddCustomTheme()}"><img alt="Create custom theme" src="${this.newIcon}" tip="Save the current layer configuration as new theme"> <span i18n="Create custom theme">Create custom theme</span></button></div></div>`;
16
+ <button class="gg-icon-button gg-big-withtext" tip="Theme selection" onclick="${() => this.toggleThemesList()}"><img alt="menu-icon" src="icons/themes.svg" class="${(!this.state.loading ? (this.openedOnce ? '' : 'gg-spin-wait') : 'hidden')}"> <img alt="loading-icon" src="icons/loading.svg" class="${(this.state.loading ? 'gg-spin' : 'hidden')}"> <span i18n="Themes">Themes</span></button><div class="${(this.menuOpen ? 'themes' : 'hidden')}"><div id="themes-type-chooser"><div class="gg-tabs">${Object.values(ThemeTypes).map((themeType) => uHtml ` <button id="${'choose-themes-type-' + themeType}" i18n="${'themes-type-' + themeType}" class="${this.isThemeTypeActive(themeType) ? 'gg-tab active' : 'gg-tab'}" .active="${this.isThemeTypeActive(themeType)}" onclick="${() => this.activateThemeType(themeType)}"></button> `)}</div></div><div id="active-themes">${Object.values(this.activeThemes).map(theme => uHtmlFor(theme, theme.id) `<div role="button" tabindex="0" class="${this.isThemeActive(theme) ? 'theme-card selected' : 'theme-card'}" onkeyup="${() => this.onThemeChanged(theme)}" onclick="${() => this.onThemeChanged(theme)}"><img class="${this.isThemeFavorite(theme) ? 'theme-favorite yes' : 'theme-favorite'}" tip="themes-add-remove-favorites" alt="themes-add-remove-favorites" src="" onclick="${(e) => this.onThemeFavoriteChanged(theme, e)}" onkeyup="${(e) => this.onThemeFavoriteChanged(theme, e)}"> <img class="${this.isCustomTheme(theme) ? 'delete-icon' : 'delete-icon placeholder'}" onclick="${(e) => this.onDeleteCustomTheme(theme, e)}" onkeyup="${(e) => this.onDeleteCustomTheme(theme, e)}" alt="delete-icon" src=""> <img class="theme-icon" alt="${'Icon for ' + theme.name}" src="${theme.icon}"> <span i18n="${theme.name}" class="theme-name">${theme.name}</span></div>`)}<div role="button" tabindex="0" class="${this.isThemeTypeActive('custom') ? 'theme-card' : 'hidden'}" onclick="${() => this.onAddCustomTheme()}" onkeyup="${() => this.onAddCustomTheme()}"><img class="theme-favorite placeholder" src="" alt="" disabled="disabled"> <img class="theme-icon" alt="Create custom theme" src="${this.newIcon}" tip="Save the current layer configuration as new theme"> <span i18n="Create custom theme" class="theme-name">Create custom theme</span></div></div></div><div id="close-themes-menu" onclick="${() => this.onBlur()}"></div>`;
13
17
  };
14
18
  newIcon = NewIcon;
15
19
  menuOpen = false;
16
20
  openedOnce = false;
21
+ activeThemeType = 'all';
22
+ clickOutsideContainer = null;
17
23
  get customThemes() {
18
24
  return this.context.customThemesManager.customThemes;
19
25
  }
26
+ get allThemes() {
27
+ return this.state.themes._allThemes ?? {};
28
+ }
29
+ get favoriteThemes() {
30
+ return [
31
+ ...Object.values(this.allThemes).filter((theme) => this.context.themeFavoritesManager.isThemeInFavorites(theme)),
32
+ ...Object.values(this.customThemes).filter((theme) => this.context.themeFavoritesManager.isThemeInFavorites(theme))
33
+ ];
34
+ }
35
+ get activeThemes() {
36
+ switch (this.activeThemeType) {
37
+ case 'all':
38
+ return this.allThemes;
39
+ case 'favorites':
40
+ return this.favoriteThemes;
41
+ case 'custom':
42
+ return this.customThemes;
43
+ default:
44
+ return {};
45
+ }
46
+ }
20
47
  constructor() {
21
48
  super('themes');
22
49
  }
23
50
  registerEvents() {
24
- this.subscribe('loading', () => super.render());
51
+ this.subscribe('loading', () => this.render());
25
52
  this.subscribe('themes.isLoaded', () => {
26
53
  if (this.state.themes.isLoaded) {
27
- super.render();
54
+ this.render();
28
55
  super.girafeTranslate();
29
56
  }
30
57
  });
31
58
  }
59
+ render() {
60
+ super.render();
61
+ if (this.clickOutsideContainer)
62
+ this.clickOutsideContainer.style.display = this.menuOpen ? 'block' : 'none';
63
+ }
32
64
  onBlur() {
33
65
  this.menuOpen = false;
34
- super.render();
66
+ this.render();
35
67
  }
36
68
  toggleThemesList() {
37
69
  this.openedOnce = true;
38
70
  this.menuOpen = !this.menuOpen;
39
- super.render();
71
+ this.render();
72
+ }
73
+ activateThemeType(themeType) {
74
+ this.activeThemeType = themeType;
75
+ this.render();
40
76
  }
41
77
  onThemeChanged(theme) {
42
78
  if (this.context.configManager.Config.themes.selectionMode === 'add') {
43
79
  this.state.themes.lastSelectedTheme = null;
44
80
  }
45
81
  this.state.themes.lastSelectedTheme = theme;
82
+ if (theme instanceof CustomTheme)
83
+ return;
46
84
  if (theme.disclaimer) {
47
85
  this.state.infobox.elements.push({
48
86
  id: theme.treeItemId,
@@ -64,7 +102,22 @@ class ThemeComponent extends GirafeHTMLElement {
64
102
  if (this.context.configManager.Config.themes.selectionMode === 'replace') {
65
103
  return theme.id === this.state.themes.lastSelectedTheme?.id;
66
104
  }
67
- return false;
105
+ return this.state.layers.layersList.some((layer) => layer instanceof ThemeLayer && layer.id === theme.id);
106
+ }
107
+ isThemeFavorite(theme) {
108
+ return this.context.themeFavoritesManager.isThemeInFavorites(theme);
109
+ }
110
+ onThemeFavoriteChanged(theme, e) {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ this.context.themeFavoritesManager.addOrRemoveThemeFromFavorites(theme);
114
+ this.render();
115
+ }
116
+ isThemeTypeActive(themeType) {
117
+ return (this.activeThemeType ?? 'all') === themeType;
118
+ }
119
+ isCustomTheme(theme) {
120
+ return theme instanceof CustomTheme;
68
121
  }
69
122
  onCustomThemeChanged(customTheme) {
70
123
  this.state.themes.lastSelectedTheme = customTheme;
@@ -74,15 +127,15 @@ class ThemeComponent extends GirafeHTMLElement {
74
127
  const themeName = await window.gPrompt('Enter a name for your custom theme', 'Create custom theme', 'Enter a name...');
75
128
  if (themeName !== false && themeName.trim().length > 0) {
76
129
  this.context.customThemesManager.addTheme(themeName, this.state.layers.layersList);
77
- super.render();
130
+ this.render();
78
131
  }
79
132
  }
80
- async onDeleteCustomTheme(themelayer, e) {
133
+ async onDeleteCustomTheme(customTheme, e) {
81
134
  e.stopPropagation();
82
135
  const confirm = await window.gConfirm('Do you want to delete this theme?', 'Delete Theme');
83
136
  if (confirm) {
84
- this.context.customThemesManager.deleteTheme(themelayer);
85
- super.render();
137
+ this.context.customThemesManager.deleteTheme(customTheme);
138
+ this.render();
86
139
  }
87
140
  }
88
141
  connectedCallback() {
@@ -90,6 +143,7 @@ class ThemeComponent extends GirafeHTMLElement {
90
143
  super.render();
91
144
  this.registerEvents();
92
145
  this.girafeTranslate();
146
+ this.clickOutsideContainer = this.shadow.getElementById('close-themes-menu');
93
147
  }
94
148
  }
95
149
  export default ThemeComponent;
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "name": "GeoGirafe PSC",
6
6
  "url": "https://doc.geomapfish.dev"
7
7
  },
8
- "version": "1.1.0-dev.2318696406",
8
+ "version": "1.1.0-dev.2321641404",
9
9
  "type": "module",
10
10
  "engines": {
11
11
  "node": ">=20.19.0"
@@ -1 +1 @@
1
- {"version":"1.1.0-dev.2318696406", "build":"2318696406", "date":"11/02/2026"}
1
+ {"version":"1.1.0-dev.2321641404", "build":"2321641404", "date":"12/02/2026"}
@@ -30,6 +30,7 @@ import UserDataManager from '../userdata/userdatamanager.js';
30
30
  import WfsManager from '../wfs/wfsmanager.js';
31
31
  import WmsManager from '../wms/wmsmanager.js';
32
32
  import IGirafeContext from './icontext.js';
33
+ import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
33
34
  export default class GirafeContext implements IGirafeContext {
34
35
  readonly userDataManager: UserDataManager;
35
36
  readonly configManager: ConfigManager;
@@ -62,6 +63,7 @@ export default class GirafeContext implements IGirafeContext {
62
63
  readonly ogcApiFeaturesManager: OgcApiFeaturesManager;
63
64
  readonly localFileManager: LocalFileManager;
64
65
  readonly onBoardingManager: OnBoardingManager;
66
+ readonly themeFavoritesManager: ThemeFavoritesManager;
65
67
  constructor();
66
68
  initialize(): Promise<void>;
67
69
  }
@@ -29,6 +29,7 @@ import UrlManager from '../url/urlmanager.js';
29
29
  import UserDataManager from '../userdata/userdatamanager.js';
30
30
  import WfsManager from '../wfs/wfsmanager.js';
31
31
  import WmsManager from '../wms/wmsmanager.js';
32
+ import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
32
33
  export default class GirafeContext {
33
34
  userDataManager;
34
35
  configManager;
@@ -61,6 +62,7 @@ export default class GirafeContext {
61
62
  ogcApiFeaturesManager;
62
63
  localFileManager;
63
64
  onBoardingManager;
65
+ themeFavoritesManager;
64
66
  constructor() {
65
67
  this.componentManager = new ComponentManager(this);
66
68
  this.userDataManager = new UserDataManager(this);
@@ -93,6 +95,7 @@ export default class GirafeContext {
93
95
  this.localFileManager = new LocalFileManager(this);
94
96
  this.ogcApiFeaturesManager = new OgcApiFeaturesManager(this);
95
97
  this.onBoardingManager = new OnBoardingManager(this);
98
+ this.themeFavoritesManager = new ThemeFavoritesManager(this);
96
99
  }
97
100
  async initialize() {
98
101
  // NOTE : This initialization order is important, because some singleton will need other ones !
@@ -129,5 +132,6 @@ export default class GirafeContext {
129
132
  this.localFileManager.initializeSingleton();
130
133
  this.ogcApiFeaturesManager.initializeSingleton();
131
134
  this.onBoardingManager.initializeSingleton();
135
+ this.themeFavoritesManager.initializeSingleton();
132
136
  }
133
137
  }
@@ -29,6 +29,7 @@ import UrlManager from '../url/urlmanager.js';
29
29
  import UserDataManager from '../userdata/userdatamanager.js';
30
30
  import WfsManager from '../wfs/wfsmanager.js';
31
31
  import WmsManager from '../wms/wmsmanager.js';
32
+ import ThemeFavoritesManager from '../themes/themefavoritesmanager.js';
32
33
  export default interface IGirafeContext {
33
34
  readonly userDataManager: UserDataManager;
34
35
  readonly configManager: ConfigManager;
@@ -61,5 +62,6 @@ export default interface IGirafeContext {
61
62
  readonly ogcApiFeaturesManager: OgcApiFeaturesManager;
62
63
  readonly localFileManager: LocalFileManager;
63
64
  readonly onBoardingManager: OnBoardingManager;
65
+ readonly themeFavoritesManager: ThemeFavoritesManager;
64
66
  initialize(): Promise<void>;
65
67
  }
package/tools/main.d.ts CHANGED
@@ -90,6 +90,7 @@ export { default as UserInteractionManager } from './state/userInteractionManage
90
90
  export type { GgUserInteractionEvent } from './state/userinteractionevent.js';
91
91
  export { gGEventDependencies, isPrimaryPointerAction, isAlternateMouseClick, isMouseWheelClick } from './state/userinteractionevent.js';
92
92
  export { default as CustomThemesManager } from './themes/customthemesmanager.js';
93
+ export { default as ThemeFavoritesManager } from './themes/themefavoritesmanager.js';
93
94
  export { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes/themes-config.js';
94
95
  export type { LayerTreeChanges } from './themes/themeshelper.js';
95
96
  export { default as ThemesHelper } from './themes/themeshelper.js';
package/tools/main.js CHANGED
@@ -68,6 +68,7 @@ export { default as StateManager } from './state/statemanager.js';
68
68
  export { default as UserInteractionManager } from './state/userInteractionManager.js';
69
69
  export { gGEventDependencies, isPrimaryPointerAction, isAlternateMouseClick, isMouseWheelClick } from './state/userinteractionevent.js';
70
70
  export { default as CustomThemesManager } from './themes/customthemesmanager.js';
71
+ export { default as ThemeFavoritesManager } from './themes/themefavoritesmanager.js';
71
72
  export { DEFAULT_OPACITY, OPACITY_FOR_DEFAULT_BASEMAP } from './themes/themes-config.js';
72
73
  export { default as ThemesHelper } from './themes/themeshelper.js';
73
74
  export { default as ThemesManager } from './themes/themesmanager.js';
@@ -13,8 +13,7 @@ export default class ActiveBasemapsSerializer {
13
13
  return this.serialize(activeBasemaps);
14
14
  }
15
15
  brainDeserialize(serializedString) {
16
- const activeBasemaps = this.deserialize(serializedString);
17
- this.state.activeBasemaps = activeBasemaps;
16
+ this.state.activeBasemaps = this.deserialize(serializedString);
18
17
  }
19
18
  serialize(basemaps) {
20
19
  const sharedBasemaps = [];
@@ -30,6 +30,7 @@ import UserDataManager from '../userdata/userdatamanager';
30
30
  import WfsManager from '../wfs/wfsmanager';
31
31
  import WmsManager from '../wms/wmsmanager';
32
32
  import OnBoardingManager from '../onboarding/onboardingmanager';
33
+ import ThemeFavoritesManager from '../themes/themefavoritesmanager';
33
34
  export default class MockGirafeContext implements IGirafeContext {
34
35
  readonly userDataManager: UserDataManager;
35
36
  readonly configManager: ConfigManager;
@@ -62,6 +63,7 @@ export default class MockGirafeContext implements IGirafeContext {
62
63
  readonly ogcApiFeaturesManager: OgcApiFeaturesManager;
63
64
  readonly localFileManager: LocalFileManager;
64
65
  readonly onBoardingManager: OnBoardingManager;
66
+ readonly themeFavoritesManager: ThemeFavoritesManager;
65
67
  constructor();
66
68
  initialize(): Promise<void>;
67
69
  }
@@ -31,6 +31,7 @@ import WfsManager from '../wfs/wfsmanager';
31
31
  import WmsManager from '../wms/wmsmanager';
32
32
  import { MockConfig } from './mockconfig';
33
33
  import OnBoardingManager from '../onboarding/onboardingmanager';
34
+ import ThemeFavoritesManager from '../themes/themefavoritesmanager';
34
35
  export default class MockGirafeContext {
35
36
  userDataManager;
36
37
  configManager;
@@ -63,6 +64,7 @@ export default class MockGirafeContext {
63
64
  ogcApiFeaturesManager;
64
65
  localFileManager;
65
66
  onBoardingManager;
67
+ themeFavoritesManager;
66
68
  constructor() {
67
69
  this.componentManager = new ComponentManager(this);
68
70
  this.userDataManager = new UserDataManager(this);
@@ -95,6 +97,7 @@ export default class MockGirafeContext {
95
97
  this.localFileManager = new LocalFileManager(this);
96
98
  this.ogcApiFeaturesManager = new OgcApiFeaturesManager(this);
97
99
  this.onBoardingManager = new OnBoardingManager(this);
100
+ this.themeFavoritesManager = new ThemeFavoritesManager(this);
98
101
  this.initialize();
99
102
  }
100
103
  async initialize() {
@@ -137,5 +140,6 @@ export default class MockGirafeContext {
137
140
  this.localFileManager.initializeSingleton();
138
141
  this.ogcApiFeaturesManager.initializeSingleton();
139
142
  this.onBoardingManager.initializeSingleton();
143
+ this.themeFavoritesManager.initializeSingleton();
140
144
  }
141
145
  }
@@ -0,0 +1,17 @@
1
+ import GirafeSingleton from '../../base/GirafeSingleton.js';
2
+ import CustomTheme from '../../models/customtheme.js';
3
+ import ThemeLayer from '../../models/layers/themelayer.js';
4
+ declare class ThemeFavoritesManager extends GirafeSingleton {
5
+ private themeFavorites;
6
+ private readonly storagePath;
7
+ initializeSingleton(): void;
8
+ private saveThemeFavorites;
9
+ private loadThemeFavorites;
10
+ addThemeToFavorites(theme: ThemeLayer | CustomTheme): void;
11
+ removeThemeFromFavorites(theme: ThemeLayer | CustomTheme): void;
12
+ addOrRemoveThemeFromFavorites(theme: ThemeLayer | CustomTheme): void;
13
+ clearThemeFavorites(): void;
14
+ isThemeInFavorites(theme: ThemeLayer | CustomTheme): boolean;
15
+ getThemeFavorites(): Array<number | string>;
16
+ }
17
+ export default ThemeFavoritesManager;
@@ -0,0 +1,53 @@
1
+ import GirafeSingleton from '../../base/GirafeSingleton.js';
2
+ import ThemeLayer from '../../models/layers/themelayer.js';
3
+ class ThemeFavoritesManager extends GirafeSingleton {
4
+ themeFavorites = [];
5
+ storagePath = 'themeFavorites';
6
+ initializeSingleton() {
7
+ super.initializeSingleton();
8
+ this.loadThemeFavorites();
9
+ }
10
+ saveThemeFavorites() {
11
+ const serializedThemeFavorites = JSON.stringify(this.themeFavorites);
12
+ this.context.userDataManager.saveUserData(this.storagePath, serializedThemeFavorites);
13
+ }
14
+ loadThemeFavorites() {
15
+ const serializedThemeFavorites = this.context.userDataManager.getUserData(this.storagePath);
16
+ if (serializedThemeFavorites) {
17
+ this.themeFavorites = JSON.parse(serializedThemeFavorites);
18
+ }
19
+ }
20
+ addThemeToFavorites(theme) {
21
+ this.themeFavorites.push(theme instanceof ThemeLayer ? theme.id : theme.name);
22
+ this.saveThemeFavorites();
23
+ }
24
+ removeThemeFromFavorites(theme) {
25
+ const index = this.themeFavorites.findIndex((themeIdOrName) => theme instanceof ThemeLayer ? themeIdOrName === theme.id : themeIdOrName === theme.name);
26
+ if (index >= 0) {
27
+ this.themeFavorites.splice(index, 1);
28
+ this.saveThemeFavorites();
29
+ }
30
+ else {
31
+ throw new Error('The theme to be removed cannot be found in the list of favorites');
32
+ }
33
+ }
34
+ addOrRemoveThemeFromFavorites(theme) {
35
+ if (this.isThemeInFavorites(theme)) {
36
+ this.removeThemeFromFavorites(theme);
37
+ }
38
+ else {
39
+ this.addThemeToFavorites(theme);
40
+ }
41
+ }
42
+ clearThemeFavorites() {
43
+ this.themeFavorites = [];
44
+ this.saveThemeFavorites();
45
+ }
46
+ isThemeInFavorites(theme) {
47
+ return this.themeFavorites.includes(theme instanceof ThemeLayer ? theme.id : theme.name);
48
+ }
49
+ getThemeFavorites() {
50
+ return this.themeFavorites;
51
+ }
52
+ }
53
+ export default ThemeFavoritesManager;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,103 @@
1
+ import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
2
+ import MockHelper from '../tests/mockhelper';
3
+ import ThemeLayer from '../../models/layers/themelayer';
4
+ import CustomTheme from '../../models/customtheme';
5
+ let themeFavoritesManager;
6
+ let context;
7
+ const storagePath = 'themeFavorites';
8
+ const themeLayer = new ThemeLayer(42, "ThemeLayer", 1);
9
+ const customTheme = new CustomTheme("CustomTheme");
10
+ beforeAll(() => {
11
+ context = MockHelper.startMocking();
12
+ themeFavoritesManager = context.themeFavoritesManager;
13
+ });
14
+ beforeEach(() => {
15
+ context.userDataManager.deleteAllUserData();
16
+ themeFavoritesManager.clearThemeFavorites();
17
+ });
18
+ describe('ThemeFavoritesManager.addThemeToFavorites', () => {
19
+ it('adding a ThemeLayer should result in an number array', () => {
20
+ themeFavoritesManager.addThemeToFavorites(themeLayer);
21
+ expect(context.userDataManager.getUserData(storagePath)).toEqual(`[${themeLayer.id}]`);
22
+ });
23
+ it('adding a CustomTheme should result in an string array', () => {
24
+ themeFavoritesManager.addThemeToFavorites(customTheme);
25
+ expect(context.userDataManager.getUserData(storagePath)).toEqual(`["${customTheme.name}"]`);
26
+ });
27
+ it('adding a ThemeLayer and a CustomTheme should result in an mixed array', () => {
28
+ themeFavoritesManager.addThemeToFavorites(themeLayer);
29
+ themeFavoritesManager.addThemeToFavorites(customTheme);
30
+ expect(context.userDataManager.getUserData(storagePath)).toEqual(`[${themeLayer.id},"${customTheme.name}"]`);
31
+ });
32
+ });
33
+ describe('ThemeFavoritesManager.initializeSingleton', () => {
34
+ it('starting without persisted favorites should result in an empty array', () => {
35
+ themeFavoritesManager.initializeSingleton();
36
+ expect(themeFavoritesManager.getThemeFavorites()).toEqual([]);
37
+ });
38
+ it('starting with an empty string as persisted data should result in an empty array', () => {
39
+ context.userDataManager.saveUserData(storagePath, '');
40
+ themeFavoritesManager.initializeSingleton();
41
+ expect(themeFavoritesManager.getThemeFavorites()).toEqual([]);
42
+ });
43
+ it('starting with "[42]" as persisted data should result in [42]', () => {
44
+ context.userDataManager.saveUserData(storagePath, '[42]');
45
+ themeFavoritesManager.initializeSingleton();
46
+ expect(themeFavoritesManager.getThemeFavorites()).toEqual([42]);
47
+ });
48
+ it('starting with "["CustomTheme""]" as persisted data should result in ["CustomTheme"]', () => {
49
+ context.userDataManager.saveUserData(storagePath, '["CustomTheme"]');
50
+ themeFavoritesManager.initializeSingleton();
51
+ expect(themeFavoritesManager.getThemeFavorites()).toEqual(['CustomTheme']);
52
+ });
53
+ it('starting with "[42,"CustomTheme""]" as persisted data should result in [42,"CustomTheme"]', () => {
54
+ context.userDataManager.saveUserData(storagePath, '[42,"CustomTheme"]');
55
+ themeFavoritesManager.initializeSingleton();
56
+ expect(themeFavoritesManager.getThemeFavorites()).toEqual([42, 'CustomTheme']);
57
+ });
58
+ });
59
+ describe('ThemeFavoritesManager.isThemeInFavorites', () => {
60
+ it('adding ThemeLayer should result in ThemeLayer being a favorite', () => {
61
+ themeFavoritesManager.addThemeToFavorites(themeLayer);
62
+ expect(themeFavoritesManager.isThemeInFavorites(themeLayer)).toBeTruthy();
63
+ });
64
+ it('adding CustomTheme should result in CustomTheme being a favorite', () => {
65
+ themeFavoritesManager.addThemeToFavorites(customTheme);
66
+ expect(themeFavoritesManager.isThemeInFavorites(customTheme)).toBeTruthy();
67
+ });
68
+ it('adding CustomTheme should result in ThemeLayer being not a favorite', () => {
69
+ themeFavoritesManager.addThemeToFavorites(customTheme);
70
+ expect(themeFavoritesManager.isThemeInFavorites(themeLayer)).toBeFalsy();
71
+ });
72
+ it('adding ThemeLayer should result in CustomTheme being not a favorite', () => {
73
+ themeFavoritesManager.addThemeToFavorites(themeLayer);
74
+ expect(themeFavoritesManager.isThemeInFavorites(customTheme)).toBeFalsy();
75
+ });
76
+ it('adding ThemeLayer and CustomTheme should result in both being a favorite', () => {
77
+ themeFavoritesManager.addThemeToFavorites(themeLayer);
78
+ themeFavoritesManager.addThemeToFavorites(customTheme);
79
+ expect(themeFavoritesManager.isThemeInFavorites(themeLayer)).toBeTruthy();
80
+ expect(themeFavoritesManager.isThemeInFavorites(customTheme)).toBeTruthy();
81
+ });
82
+ });
83
+ describe('ThemeFavoritesManager.clearThemeFavorites', () => {
84
+ it('clearing favorites should result in an empty array', () => {
85
+ context.userDataManager.saveUserData(storagePath, '[42,"CustomTheme"]');
86
+ themeFavoritesManager.initializeSingleton();
87
+ expect(themeFavoritesManager.getThemeFavorites().length).toEqual(2);
88
+ themeFavoritesManager.clearThemeFavorites();
89
+ expect(themeFavoritesManager.getThemeFavorites().length).toEqual(0);
90
+ });
91
+ });
92
+ describe('ThemeFavoritesManager.addOrRemoveThemeFromFavorites', () => {
93
+ it('not already being a favorite should result in being a favorite afterwards ', () => {
94
+ themeFavoritesManager.addOrRemoveThemeFromFavorites(themeLayer);
95
+ expect(themeFavoritesManager.isThemeInFavorites(themeLayer)).toBeTruthy();
96
+ });
97
+ it('already being a favorite should result in no longer being a favorite afterwards ', () => {
98
+ context.userDataManager.saveUserData(storagePath, '[42]');
99
+ themeFavoritesManager.initializeSingleton();
100
+ themeFavoritesManager.addOrRemoveThemeFromFavorites(themeLayer);
101
+ expect(themeFavoritesManager.isThemeInFavorites(themeLayer)).toBeFalsy();
102
+ });
103
+ });