@panoramax/web-viewer 3.1.1 → 3.2.0-develop-8f79d734

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.
Files changed (38) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/build/index.css +2 -2
  3. package/build/index.css.map +1 -1
  4. package/build/index.js +6 -6
  5. package/build/index.js.map +1 -1
  6. package/docs/02_Usage.md +267 -224
  7. package/docs/03_URL_settings.md +10 -1
  8. package/docs/05_Compatibility.md +1 -0
  9. package/package.json +4 -3
  10. package/src/Viewer.js +51 -3
  11. package/src/components/CoreView.css +6 -0
  12. package/src/components/CoreView.js +25 -10
  13. package/src/components/Map.js +41 -2
  14. package/src/components/Photo.css +5 -0
  15. package/src/translations/de.json +28 -11
  16. package/src/translations/en.json +22 -11
  17. package/src/translations/es.json +23 -11
  18. package/src/translations/fr.json +22 -11
  19. package/src/translations/hu.json +117 -73
  20. package/src/translations/nl.json +73 -1
  21. package/src/translations/pl.json +1 -0
  22. package/src/translations/zh_Hant.json +53 -9
  23. package/src/utils/API.js +20 -3
  24. package/src/utils/Exif.js +5 -10
  25. package/src/utils/Map.js +56 -10
  26. package/src/utils/Utils.js +68 -24
  27. package/src/utils/Widgets.js +64 -2
  28. package/src/viewer/URLHash.js +18 -3
  29. package/src/viewer/Widgets.css +139 -0
  30. package/src/viewer/Widgets.js +207 -42
  31. package/tests/Viewer.test.js +1 -0
  32. package/tests/__snapshots__/Editor.test.js.snap +1 -5
  33. package/tests/components/__snapshots__/Photo.test.js.snap +2 -10
  34. package/tests/utils/Exif.test.js +10 -10
  35. package/tests/utils/Map.test.js +8 -0
  36. package/tests/utils/__snapshots__/API.test.js.snap +5 -0
  37. package/tests/utils/__snapshots__/Widgets.test.js.snap +1 -1
  38. package/tests/viewer/__snapshots__/Widgets.test.js.snap +39 -29
@@ -3,9 +3,10 @@ import { PSV_ANIM_DURATION, PSV_ZOOM_DELTA, PIC_MAX_STAY_DURATION } from "../Vie
3
3
  import {
4
4
  createPanel, createGroup, fa, fat, createButton, disableButton,
5
5
  createSearchBar, createExpandableButton, enableButton, enableCopyButton, closeOtherPanels,
6
- createLinkCell, createTable, createHeader, createButtonSpan, createLabel
6
+ createLinkCell, createTable, createHeader, createButtonSpan, createLabel, showGrade,
7
+ showQualityScore,
7
8
  } from "../utils/Widgets";
8
- import { COLORS, isInIframe, getUserAccount } from "../utils/Utils";
9
+ import { COLORS, isInIframe, getUserAccount, QUALITYSCORE_VALUES, getGrade, QUALITYSCORE_GPS_VALUES, QUALITYSCORE_RES_360_VALUES, QUALITYSCORE_RES_FLAT_VALUES, QUALITYSCORE_POND_RES, QUALITYSCORE_POND_GPS } from "../utils/Utils";
9
10
  import SwitchBig from "../img/switch_big.svg";
10
11
  import SwitchMini from "../img/switch_mini.svg";
11
12
  import BackgroundAerial from "../img/bg_aerial.jpg";
@@ -50,6 +51,8 @@ import { faCircleQuestion } from "@fortawesome/free-solid-svg-icons/faCircleQues
50
51
  import { faCommentDots } from "@fortawesome/free-solid-svg-icons/faCommentDots";
51
52
  import { faAt } from "@fortawesome/free-solid-svg-icons/faAt";
52
53
  import { faPaperPlane } from "@fortawesome/free-solid-svg-icons/faPaperPlane";
54
+ import { faMedal } from "@fortawesome/free-solid-svg-icons/faMedal";
55
+ import { faInfoCircle } from "@fortawesome/free-solid-svg-icons/faInfoCircle";
53
56
 
54
57
 
55
58
  /**
@@ -112,7 +115,8 @@ export default class Widgets {
112
115
  this._initWidgetSearch();
113
116
  this._initWidgetFilters(
114
117
  this._viewer._api._endpoints.user_search !== null
115
- && this._viewer._api._endpoints.user_tiles !== null
118
+ && this._viewer._api._endpoints.user_tiles !== null,
119
+ this._viewer.map && this._viewer.map._hasQualityScore()
116
120
  );
117
121
  this._initWidgetMapLayers();
118
122
  this._listenMapFiltersChanges();
@@ -494,18 +498,30 @@ export default class Widgets {
494
498
 
495
499
  // Camera details
496
500
  popupContent.push(createHeader("h4", `${fat(faCamera)} ${this._t.gvs.metadata_camera}`));
497
- const focal = picMeta?.properties?.["pers:interior_orientation"]?.focal_length ? `${picMeta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "unknown";
501
+ const focal = picMeta?.properties?.["pers:interior_orientation"]?.focal_length ? `${picMeta?.properties?.["pers:interior_orientation"]?.focal_length} mm` : "";
502
+ let resmp = picMeta?.properties?.["pers:interior_orientation"]?.["sensor_array_dimensions"];
503
+ if(resmp) {
504
+ resmp = `${resmp[0]} x ${resmp[1]} px (${Math.floor(resmp[0] * resmp[1] / 1000000)} Mpx)`;
505
+ }
506
+ let pictype = this._t.gvs.picture_flat;
507
+ let picFov = picMeta?.properties?.["pers:interior_orientation"]?.["field_of_view"]; // Use raw value instead of horizontalFov to avoid default showing up
508
+ if(picFov !== null && picFov !== undefined) {
509
+ if(picFov === 360) { pictype = this._t.gvs.picture_360; }
510
+ else { pictype += ` (${picFov}°)`; }
511
+ }
512
+
498
513
  const cameraData = [
499
- { section: this._t.gvs.metadata_camera_make, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_manufacturer },
500
- { section: this._t.gvs.metadata_camera_model, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_model },
501
- { section: this._t.gvs.metadata_camera_type, value: picMeta?.horizontalFov === 360 ? this._t.gvs.picture_360 : this._t.gvs.picture_flat },
514
+ { section: this._t.gvs.metadata_camera_make, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_manufacturer || "❓" },
515
+ { section: this._t.gvs.metadata_camera_model, value: picMeta?.properties?.["pers:interior_orientation"]?.camera_model || "❓" },
516
+ { section: this._t.gvs.metadata_camera_type, value: pictype },
517
+ { section: this._t.gvs.metadata_camera_resolution, value: resmp || "❓" },
502
518
  { section: this._t.gvs.metadata_camera_focal_length, value: focal },
503
519
  ];
504
520
  popupContent.push(createTable("gvs-table-light", cameraData));
505
521
 
506
522
  // Location details
507
523
  popupContent.push(createHeader("h4", `${fat(faLocationDot)} ${this._t.gvs.metadata_location}`));
508
- const orientation = picMeta?.properties?.["view:azimuth"] !== undefined ? `${picMeta.properties["view:azimuth"]}°` : "unknown";
524
+ const orientation = picMeta?.properties?.["view:azimuth"] !== undefined ? `${picMeta.properties["view:azimuth"]}°` : "";
509
525
  const gpsPrecisionLabel = getGPSPrecision(picMeta);
510
526
  const locationData = [
511
527
  { section: this._t.gvs.metadata_location_longitude, value: picMeta.gps[0] },
@@ -515,6 +531,28 @@ export default class Widgets {
515
531
  ];
516
532
  popupContent.push(createTable("gvs-table-light", locationData));
517
533
 
534
+ // Picture quality level
535
+ if(this._viewer?.map?._hasQualityScore()) {
536
+ popupContent.push(createHeader(
537
+ "h4",
538
+ `${fat(faMedal)} ${this._t.gvs.metadata_quality} <a href="https://docs.panoramax.fr/pictures-metadata/quality_score/" target="_blank" title="${this._t.gvs.metadata_quality_help}">${fat(faInfoCircle)}</a>`
539
+ ));
540
+ const gpsGrade = getGrade(QUALITYSCORE_GPS_VALUES, picMeta?.properties?.["quality:horizontal_accuracy"]);
541
+ const resGrade = getGrade(
542
+ picMeta?.horizontalFov === 360 ? QUALITYSCORE_RES_360_VALUES : QUALITYSCORE_RES_FLAT_VALUES,
543
+ picMeta?.properties?.["panoramax:horizontal_pixel_density"]
544
+ );
545
+ // Note: score is also calculated in utils/map code
546
+ const generalGrade = Math.round((resGrade || 1) * QUALITYSCORE_POND_RES + (gpsGrade || 1) * QUALITYSCORE_POND_GPS);
547
+
548
+ const qualityData = [
549
+ { section: this._t.gvs.metadata_quality_score, value: showQualityScore(generalGrade) },
550
+ { section: this._t.gvs.metadata_quality_gps_score, value: showGrade(gpsGrade, this._t) },
551
+ { section: this._t.gvs.metadata_quality_resolution_score, value: showGrade(resGrade, this._t) },
552
+ ];
553
+ popupContent.push(createTable("gvs-table-light", qualityData));
554
+ }
555
+
518
556
  // EXIF
519
557
  if (picMeta.properties?.exif) {
520
558
  const exifDetails = document.createElement("details");
@@ -829,6 +867,7 @@ export default class Widgets {
829
867
  <option value="default">${this._t.gvs.map_theme_default}</option>
830
868
  <option value="age">${this._t.gvs.map_theme_age}</option>
831
869
  <option value="type">${this._t.gvs.map_theme_type}</option>
870
+ ${this._viewer?.map?._hasQualityScore() ? "<option value=\"score\">"+this._t.gvs.map_theme_score+"</option>" : ""}
832
871
  </select>
833
872
  </div>
834
873
  <div>
@@ -864,6 +903,9 @@ export default class Widgets {
864
903
  ${this._t.gvs.picture_flat}
865
904
  </div>
866
905
  </div>
906
+ <div id="gvs-map-theme-legend-score" class="gvs-map-theme-legend gvs-hidden">
907
+ ${QUALITYSCORE_VALUES.map(pv => "<span class=\"gvs-qualityscore\" style=\"background-color: "+pv.color+";\">"+pv.label+"</span>").join("")}
908
+ </div>
867
909
  </div>`;
868
910
 
869
911
  // Map theme events
@@ -929,29 +971,32 @@ export default class Widgets {
929
971
  * This should be called only if map is enabled.
930
972
  * @private
931
973
  */
932
- _initWidgetFilters(hasUserSearch) {
974
+ _initWidgetFilters(hasUserSearch, hasQualityScore) {
933
975
  const btnFilter = createExpandableButton("gvs-filter", faSliders, this._t.gvs.filters, this);
934
976
  const pnlFilter = createPanel(this, btnFilter, []);
935
977
  pnlFilter.innerHTML = `
936
978
  <form id="gvs-filter-form">
937
979
  <div id="gvs-filter-zoomin">${fat(faTriangleExclamation)} ${this._t.gvs.filter_zoom_in}</div>
938
980
  <h4>${fat(faCalendar)} ${this._t.gvs.filter_date}</h4>
981
+ <div class="gvs-input-shortcuts">
982
+ <button data-for="gvs-filter-date-from" data-value="${new Date(new Date().setMonth(new Date().getMonth() - 1)).toISOString().split("T")[0]}">${this._t.gvs.filter_date_1month}</button>
983
+ <button data-for="gvs-filter-date-from" data-value="${new Date(new Date().setFullYear(new Date().getFullYear() - 1)).toISOString().split("T")[0]}">${this._t.gvs.filter_date_1year}</button>
984
+ </div>
939
985
  <div class="gvs-input-group">
940
986
  <input type="date" id="gvs-filter-date-from" />
941
987
  ${fat(faArrowRight)}
942
988
  <input type="date" id="gvs-filter-date-end" />
943
989
  </div>
944
990
  <h4>${fat(faImage)} ${this._t.gvs.filter_picture}</h4>
945
- <div class="gvs-input-group" style="justify-content: center;">
991
+ <div class="gvs-input-group gvs-checkbox-btns" style="justify-content: center;">
946
992
  <input type="checkbox" id="gvs-filter-type-flat" name="flat" checked />
947
- <label for="gvs-filter-type-flat" style="margin-right: 20px">${this._t.gvs.picture_flat}</label>
993
+ <label for="gvs-filter-type-flat">${fat(faImage)} ${this._t.gvs.picture_flat}</label>
948
994
  <input type="checkbox" id="gvs-filter-type-360" name="360" checked />
949
- <label for="gvs-filter-type-360">${this._t.gvs.picture_360}</label>
995
+ <label for="gvs-filter-type-360">${fat(faPanorama)} ${this._t.gvs.picture_360}</label>
950
996
  </div>
951
- <!--h4>${fat(faCamera)} ${this._t.gvs.filter_camera_model}</h4>
952
- <div class="gvs-input-group" id="gvs-filter-model"></div-->
953
997
  </form>
954
998
  `;
999
+ const form = pnlFilter.children[0];
955
1000
  createGroup(
956
1001
  "gvs-widget-filter",
957
1002
  this._viewer.isWidthSmall() ? "main-top-right" : "main-top-left",
@@ -964,48 +1009,121 @@ export default class Widgets {
964
1009
  pnlFilter.style.width = `${this._viewer.container.offsetWidth - 70}px`;
965
1010
  }
966
1011
 
1012
+ // Create qualityscore filter
1013
+ if(hasQualityScore) {
1014
+ const title = document.createElement("h4");
1015
+ title.innerHTML = `${fat(faMedal)} ${this._t.gvs.filter_qualityscore}`;
1016
+ title.style.marginBottom = "3px";
1017
+ form.appendChild(title);
1018
+
1019
+ const div = document.createElement("div");
1020
+ div.id = "gvs-filter-qualityscore";
1021
+ div.classList.add("gvs-input-group");
1022
+
1023
+ QUALITYSCORE_VALUES.forEach(pv => {
1024
+ const input = document.createElement("input");
1025
+ input.id = "gvs-filter-qualityscore-" + pv.label;
1026
+ input.type = "checkbox";
1027
+ input.name = "qualityscore";
1028
+ input.value = pv.label;
1029
+
1030
+ const label = document.createElement("label");
1031
+ label.setAttribute("for", input.id);
1032
+ label.title = this._t.gvs.filter_qualityscore_help;
1033
+ label.appendChild(document.createTextNode(pv.label));
1034
+ label.style.backgroundColor = pv.color;
1035
+
1036
+ div.appendChild(input);
1037
+ div.appendChild(label);
1038
+ });
1039
+
1040
+ form.appendChild(div);
1041
+ }
1042
+
967
1043
  // Create search bar for users
968
1044
  if(hasUserSearch) {
969
- const form = pnlFilter.querySelector("#gvs-filter-form");
970
-
971
1045
  const title = document.createElement("h4");
972
1046
  title.innerHTML = `${fat(faUser)} ${this._t.gvs.filter_user}`;
973
1047
  form.appendChild(title);
974
1048
 
1049
+ // Shortcut for my own pictures
1050
+ const userAccount = getUserAccount();
1051
+ let mypics;
1052
+ if(userAccount) {
1053
+ const shortcuts = document.createElement("div");
1054
+ shortcuts.classList.add("gvs-input-shortcuts");
1055
+ mypics = document.createElement("button");
1056
+ mypics.appendChild(document.createTextNode(this._t.gvs.filter_user_mypics));
1057
+ shortcuts.appendChild(mypics);
1058
+ form.appendChild(shortcuts);
1059
+ }
1060
+
975
1061
  const input = document.createElement("div");
976
1062
  input.id = "gvs-filter-user";
977
1063
  input.classList.add("gvs-input-group");
978
1064
 
979
- const userSearch = createSearchBar(
1065
+ let userSearch;
1066
+ userSearch = createSearchBar(
980
1067
  "gvs-filter-search-user",
981
1068
  this._t.gvs.search_user,
982
- q => this._viewer._api.searchUsers(q)
983
- .then(data => ((data || [])
984
- .map(f => ({
985
- title: f.label,
986
- data: f
987
- }))
988
- )),
989
- d => this._viewer.map.setVisibleUsers(d ? [d.data.id] : ["geovisio"]),
1069
+ q => {
1070
+ userSearch.classList.remove("gvs-filter-active");
1071
+ return this._viewer._api.searchUsers(q)
1072
+ .then(data => ((data || [])
1073
+ .map(f => ({
1074
+ title: f.label,
1075
+ data: f
1076
+ }))
1077
+ ));
1078
+ },
1079
+ d => {
1080
+ if(d) { userSearch.classList.add("gvs-filter-active"); }
1081
+ else { userSearch.classList.remove("gvs-filter-active"); }
1082
+ return this._viewer.map.setVisibleUsers(d ? [d.data.id] : ["geovisio"]);
1083
+ },
990
1084
  this,
991
1085
  true
992
1086
  );
993
1087
  input.appendChild(userSearch);
994
1088
  form.appendChild(input);
1089
+
1090
+ // Trigger "my pictures" shortcut action
1091
+ if(userAccount) {
1092
+ mypics.addEventListener("click", () => {
1093
+ const userInput = userSearch.querySelector("input");
1094
+ if(userInput.value === userAccount.name) {
1095
+ userSearch.resetSearch();
1096
+ }
1097
+ else {
1098
+ userInput.goItem({ title: userAccount.name, data: { id: userAccount.id } });
1099
+ }
1100
+ });
1101
+ }
995
1102
  }
996
1103
 
997
- // Create search bar for camera model
998
- // TODO : implement when API is ready
999
- // const cameraSearch = createSearchBar(
1000
- // "gvs-filter-camera-model",
1001
- // this._t.gvs.search,
1002
- // () => Promise.reject(),
1003
- // () => {},
1004
- // this
1005
- // );
1006
- // document.getElementById("gvs-filter-model").appendChild(cameraSearch);
1104
+ // Shortcuts
1105
+ pnlFilter.querySelectorAll(".gvs-input-shortcuts button").forEach(btn => {
1106
+ btn.addEventListener("click", () => {
1107
+ const elem = document.getElementById(btn.getAttribute("data-for"));
1108
+ const val = btn.getAttribute("data-value");
1109
+ if(elem) {
1110
+ if(elem.value !== val) { elem.value = val; }
1111
+ else { elem.value = ""; }
1112
+ }
1113
+ });
1114
+ });
1007
1115
 
1008
- const form = pnlFilter.children[0];
1116
+ // Fields change events (for active highlighting)
1117
+ const fMinDate = document.getElementById("gvs-filter-date-from");
1118
+ const fMaxDate = document.getElementById("gvs-filter-date-end");
1119
+ [fMinDate, fMaxDate].forEach(f => {
1120
+ f.addEventListener("change", () => {
1121
+ if(f.value) { f.classList.add("gvs-filter-active"); }
1122
+ else { f.classList.remove("gvs-filter-active"); }
1123
+ });
1124
+ });
1125
+
1126
+ // Form update events
1009
1127
  this._formDelay = null;
1010
1128
 
1011
1129
  const onFormChange = () => {
@@ -1041,19 +1159,33 @@ export default class Widgets {
1041
1159
  const fMaxDate = document.getElementById("gvs-filter-date-end");
1042
1160
  const fTypeFlat = document.getElementById("gvs-filter-type-flat");
1043
1161
  const fType360 = document.getElementById("gvs-filter-type-360");
1044
- // const fCamera = document.getElementById("gvs-filter-camera");
1045
1162
  const fMapTheme = document.getElementById("gvs-map-theme");
1046
1163
 
1047
1164
  let type = "";
1048
1165
  if(fType360.checked && !fTypeFlat.checked) { type = "equirectangular"; }
1049
1166
  if(!fType360.checked && fTypeFlat.checked) { type = "flat"; }
1050
1167
 
1168
+ let qualityscore = [];
1169
+ if(this._viewer?.map?._hasQualityScore()) {
1170
+ const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
1171
+ const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
1172
+ const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
1173
+ const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
1174
+ const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
1175
+ if(fScoreA.checked) { qualityscore.push(5); }
1176
+ if(fScoreB.checked) { qualityscore.push(4); }
1177
+ if(fScoreC.checked) { qualityscore.push(3); }
1178
+ if(fScoreD.checked) { qualityscore.push(2); }
1179
+ if(fScoreE.checked) { qualityscore.push(1); }
1180
+ if(qualityscore.length == 5) { qualityscore = []; }
1181
+ }
1182
+
1051
1183
  const values = {
1052
1184
  minDate: fMinDate.value,
1053
1185
  maxDate: fMaxDate.value,
1054
1186
  type,
1055
- // camera: fCamera.value,
1056
1187
  theme: fMapTheme.value,
1188
+ qualityscore,
1057
1189
  };
1058
1190
 
1059
1191
  this._viewer.setFilters(values);
@@ -1064,26 +1196,59 @@ export default class Widgets {
1064
1196
  * @private
1065
1197
  */
1066
1198
  _listenMapFiltersChanges() {
1199
+ const btnFilter = document.getElementById("gvs-filter");
1067
1200
  const fMinDate = document.getElementById("gvs-filter-date-from");
1068
1201
  const fMaxDate = document.getElementById("gvs-filter-date-end");
1069
1202
  const fTypeFlat = document.getElementById("gvs-filter-type-flat");
1070
1203
  const fType360 = document.getElementById("gvs-filter-type-360");
1071
- // const fCamera = document.getElementById("gvs-filter-camera");
1072
1204
  const fMapTheme = document.getElementById("gvs-map-theme");
1205
+ const fScoreA = document.getElementById("gvs-filter-qualityscore-A");
1206
+ const fScoreB = document.getElementById("gvs-filter-qualityscore-B");
1207
+ const fScoreC = document.getElementById("gvs-filter-qualityscore-C");
1208
+ const fScoreD = document.getElementById("gvs-filter-qualityscore-D");
1209
+ const fScoreE = document.getElementById("gvs-filter-qualityscore-E");
1073
1210
 
1074
1211
  // Update widget based on programmatic filter changes
1075
1212
  this._viewer.addEventListener("filters-changed", e => {
1076
- if(e.detail.minDate) { fMinDate.value = e.detail.minDate; }
1077
- if(e.detail.maxDate) { fMaxDate.value = e.detail.maxDate; }
1078
- // if(e.detail.camera) { fCamera.value = e.detail.camera; }
1213
+ if(e.detail.minDate) {
1214
+ fMinDate.value = e.detail.minDate;
1215
+ fMinDate.classList.add("gvs-filter-active");
1216
+ }
1217
+ else { fMinDate.classList.remove("gvs-filter-active"); }
1218
+ if(e.detail.maxDate) {
1219
+ fMaxDate.value = e.detail.maxDate;
1220
+ fMaxDate.classList.add("gvs-filter-active");
1221
+ }
1222
+ else { fMaxDate.classList.remove("gvs-filter-active"); }
1079
1223
  if(e.detail.theme) { fMapTheme.value = e.detail.theme; }
1080
1224
  if(e.detail.type) {
1081
1225
  fType360.checked = ["", "equirectangular"].includes(e.detail.type);
1082
1226
  fTypeFlat.checked = ["", "flat"].includes(e.detail.type);
1083
1227
  }
1228
+ if(e.detail.qualityscore) {
1229
+ fScoreA.checked = e.detail.qualityscore.includes(5) && e.detail.qualityscore.length < 5;
1230
+ fScoreB.checked = e.detail.qualityscore.includes(4) && e.detail.qualityscore.length < 5;
1231
+ fScoreC.checked = e.detail.qualityscore.includes(3) && e.detail.qualityscore.length < 5;
1232
+ fScoreD.checked = e.detail.qualityscore.includes(2) && e.detail.qualityscore.length < 5;
1233
+ fScoreE.checked = e.detail.qualityscore.includes(1) && e.detail.qualityscore.length < 5;
1234
+ }
1235
+ let activeFilters = (
1236
+ Object.keys(e.detail).filter(d => d && d !== "theme").length > 0
1237
+ || this._viewer.map.getVisibleUsers().filter(u => u !== "geovisio").length > 0
1238
+ );
1239
+ btnFilter.setActive(activeFilters);
1084
1240
  this._onMapThemeChange();
1085
1241
  });
1086
1242
 
1243
+ // Listen to user visibility changes to switch the filter active icon
1244
+ this._viewer.addEventListener("map:users-changed", e => {
1245
+ let activeFilters = (
1246
+ Object.keys(this._viewer._mapFilters).filter(d => d && d !== "theme").length > 0
1247
+ || e.detail.usersIds.filter(u => u !== "geovisio").length > 0
1248
+ );
1249
+ btnFilter.setActive(activeFilters);
1250
+ });
1251
+
1087
1252
  // Show/hide zoom in warning when map zoom changes
1088
1253
  const lblZoomIn = document.getElementById("gvs-filter-zoomin");
1089
1254
  const changeLblZoomInDisplay = () => {
@@ -344,6 +344,7 @@ describe("setFilters", () => {
344
344
  reloadLayersStyles: jest.fn(),
345
345
  filterUserLayersContent: jest.fn(),
346
346
  getVisibleUsers: () => ["geovisio"],
347
+ _hasGridStats: () => false,
347
348
  };
348
349
  v.dispatchEvent = jest.fn();
349
350
  Date.prototype.getDate = () => 8;
@@ -185,11 +185,7 @@ Object {
185
185
  "nextPic": undefined,
186
186
  "prevPic": undefined,
187
187
  },
188
- "sphereCorrection": Object {
189
- "pan": 0,
190
- "roll": 0,
191
- "tilt": 0,
192
- },
188
+ "sphereCorrection": Object {},
193
189
  }
194
190
  `;
195
191
 
@@ -104,11 +104,7 @@ Object {
104
104
  "nextPic": "ccf5f468-38b8-4b42-8d79-b97f5391e0a3",
105
105
  "prevPic": "127d9282-d082-46ca-bc09-4b0d8569dd2c",
106
106
  },
107
- "sphereCorrection": Object {
108
- "pan": 0,
109
- "roll": 0,
110
- "tilt": 0,
111
- },
107
+ "sphereCorrection": Object {},
112
108
  }
113
109
  `;
114
110
 
@@ -177,11 +173,7 @@ Object {
177
173
  "nextPic": "ccf5f468-38b8-4b42-8d79-b97f5391e0a3",
178
174
  "prevPic": "127d9282-d082-46ca-bc09-4b0d8569dd2c",
179
175
  },
180
- "sphereCorrection": Object {
181
- "pan": 0,
182
- "roll": 0,
183
- "tilt": 0,
184
- },
176
+ "sphereCorrection": Object {},
185
177
  }
186
178
  `;
187
179
 
@@ -19,22 +19,22 @@ describe("getExifFloat", () => {
19
19
 
20
20
  describe("getGPSPrecision", () => {
21
21
  it.each([
22
- [undefined, "unknown"],
23
- [0.4, "ideal"],
24
- [0.9, "excellent"],
25
- [2.9, "good"],
26
- [6.9, "moderate"],
27
- [9.9, "fair"],
28
- [20, "poor"],
29
- ["9/10", "excellent"],
30
- ["99/10", "fair"],
22
+ [undefined, ""],
23
+ [0.4, "0.4 m"],
24
+ [0.9, "0.9 m"],
25
+ [2.9, "2.9 m"],
26
+ [6.9, "6.9 m"],
27
+ [9.9, "9.9 m"],
28
+ [20, "20 m"],
29
+ ["9/10", "0.9 m"],
30
+ ["99/10", "9.9 m"],
31
31
  ])("handles GPSHPos %s > %s", (input, expected) => {
32
32
  const p = { properties: { exif: { "Exif.GPSInfo.GPSHPositioningError": input, "Exif.GPSInfo.GPSDOP": input } } };
33
33
  expect(Exif.getGPSPrecision(p)).toBe(expected);
34
34
  });
35
35
 
36
36
  it.each([
37
- [undefined, "unknown"],
37
+ [undefined, ""],
38
38
  [0.9, "ideal"],
39
39
  [1.9, "excellent"],
40
40
  [4.9, "good"],
@@ -50,6 +50,14 @@ describe("getUserSourceId", () => {
50
50
  });
51
51
  });
52
52
 
53
+ describe("switchCoefValue", () => {
54
+ it("works", () => {
55
+ const l = {id: "bla", paint: { "circle-radius": ["bla", ["get", "coef"]]}, layout: {"circle-sort": "coef"}};
56
+ const res = Map.switchCoefValue(l, "coef_360");
57
+ expect(res).toEqual({id: "bla", paint: { "circle-radius": ["bla", ["get", "coef_360"]]}, layout: {"circle-sort": "coef_360"}})
58
+ });
59
+ });
60
+
53
61
  describe("geocoderParamsToURLString", () => {
54
62
  it("works", () => {
55
63
  const p = { bla: "blorg", you: 1 };
@@ -3,6 +3,7 @@
3
3
  exports[`_loadMapStyles handles default user 1`] = `
4
4
  Object {
5
5
  "layers": Array [],
6
+ "metadata": Object {},
6
7
  "sources": Object {
7
8
  "geovisio": Object {
8
9
  "maxzoom": 15,
@@ -30,6 +31,7 @@ Object {
30
31
  "id": "provider_blo",
31
32
  },
32
33
  ],
34
+ "metadata": Object {},
33
35
  "sources": Object {
34
36
  "provider": Object {},
35
37
  "provider_bla": Object {},
@@ -46,6 +48,7 @@ Object {
46
48
  "id": "provlayer",
47
49
  },
48
50
  ],
51
+ "metadata": Object {},
49
52
  "name": "Provider",
50
53
  "sources": Object {
51
54
  "geovisio": Object {
@@ -69,6 +72,7 @@ Object {
69
72
  "id": "provlayer",
70
73
  },
71
74
  ],
75
+ "metadata": Object {},
72
76
  "name": "Provider",
73
77
  "sources": Object {
74
78
  "geovisio": Object {
@@ -88,6 +92,7 @@ Object {
88
92
  exports[`_loadMapStyles works if no background style set 1`] = `
89
93
  Object {
90
94
  "layers": Array [],
95
+ "metadata": Object {},
91
96
  "sources": Object {
92
97
  "geovisio": Object {
93
98
  "maxzoom": 15,
@@ -21,7 +21,7 @@ Array [
21
21
  ]
22
22
  `;
23
23
 
24
- exports[`createSearchBar works 1`] = `"<input type=\\"text\\" placeholder=\\"no res\\"><span class=\\"gvs-search-bar-icon\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" data-prefix=\\"fas\\" data-icon=\\"magnifying-glass\\" class=\\"svg-inline--fa fa-magnifying-glass\\" role=\\"img\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 512 512\\"><path fill=\\"currentColor\\" d=\\"M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z\\"></path></svg></span><div id=\\"mysrch-panel\\" class=\\"gvs-panel gvs-widget-bg gvs-hidden gvs-search-bar-results\\"></div>"`;
24
+ exports[`createSearchBar works 1`] = `"<input type=\\"text\\" placeholder=\\"no res\\" id=\\"mysrch-input\\"><span class=\\"gvs-search-bar-icon\\"><svg aria-hidden=\\"true\\" focusable=\\"false\\" data-prefix=\\"fas\\" data-icon=\\"magnifying-glass\\" class=\\"svg-inline--fa fa-magnifying-glass\\" role=\\"img\\" xmlns=\\"http://www.w3.org/2000/svg\\" viewBox=\\"0 0 512 512\\"><path fill=\\"currentColor\\" d=\\"M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z\\"></path></svg></span><div id=\\"mysrch-panel\\" class=\\"gvs-panel gvs-widget-bg gvs-hidden gvs-search-bar-results\\"></div>"`;
25
25
 
26
26
  exports[`fa works 1`] = `
27
27
  <svg