bruce-cesium 7.5.4 → 7.5.6
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/dist/bruce-cesium.es5.js +850 -348
- package/dist/bruce-cesium.es5.js.map +1 -1
- package/dist/bruce-cesium.umd.js +848 -346
- package/dist/bruce-cesium.umd.js.map +1 -1
- package/dist/lib/bruce-cesium.js +1 -1
- package/dist/lib/internal/image-utils.js +24 -1
- package/dist/lib/internal/image-utils.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-point.js +16 -25
- package/dist/lib/rendering/entity-render-engine-point.js.map +1 -1
- package/dist/lib/rendering/entity-render-engine-polygon.js +16 -14
- package/dist/lib/rendering/entity-render-engine-polygon.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label-group.js +178 -64
- package/dist/lib/rendering/render-managers/common/entity-label-group.js.map +1 -1
- package/dist/lib/rendering/render-managers/common/entity-label.js +437 -71
- package/dist/lib/rendering/render-managers/common/entity-label.js.map +1 -1
- package/dist/lib/rendering/render-managers/other/google-search-render-manager.js +32 -37
- package/dist/lib/rendering/render-managers/other/google-search-render-manager.js.map +1 -1
- package/dist/lib/rendering/visuals-register.js +16 -2
- package/dist/lib/rendering/visuals-register.js.map +1 -1
- package/dist/types/bruce-cesium.d.ts +1 -1
- package/dist/types/internal/image-utils.d.ts +1 -0
- package/dist/types/rendering/render-managers/common/entity-label-group.d.ts +9 -1
- package/dist/types/rendering/render-managers/common/entity-label.d.ts +13 -2
- package/dist/types/rendering/visuals-register.d.ts +1 -0
- package/package.json +1 -1
package/dist/bruce-cesium.es5.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Cesium from 'cesium';
|
|
2
|
-
import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, HeightReference, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model,
|
|
3
|
-
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, Carto, Geometry as Geometry$1,
|
|
2
|
+
import { Cartographic, ColorMaterialProperty, Entity, Color, ConstantProperty, CallbackProperty, Primitive, Cesium3DTileFeature, HeightReference, ColorBlendMode, HeadingPitchRoll, Math as Math$1, Transforms, ShadowMode, Cartesian3, ClassificationType, Model, ConstantPositionProperty, DistanceDisplayCondition, JulianDate, Quaternion, Matrix4, HorizontalOrigin, VerticalOrigin, ArcType, CornerType, ImageMaterialProperty, PolygonHierarchy, PolylineGraphics, Rectangle, ShaderSource, Ellipsoid, Geometry, GeometryAttribute, ComponentDatatype, PrimitiveType, BoundingSphere, GeometryPipeline, Cartesian2, Cartesian4, sampleTerrain, Texture, PixelFormat, Sampler, TextureWrap, TextureMinificationFilter, TextureMagnificationFilter, VertexArray, BufferUsage, ShaderProgram, RenderState, BlendingState, DrawCommand, Pass, SceneTransforms, FeatureDetection, SceneMode, NearFarScalar, Matrix3, KmlDataSource, GeoJsonDataSource, Cesium3DTileStyle, HeadingPitchRange, Cesium3DTileColorBlendMode, Ion, IonResource, EllipsoidTerrainProvider, IonImageryProvider, createWorldImagery, createWorldImageryAsync, BingMapsImageryProvider, BingMapsStyle, MapboxImageryProvider, MapboxStyleImageryProvider, ArcGisMapServerImageryProvider, OpenStreetMapImageryProvider, UrlTemplateImageryProvider, GridImageryProvider, GeographicTilingScheme, ImageryLayer, TileMapServiceImageryProvider, CesiumTerrainProvider, Cesium3DTileset, Intersect, OrthographicFrustum, EasingFunction, CustomDataSource, PolygonPipeline, ModelGraphics, PolygonGraphics, CorridorGraphics, PointGraphics, BillboardGraphics, EllipseGraphics, PolylineDashMaterialProperty, EllipsoidGeodesic, sampleTerrainMostDetailed, defined, CesiumInspector, ClockRange, DynamicEnvironmentMapManager, GeometryInstance, ScreenSpaceEventHandler, ScreenSpaceEventType, Fullscreen, CzmlDataSource } from 'cesium';
|
|
3
|
+
import { Cartes, LRUCache, ProjectViewTile, ENVIRONMENT, Calculator, ClientFile, Entity as Entity$1, EntityLod, EntityTag, EntityType, ObjectUtils, Style, Api, Carto, Geometry as Geometry$1, Bounds, ClientFileValueMap, Color as Color$1, MenuItem, ProjectView, ProjectViewBookmark, Tileset, ZoomControl, BruceEvent, EntityCoords, DataLab, DelayQueue, AccountConcept, EntityRelation, RecordChangeFeed, BruceApi, EntitySource, ProgramKey, ProjectViewLegacyTile, Camera, Account, AccountLimits, Session, UrlUtils, ProjectViewBookmarkGroup, EntityAttachment, EntityAttachmentType, EntityAttribute, MathUtils, EntityRelationType, EntityHistoricData } from 'bruce-models';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Ensures a number is returned from a given value.
|
|
@@ -5531,11 +5531,196 @@ var EntityLabel;
|
|
|
5531
5531
|
const ICON_RING_PADDING = 4;
|
|
5532
5532
|
// Small points still get a ring as large as the default near ring.
|
|
5533
5533
|
const ICON_RING_MIN = 10;
|
|
5534
|
+
// A photo grows by this much while its entity is hovered, since it is content worth a closer look.
|
|
5535
|
+
const PHOTO_HOVER_SCALE = 2.5;
|
|
5536
|
+
// How long a label must stay on show before its photo is fetched, so labels that only flash past while
|
|
5537
|
+
// clusters settle or the camera sweeps by cost nothing.
|
|
5538
|
+
const PHOTO_SHOW_DELAY = 250;
|
|
5539
|
+
// Photos rest this much larger than an icon on its chip, since they are content rather than a marker.
|
|
5540
|
+
const PHOTO_SIZE_SCALE = 1.5;
|
|
5541
|
+
// Corner radius of a photo too oblong for a circle, relative to its longest side.
|
|
5542
|
+
const PHOTO_CORNER = 0.15;
|
|
5543
|
+
// How long a photo takes to grow into its label, and to grow on hover.
|
|
5544
|
+
const PHOTO_TRANSITION = 250;
|
|
5545
|
+
// Downsized photos kept per viewer before the least recently used is released.
|
|
5546
|
+
const PHOTO_CACHE_LIMIT = 500;
|
|
5547
|
+
// Key where the viewer keeps its downsized photos.
|
|
5548
|
+
const PHOTO_CACHE_KEY = "_nextspaceLabelPhotos";
|
|
5534
5549
|
// One per tinted icon, since an SVG mask is addressed by id across the whole document.
|
|
5535
5550
|
let tintCounter = 0;
|
|
5536
5551
|
// Key where the label instance is stored within the cesium entity.
|
|
5537
5552
|
// Eg: rego[ATTACH_KEY] = new EntityLabel(...);
|
|
5538
5553
|
EntityLabel.ATTACH_KEY = "nextspace-navigator-entity-label";
|
|
5554
|
+
// Image types a label draws, read from the file extension. Anything else stays text.
|
|
5555
|
+
const IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "bmp", "svg", "webp"];
|
|
5556
|
+
// Shortest side a thumbnail needs before it is preferred over the full file.
|
|
5557
|
+
const IMAGE_THUMBNAIL_MIN = 64;
|
|
5558
|
+
// Size a found image is drawn at when the group names none, so a photo never lands at its natural size.
|
|
5559
|
+
const IMAGE_SIZE = 24;
|
|
5560
|
+
function isImageFile(name) {
|
|
5561
|
+
return typeof name === "string" && IMAGE_EXTENSIONS.includes(name.split(".").pop().toLowerCase());
|
|
5562
|
+
}
|
|
5563
|
+
// Whether a label was configured with an icon of its own, which always outranks a found photo.
|
|
5564
|
+
function HasIcon(options) {
|
|
5565
|
+
return Boolean(options.iconUrl || options.iconClass || options.icon);
|
|
5566
|
+
}
|
|
5567
|
+
EntityLabel.HasIcon = HasIcon;
|
|
5568
|
+
/*
|
|
5569
|
+
* Whether a label line is an http(s) link to an image, judged by its path so a query string cannot pass for one.
|
|
5570
|
+
*/
|
|
5571
|
+
function IsImageUrl(value) {
|
|
5572
|
+
const text = value === null || value === void 0 ? void 0 : value.trim();
|
|
5573
|
+
if (!text || /\s/.test(text)) {
|
|
5574
|
+
return false;
|
|
5575
|
+
}
|
|
5576
|
+
let url;
|
|
5577
|
+
try {
|
|
5578
|
+
url = new URL(text);
|
|
5579
|
+
}
|
|
5580
|
+
catch {
|
|
5581
|
+
return false;
|
|
5582
|
+
}
|
|
5583
|
+
return (url.protocol === "http:" || url.protocol === "https:") && isImageFile(url.pathname);
|
|
5584
|
+
}
|
|
5585
|
+
EntityLabel.IsImageUrl = IsImageUrl;
|
|
5586
|
+
/*
|
|
5587
|
+
* The entity's default image as InfoView picks it: its first photo attachment by display order that is an image file.
|
|
5588
|
+
* Reads attachments expanded onto the entity, and prefers the smallest thumbnail that still covers an icon.
|
|
5589
|
+
*/
|
|
5590
|
+
function GetDefaultImageUrl(entity, api) {
|
|
5591
|
+
var _a, _b, _c;
|
|
5592
|
+
const attachments = (_b = (_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.Attachment) !== null && _b !== void 0 ? _b : [];
|
|
5593
|
+
const photo = attachments
|
|
5594
|
+
.filter(x => (x === null || x === void 0 ? void 0 : x["EntityAttachmentType.ID"]) == EntityAttachmentType.EType.Photo && x.ClientFile)
|
|
5595
|
+
.filter(x => isImageFile(x.ClientFile.FileExt || x.ClientFile.OriginalFileName))
|
|
5596
|
+
.sort((a, b) => { var _a, _b; return ((_a = a.DisplayOrder) !== null && _a !== void 0 ? _a : 0) - ((_b = b.DisplayOrder) !== null && _b !== void 0 ? _b : 0); })[0];
|
|
5597
|
+
if (!photo) {
|
|
5598
|
+
return null;
|
|
5599
|
+
}
|
|
5600
|
+
const thumbnail = ((_c = photo.ClientFile.Thumbnails) !== null && _c !== void 0 ? _c : [])
|
|
5601
|
+
.filter(x => Math.min(x.Width, x.Height) >= IMAGE_THUMBNAIL_MIN && (x.URL || x["ClientFile.ID"]))
|
|
5602
|
+
.sort((a, b) => a.Width * a.Height - b.Width * b.Height)[0];
|
|
5603
|
+
if (thumbnail) {
|
|
5604
|
+
return thumbnail.URL || ClientFile.GetUrl({ fileId: thumbnail["ClientFile.ID"], api, viaCdn: true });
|
|
5605
|
+
}
|
|
5606
|
+
return photo.ClientFile.URL || photo.URL || ClientFile.GetUrl({ fileId: photo["ClientFile.ID"], api, viaCdn: true });
|
|
5607
|
+
}
|
|
5608
|
+
EntityLabel.GetDefaultImageUrl = GetDefaultImageUrl;
|
|
5609
|
+
/*
|
|
5610
|
+
* Gives a label an image it found rather than one it was configured with.
|
|
5611
|
+
* A text label gains it beside the text, or alone when the image was all the text there was.
|
|
5612
|
+
*/
|
|
5613
|
+
function WithPhoto(options, url) {
|
|
5614
|
+
var _a, _b, _c, _d;
|
|
5615
|
+
const presentation = (_a = options.presentation) !== null && _a !== void 0 ? _a : "text";
|
|
5616
|
+
options.iconUrl = url;
|
|
5617
|
+
options.iconPhoto = true;
|
|
5618
|
+
// A tint would flatten a photo into a silhouette of its own outline.
|
|
5619
|
+
options.iconColor = undefined;
|
|
5620
|
+
(_b = options.iconSize) !== null && _b !== void 0 ? _b : (options.iconSize = IMAGE_SIZE);
|
|
5621
|
+
if (!((_c = options.text) === null || _c === void 0 ? void 0 : _c.trim())) {
|
|
5622
|
+
options.presentation = "icon";
|
|
5623
|
+
}
|
|
5624
|
+
else if (presentation === "text") {
|
|
5625
|
+
options.presentation = "icon-text-side";
|
|
5626
|
+
}
|
|
5627
|
+
if (presentation === "text") {
|
|
5628
|
+
// Keeps the leader a text label draws by default.
|
|
5629
|
+
(_d = options.leader) !== null && _d !== void 0 ? _d : (options.leader = true);
|
|
5630
|
+
}
|
|
5631
|
+
return options;
|
|
5632
|
+
}
|
|
5633
|
+
EntityLabel.WithPhoto = WithPhoto;
|
|
5634
|
+
/*
|
|
5635
|
+
* Near-square photos are cropped to a circle the size of an icon chip; the rest keep their shape with rounded corners.
|
|
5636
|
+
* An unknown size reads as square, since the circle crops whatever arrives.
|
|
5637
|
+
*/
|
|
5638
|
+
function PhotoShape(width, height) {
|
|
5639
|
+
if (!(width > 0 && height > 0)) {
|
|
5640
|
+
return "circle";
|
|
5641
|
+
}
|
|
5642
|
+
const longest = Math.max(width, height);
|
|
5643
|
+
return (longest - Math.min(width, height)) / longest <= ICON_BACKGROUND_UNIFORM_TOLERANCE ? "circle" : "rect";
|
|
5644
|
+
}
|
|
5645
|
+
EntityLabel.PhotoShape = PhotoShape;
|
|
5646
|
+
/*
|
|
5647
|
+
* Loads a photo through a plain image element, so a host without CORS still works, and redraws it once onto a canvas
|
|
5648
|
+
* no larger than it is shown. Labels draw from that canvas, so none of them holds a camera original.
|
|
5649
|
+
*/
|
|
5650
|
+
async function shrinkPhoto(url, pixels) {
|
|
5651
|
+
const none = { source: null, width: 0, height: 0 };
|
|
5652
|
+
const image = new Image();
|
|
5653
|
+
image.decoding = "async";
|
|
5654
|
+
image.src = url;
|
|
5655
|
+
try {
|
|
5656
|
+
await image.decode();
|
|
5657
|
+
}
|
|
5658
|
+
catch {
|
|
5659
|
+
return none;
|
|
5660
|
+
}
|
|
5661
|
+
const width = image.naturalWidth;
|
|
5662
|
+
const height = image.naturalHeight;
|
|
5663
|
+
if (!width || !height) {
|
|
5664
|
+
return none;
|
|
5665
|
+
}
|
|
5666
|
+
// A circle only ever shows the centre square, so that is all worth keeping.
|
|
5667
|
+
const circle = PhotoShape(width, height) === "circle";
|
|
5668
|
+
const sourceWidth = circle ? Math.min(width, height) : width;
|
|
5669
|
+
const sourceHeight = circle ? Math.min(width, height) : height;
|
|
5670
|
+
const scale = Math.min(1, pixels / Math.max(sourceWidth, sourceHeight));
|
|
5671
|
+
const canvas = document.createElement("canvas");
|
|
5672
|
+
canvas.width = Math.max(1, Math.round(sourceWidth * scale));
|
|
5673
|
+
canvas.height = Math.max(1, Math.round(sourceHeight * scale));
|
|
5674
|
+
const context = canvas.getContext("2d");
|
|
5675
|
+
if (!context) {
|
|
5676
|
+
return none;
|
|
5677
|
+
}
|
|
5678
|
+
context.imageSmoothingQuality = "high";
|
|
5679
|
+
context.drawImage(image, (width - sourceWidth) / 2, (height - sourceHeight) / 2, sourceWidth, sourceHeight, 0, 0, canvas.width, canvas.height);
|
|
5680
|
+
return { source: canvas, width, height };
|
|
5681
|
+
}
|
|
5682
|
+
/*
|
|
5683
|
+
* Downsized label photos, kept on the viewer so every label in a scene shares one load and one decode.
|
|
5684
|
+
*/
|
|
5685
|
+
class Photos {
|
|
5686
|
+
constructor() {
|
|
5687
|
+
this.entries = new Map();
|
|
5688
|
+
this.ready = new Map();
|
|
5689
|
+
}
|
|
5690
|
+
static Get(viewer) {
|
|
5691
|
+
var _a;
|
|
5692
|
+
return (_a = viewer[PHOTO_CACHE_KEY]) !== null && _a !== void 0 ? _a : (viewer[PHOTO_CACHE_KEY] = new Photos());
|
|
5693
|
+
}
|
|
5694
|
+
// The photo when it is already drawn, so a rebuilt label shows it at once instead of growing it again.
|
|
5695
|
+
Peek(url, pixels) {
|
|
5696
|
+
return this.ready.get(`${pixels} ${url}`);
|
|
5697
|
+
}
|
|
5698
|
+
Load(url, pixels) {
|
|
5699
|
+
const key = `${pixels} ${url}`;
|
|
5700
|
+
let entry = this.entries.get(key);
|
|
5701
|
+
if (entry) {
|
|
5702
|
+
// Reinsertion keeps the map in least recently used order.
|
|
5703
|
+
this.entries.delete(key);
|
|
5704
|
+
this.entries.set(key, entry);
|
|
5705
|
+
return entry;
|
|
5706
|
+
}
|
|
5707
|
+
entry = shrinkPhoto(url, pixels).then(photo => {
|
|
5708
|
+
if (this.entries.get(key) === entry) {
|
|
5709
|
+
this.ready.set(key, photo);
|
|
5710
|
+
}
|
|
5711
|
+
return photo;
|
|
5712
|
+
});
|
|
5713
|
+
this.entries.set(key, entry);
|
|
5714
|
+
for (const oldest of this.entries.keys()) {
|
|
5715
|
+
if (this.entries.size <= PHOTO_CACHE_LIMIT) {
|
|
5716
|
+
break;
|
|
5717
|
+
}
|
|
5718
|
+
this.entries.delete(oldest);
|
|
5719
|
+
this.ready.delete(oldest);
|
|
5720
|
+
}
|
|
5721
|
+
return entry;
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5539
5724
|
/*
|
|
5540
5725
|
* Indexes screen rectangles so sparse scenes do not compare every label with every other label.
|
|
5541
5726
|
*/
|
|
@@ -6251,6 +6436,10 @@ var EntityLabel;
|
|
|
6251
6436
|
this.cursorMotion = { amount: 0, from: 0, target: 0, start: 0, position: undefined };
|
|
6252
6437
|
// Counter to help differentiate between createLabel calls.
|
|
6253
6438
|
this.counter = 0;
|
|
6439
|
+
// Whether this label has asked its group for a default image, which it does once on screen.
|
|
6440
|
+
this.imageAsked = false;
|
|
6441
|
+
// The photo a label outside any group found while reading its attributes.
|
|
6442
|
+
this.photo = null;
|
|
6254
6443
|
// The label element itself.
|
|
6255
6444
|
this.label = null;
|
|
6256
6445
|
// Callback to kill render listener.
|
|
@@ -6285,6 +6474,7 @@ var EntityLabel;
|
|
|
6285
6474
|
this.visible = params.visible;
|
|
6286
6475
|
if (((_c = params.options) === null || _c === void 0 ? void 0 : _c.text) != null)
|
|
6287
6476
|
this.additionalTexts = [];
|
|
6477
|
+
this.photo = null;
|
|
6288
6478
|
this.prePositioned = !isNaN((_d = params.pos3d) === null || _d === void 0 ? void 0 : _d.x);
|
|
6289
6479
|
this.createLabel(params.pos3d);
|
|
6290
6480
|
if (!params.api && !ENVIRONMENT.IS_SELF_MANAGED && this.options.text == null) {
|
|
@@ -6347,6 +6537,7 @@ var EntityLabel;
|
|
|
6347
6537
|
this.options = options || {};
|
|
6348
6538
|
this.groupId = groupId;
|
|
6349
6539
|
this.additionalTexts = this.options.text != null ? [] : null;
|
|
6540
|
+
this.photo = null;
|
|
6350
6541
|
this.createLabel();
|
|
6351
6542
|
}
|
|
6352
6543
|
/**
|
|
@@ -6354,8 +6545,8 @@ var EntityLabel;
|
|
|
6354
6545
|
* @param starterPos3d optional position to create label at. Ignored for billboard labels.
|
|
6355
6546
|
*/
|
|
6356
6547
|
async createLabel(starterPos3d) {
|
|
6357
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
|
|
6358
|
-
|
|
6548
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
|
|
6549
|
+
// The old label stays until its replacement is built, so a rebuild never blinks while a location loads.
|
|
6359
6550
|
let pos3d = starterPos3d ? starterPos3d.clone() : null;
|
|
6360
6551
|
const counter = ++this.counter;
|
|
6361
6552
|
if (this.disposed) {
|
|
@@ -6513,6 +6704,7 @@ var EntityLabel;
|
|
|
6513
6704
|
}
|
|
6514
6705
|
}
|
|
6515
6706
|
if (isNaN(pos3d === null || pos3d === void 0 ? void 0 : pos3d.x)) {
|
|
6707
|
+
this.removeLabel();
|
|
6516
6708
|
return;
|
|
6517
6709
|
}
|
|
6518
6710
|
pos3d = DrawingUtils.EnsurePosHeight({
|
|
@@ -6522,6 +6714,7 @@ var EntityLabel;
|
|
|
6522
6714
|
viewer: this.viewer
|
|
6523
6715
|
});
|
|
6524
6716
|
if (isNaN(pos3d === null || pos3d === void 0 ? void 0 : pos3d.x)) {
|
|
6717
|
+
this.removeLabel();
|
|
6525
6718
|
return;
|
|
6526
6719
|
}
|
|
6527
6720
|
this._pos3d = pos3d;
|
|
@@ -6587,35 +6780,39 @@ var EntityLabel;
|
|
|
6587
6780
|
catch (e) {
|
|
6588
6781
|
console.error(e);
|
|
6589
6782
|
}
|
|
6590
|
-
|
|
6591
|
-
//
|
|
6592
|
-
if (
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6615
|
-
|
|
6616
|
-
console.error(e);
|
|
6783
|
+
const attributes = (_a = this.options.attributes) !== null && _a !== void 0 ? _a : (_c = (_b = entityType === null || entityType === void 0 ? void 0 : entityType.VisualSettings) === null || _b === void 0 ? void 0 : _b.label) === null || _c === void 0 ? void 0 : _c.attributes;
|
|
6784
|
+
// Read for its attributes, and for a photo when the label has no icon of its own.
|
|
6785
|
+
if (attributes || !HasIcon(this.options)) {
|
|
6786
|
+
let entity = null;
|
|
6787
|
+
try {
|
|
6788
|
+
entity = (await Entity$1.Get({
|
|
6789
|
+
api: this.api,
|
|
6790
|
+
entityId: this.rego.entityId,
|
|
6791
|
+
scenario: ((_d = this.rego.outline) === null || _d === void 0 ? void 0 : _d.length) ? (_e = this.rego.outline.find(x => !!x.Scenario)) === null || _e === void 0 ? void 0 : _e.Scenario : null,
|
|
6792
|
+
schemaId: this.rego.schema,
|
|
6793
|
+
migrated: true,
|
|
6794
|
+
expandAttachments: !HasIcon(this.options)
|
|
6795
|
+
})).entity;
|
|
6796
|
+
}
|
|
6797
|
+
// Entity doesn't exist.
|
|
6798
|
+
// Likely a graphic with an ID that points to no record.
|
|
6799
|
+
catch (e) {
|
|
6800
|
+
console.error(e);
|
|
6801
|
+
}
|
|
6802
|
+
for (let i = 0; entity && attributes && i < attributes.length; i++) {
|
|
6803
|
+
const value = Entity$1.GetValue({
|
|
6804
|
+
entity: entity,
|
|
6805
|
+
path: attributes[i]
|
|
6806
|
+
});
|
|
6807
|
+
if (value != null) {
|
|
6808
|
+
additionalTexts.push(value);
|
|
6617
6809
|
}
|
|
6618
6810
|
}
|
|
6811
|
+
// A linked image outranks the default one, as it does for group labels.
|
|
6812
|
+
if (entity && !HasIcon(this.options)) {
|
|
6813
|
+
const at = additionalTexts.findIndex(value => IsImageUrl(String(value)));
|
|
6814
|
+
this.photo = at >= 0 ? String(additionalTexts.splice(at, 1)[0]).trim() : GetDefaultImageUrl(entity, this.api);
|
|
6815
|
+
}
|
|
6619
6816
|
}
|
|
6620
6817
|
}
|
|
6621
6818
|
this.additionalTexts = additionalTexts;
|
|
@@ -6634,9 +6831,13 @@ var EntityLabel;
|
|
|
6634
6831
|
if (this.disposed || this.counter != counter) {
|
|
6635
6832
|
return;
|
|
6636
6833
|
}
|
|
6637
|
-
|
|
6834
|
+
this.removeLabel();
|
|
6835
|
+
// A found photo draws as if configured, without changing the options clustering compares and measures.
|
|
6836
|
+
const photo = HasIcon(this.options) ? null : (_j = this.options.photo) !== null && _j !== void 0 ? _j : this.photo;
|
|
6837
|
+
const look = photo ? WithPhoto({ ...this.options, text: (_k = this.options.text) !== null && _k !== void 0 ? _k : [text, ...additionalTexts].join("\n") }, photo) : this.options;
|
|
6838
|
+
const presentation = look.presentation || "text";
|
|
6638
6839
|
let clickEntityIds = [];
|
|
6639
|
-
const selectOnClick = (
|
|
6840
|
+
const selectOnClick = (_l = this.options.selectOnClick) !== null && _l !== void 0 ? _l : Boolean(this.groupId);
|
|
6640
6841
|
const select = (event) => {
|
|
6641
6842
|
event.preventDefault();
|
|
6642
6843
|
event.stopPropagation();
|
|
@@ -6665,11 +6866,11 @@ var EntityLabel;
|
|
|
6665
6866
|
target.addEventListener(type, event => event.stopPropagation());
|
|
6666
6867
|
}
|
|
6667
6868
|
};
|
|
6668
|
-
const normalLeader = (
|
|
6869
|
+
const normalLeader = (_m = look.leader) !== null && _m !== void 0 ? _m : presentation === "text";
|
|
6669
6870
|
const angled = this.options.leaderStyle === "angled";
|
|
6670
6871
|
let leader = normalLeader;
|
|
6671
6872
|
const content = document.createElement("span");
|
|
6672
|
-
const originalText = (
|
|
6873
|
+
const originalText = (_o = this.options.text) !== null && _o !== void 0 ? _o : [text, ...additionalTexts].join("\n");
|
|
6673
6874
|
let textElement;
|
|
6674
6875
|
let sourceText;
|
|
6675
6876
|
let sourceNaturalText;
|
|
@@ -6684,10 +6885,33 @@ var EntityLabel;
|
|
|
6684
6885
|
if (presentation === "icon-text-above") {
|
|
6685
6886
|
content.style.flexDirection = "column-reverse";
|
|
6686
6887
|
}
|
|
6888
|
+
// A found photo's shape decides its layout once it loads: a rectangle sits under the name it adds to, a circle beside it.
|
|
6889
|
+
let photoShape = null;
|
|
6890
|
+
let growPhoto = null;
|
|
6891
|
+
// Loads the photo; called once the label is actually showing, so labels out of view or folded into a cluster never fetch one.
|
|
6892
|
+
let startPhoto = null;
|
|
6893
|
+
let photoTimer = null;
|
|
6894
|
+
const placePhoto = (shape, grow) => {
|
|
6895
|
+
photoShape = shape;
|
|
6896
|
+
growPhoto = grow;
|
|
6897
|
+
content.style.flexDirection = shape === "rect" ? "column-reverse" : "";
|
|
6898
|
+
};
|
|
6687
6899
|
if (presentation !== "text") {
|
|
6688
|
-
const built = this.buildIcon(
|
|
6900
|
+
const built = this.buildIcon(look, counter, placePhoto);
|
|
6901
|
+
startPhoto = built.start;
|
|
6689
6902
|
const icon = iconElement = built.icon;
|
|
6690
6903
|
fitIconBackground = built.fit;
|
|
6904
|
+
if (built.arriving) {
|
|
6905
|
+
// The gap opens with the photo, so a text label does not shift before its photo grows in.
|
|
6906
|
+
content.style.gap = "0px";
|
|
6907
|
+
built.arriving.then(() => {
|
|
6908
|
+
if (iconElement.style.display === "none") {
|
|
6909
|
+
return;
|
|
6910
|
+
}
|
|
6911
|
+
content.style.transition = `gap ${PHOTO_TRANSITION}ms ease-out`;
|
|
6912
|
+
content.style.gap = "6px";
|
|
6913
|
+
});
|
|
6914
|
+
}
|
|
6691
6915
|
makeClickable(icon);
|
|
6692
6916
|
// Keep badges outside the glyph so font classes and image masks cannot affect them.
|
|
6693
6917
|
iconContainer = document.createElement("span");
|
|
@@ -6709,7 +6933,7 @@ var EntityLabel;
|
|
|
6709
6933
|
if (presentation === "text")
|
|
6710
6934
|
makeClickable(textElement);
|
|
6711
6935
|
content.appendChild(textElement);
|
|
6712
|
-
if ((
|
|
6936
|
+
if ((_p = this.options.cluster) === null || _p === void 0 ? void 0 : _p.enabled) {
|
|
6713
6937
|
sourceText = document.createElement("span");
|
|
6714
6938
|
sourceText.className = "entity-label-source-text";
|
|
6715
6939
|
sourceText.setAttribute("aria-hidden", "true");
|
|
@@ -6729,7 +6953,9 @@ var EntityLabel;
|
|
|
6729
6953
|
countElement.style.cssText = "flex:none;align-self:center;white-space:nowrap;background:#fff;color:#000;text-shadow:none;border-radius:8px;padding:0 3px;display:none;";
|
|
6730
6954
|
// Text above the icon would have the badge land in it, so the free corner is the lower one.
|
|
6731
6955
|
const badgeBelow = presentation === "icon-text-above";
|
|
6732
|
-
|
|
6956
|
+
// A photo is put away while its label stands for a cluster, so its count sits with the text instead.
|
|
6957
|
+
const badgeOnIcon = Boolean(iconContainer) && !look.iconPhoto;
|
|
6958
|
+
if (badgeOnIcon) {
|
|
6733
6959
|
countElement.style.cssText += `position:absolute;left:100%;top:${badgeBelow ? "100%" : "0"};transform:translate(-50%,-50%);`;
|
|
6734
6960
|
iconContainer.appendChild(countElement);
|
|
6735
6961
|
}
|
|
@@ -6746,11 +6972,11 @@ var EntityLabel;
|
|
|
6746
6972
|
else {
|
|
6747
6973
|
ele = document.createElement("div");
|
|
6748
6974
|
ele.appendChild(content);
|
|
6749
|
-
if (!
|
|
6975
|
+
if (!badgeOnIcon) {
|
|
6750
6976
|
ele.appendChild(countElement);
|
|
6751
6977
|
}
|
|
6752
6978
|
ele.className = "entity-label";
|
|
6753
|
-
ele.dataset.entityId = ((
|
|
6979
|
+
ele.dataset.entityId = ((_q = this.rego) === null || _q === void 0 ? void 0 : _q.entityId) || "";
|
|
6754
6980
|
ele.dataset.presentation = presentation;
|
|
6755
6981
|
if (this.groupId) {
|
|
6756
6982
|
ele.dataset.groupId = this.groupId;
|
|
@@ -6903,7 +7129,7 @@ var EntityLabel;
|
|
|
6903
7129
|
};
|
|
6904
7130
|
let cursorMotion = this.cursorMotion;
|
|
6905
7131
|
const project = () => {
|
|
6906
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t;
|
|
7132
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
6907
7133
|
if (this.disposed || !this.viewer || this.viewer.isDestroyed() || ((_a = this.visible) === null || _a === void 0 ? void 0 : _a.call(this)) === false ||
|
|
6908
7134
|
(presentation === "icon" && this.options.iconScale === 0)) {
|
|
6909
7135
|
return null;
|
|
@@ -6941,13 +7167,18 @@ var EntityLabel;
|
|
|
6941
7167
|
if (!Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.x) || !Number.isFinite(anchor === null || anchor === void 0 ? void 0 : anchor.y)) {
|
|
6942
7168
|
return null;
|
|
6943
7169
|
}
|
|
6944
|
-
|
|
7170
|
+
const onScreen = anchor.x >= 0 && anchor.y >= 0 && anchor.x < this.viewer.canvas.clientWidth && anchor.y < this.viewer.canvas.clientHeight;
|
|
7171
|
+
if (leader && !onScreen)
|
|
6945
7172
|
return null;
|
|
7173
|
+
if (onScreen && !this.imageAsked && this.groupId && !HasIcon(this.options) && !this.options.photo) {
|
|
7174
|
+
this.imageAsked = true;
|
|
7175
|
+
(_e = this.visualRegister) === null || _e === void 0 ? void 0 : _e.RequestLabelImage((_f = this.rego) === null || _f === void 0 ? void 0 : _f.entityId);
|
|
7176
|
+
}
|
|
6946
7177
|
const transientPivot = (this.Transient && !this.Persistent ?
|
|
6947
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
7178
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_g = this.visualRegister) === null || _g === void 0 ? void 0 : _g.GetLabelHoverPosition((_h = this.rego) === null || _h === void 0 ? void 0 : _h.entityId, (_j = this.rego) === null || _j === void 0 ? void 0 : _j.menuItemId)) : undefined);
|
|
6948
7179
|
const hover = (presentation !== "icon" ?
|
|
6949
7180
|
(this.Transient && !this.Persistent && this.hoverFade.to === 0 ? this.retainedHoverPosition :
|
|
6950
|
-
(
|
|
7181
|
+
(_k = this.visualRegister) === null || _k === void 0 ? void 0 : _k.GetLabelHoverPosition((_l = this.rego) === null || _l === void 0 ? void 0 : _l.entityId, (_m = this.rego) === null || _m === void 0 ? void 0 : _m.menuItemId)) : undefined);
|
|
6951
7182
|
const reused = !this.Transient || this.Persistent;
|
|
6952
7183
|
const now = Date.now();
|
|
6953
7184
|
const animateCursor = () => {
|
|
@@ -6974,8 +7205,8 @@ var EntityLabel;
|
|
|
6974
7205
|
const icon = measureIcon();
|
|
6975
7206
|
const iconRing = icon ? Math.max(ICON_RING_MIN, icon.radius + ICON_RING_PADDING) : 0;
|
|
6976
7207
|
const pivot = icon ? undefined : transientPivot;
|
|
6977
|
-
let x = ((
|
|
6978
|
-
let y = ((
|
|
7208
|
+
let x = ((_o = pivot === null || pivot === void 0 ? void 0 : pivot.x) !== null && _o !== void 0 ? _o : anchor.x) + (icon || pixelOffset).x;
|
|
7209
|
+
let y = ((_p = pivot === null || pivot === void 0 ? void 0 : pivot.y) !== null && _p !== void 0 ? _p : anchor.y) + (icon || pixelOffset).y;
|
|
6979
7210
|
if (cursorAmount > 0 && !icon) {
|
|
6980
7211
|
// A leader-less label starts centred on its anchor; preserve that box as it lifts away.
|
|
6981
7212
|
if (!normalLeader) {
|
|
@@ -7027,11 +7258,12 @@ var EntityLabel;
|
|
|
7027
7258
|
const width = ele.offsetWidth / coordinates.scaleX;
|
|
7028
7259
|
const height = ele.offsetHeight / coordinates.scaleY;
|
|
7029
7260
|
let clusterBounds;
|
|
7030
|
-
if ((
|
|
7261
|
+
if ((_q = this.options.cluster) === null || _q === void 0 ? void 0 : _q.enabled) {
|
|
7031
7262
|
const textWidth = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetWidth) || 0;
|
|
7032
7263
|
const textHeight = (sourceText === null || sourceText === void 0 ? void 0 : sourceText.offsetHeight) || 0;
|
|
7033
|
-
|
|
7034
|
-
const
|
|
7264
|
+
// A photo is never part of the footprint, so its arrival cannot change which cluster a label joins.
|
|
7265
|
+
const iconWidth = look.iconPhoto ? 0 : (iconElement === null || iconElement === void 0 ? void 0 : iconElement.offsetWidth) || 0;
|
|
7266
|
+
const iconHeight = look.iconPhoto ? 0 : (iconElement === null || iconElement === void 0 ? void 0 : iconElement.offsetHeight) || 0;
|
|
7035
7267
|
const gap = sourceText && iconWidth ? 6 : 0;
|
|
7036
7268
|
const above = presentation === "icon-text-above";
|
|
7037
7269
|
// Match the fixed content gap and label padding, excluding the count badge.
|
|
@@ -7060,7 +7292,7 @@ var EntityLabel;
|
|
|
7060
7292
|
if (!leader && (bounds.x + bounds.width < 0 || bounds.y + bounds.height < 0 || bounds.x > this.viewer.canvas.clientWidth || bounds.y > this.viewer.canvas.clientHeight)) {
|
|
7061
7293
|
return null;
|
|
7062
7294
|
}
|
|
7063
|
-
const state = (
|
|
7295
|
+
const state = (_r = this.visualRegister) === null || _r === void 0 ? void 0 : _r.GetState({ entityId: (_s = this.rego) === null || _s === void 0 ? void 0 : _s.entityId, menuItemId: (_t = this.rego) === null || _t === void 0 ? void 0 : _t.menuItemId });
|
|
7064
7296
|
const opacity = this.prePositioned || hoverOnly || MAX_DISTANCE === Infinity ? 1 : 1 - distance / MAX_DISTANCE;
|
|
7065
7297
|
return {
|
|
7066
7298
|
canFlip: leader && presentation !== "icon", hasLeader: leader,
|
|
@@ -7068,8 +7300,8 @@ var EntityLabel;
|
|
|
7068
7300
|
transient: this.Transient || Boolean(hover) || cursorAmount > 0,
|
|
7069
7301
|
extendedGeometry: visual instanceof Entity && Boolean(visual.polygon || visual.polyline || visual.corridor),
|
|
7070
7302
|
viewportWidth: this.viewer.canvas.clientWidth,
|
|
7071
|
-
id: this.id, entityKey: ((
|
|
7072
|
-
entityId: (
|
|
7303
|
+
id: this.id, entityKey: ((_u = this.rego) === null || _u === void 0 ? void 0 : _u.entityId) ? JSON.stringify([this.rego.entityId, this.rego.menuItemId || "", this.Transient && !this.Persistent ? "hover" : ""]) : undefined,
|
|
7304
|
+
entityId: (_v = this.rego) === null || _v === void 0 ? void 0 : _v.entityId, group: this.groupId, x, y, width, height, anchorX, anchorY, depth, leaderLength, leaderRun, leaderSide, distance,
|
|
7073
7305
|
iconRadius: icon === null || icon === void 0 ? void 0 : icon.radius,
|
|
7074
7306
|
// Priority shows the hovered member individually without dragging its cluster along.
|
|
7075
7307
|
opacity, priority: hover || cursorAmount > 0 || (state === null || state === void 0 ? void 0 : state.highlighted) ? 1 : 0, options: this.options, text: originalText, clusterBounds
|
|
@@ -7078,7 +7310,7 @@ var EntityLabel;
|
|
|
7078
7310
|
let displacement = { x: 0, y: 0 };
|
|
7079
7311
|
let nudge = { x: 0, y: 0, fromX: 0, fromY: 0, targetX: 0, targetY: 0, start: 0 };
|
|
7080
7312
|
const apply = (placement) => {
|
|
7081
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
|
|
7313
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4;
|
|
7082
7314
|
const show = (placement === null || placement === void 0 ? void 0 : placement.visible) && placement.opacity > 0;
|
|
7083
7315
|
clickEntityIds = show ? placement.entityIds : [];
|
|
7084
7316
|
ele.style.visibility = show ? "visible" : "hidden";
|
|
@@ -7092,9 +7324,30 @@ var EntityLabel;
|
|
|
7092
7324
|
if (!this.Transient) {
|
|
7093
7325
|
this.ringAnimation = undefined;
|
|
7094
7326
|
}
|
|
7327
|
+
clearTimeout(photoTimer);
|
|
7328
|
+
photoTimer = null;
|
|
7095
7329
|
return;
|
|
7096
7330
|
}
|
|
7097
|
-
|
|
7331
|
+
// A label standing for a cluster shows its summary only; its photo is put away and never fetched.
|
|
7332
|
+
const solo = !((_a = placement.members) === null || _a === void 0 ? void 0 : _a.length);
|
|
7333
|
+
if (look.iconPhoto && iconElement && iconElement.dataset.failed !== "true") {
|
|
7334
|
+
const display = solo ? "inline-flex" : "none";
|
|
7335
|
+
if (iconElement.style.display !== display) {
|
|
7336
|
+
iconElement.style.display = display;
|
|
7337
|
+
}
|
|
7338
|
+
}
|
|
7339
|
+
if (!solo) {
|
|
7340
|
+
clearTimeout(photoTimer);
|
|
7341
|
+
photoTimer = null;
|
|
7342
|
+
}
|
|
7343
|
+
else if (startPhoto && photoTimer == null) {
|
|
7344
|
+
photoTimer = setTimeout(() => {
|
|
7345
|
+
if (!this.disposed && this.counter === counter) {
|
|
7346
|
+
startPhoto();
|
|
7347
|
+
}
|
|
7348
|
+
}, PHOTO_SHOW_DELAY);
|
|
7349
|
+
}
|
|
7350
|
+
const displayText = (_b = placement.text) !== null && _b !== void 0 ? _b : originalText;
|
|
7098
7351
|
if (textElement && textElement.textContent !== displayText) {
|
|
7099
7352
|
textElement.textContent = displayText;
|
|
7100
7353
|
// Measure the summary's footprint next frame without replacing each member's source text.
|
|
@@ -7118,10 +7371,12 @@ var EntityLabel;
|
|
|
7118
7371
|
const stack = JSON.stringify(placement.icons || []);
|
|
7119
7372
|
if (iconStack && stack !== displayedStack) {
|
|
7120
7373
|
displayedStack = stack;
|
|
7121
|
-
const looks = ((
|
|
7374
|
+
const looks = ((_c = placement.icons) === null || _c === void 0 ? void 0 : _c.length) ? [iconLook(this.options), ...placement.icons] : [];
|
|
7122
7375
|
// One small size for all, so no icon in the arrangement reads as more important than another.
|
|
7123
|
-
const size = (this.options.iconSize || 24) * ((
|
|
7376
|
+
const size = (this.options.iconSize || 24) * ((_d = this.options.iconScale) !== null && _d !== void 0 ? _d : 1) * ICON_STACK_SCALE;
|
|
7124
7377
|
const built = looks.map(look => this.buildIcon({ ...look, iconSize: size }, counter));
|
|
7378
|
+
// A stack is on show already, so its photos load straight away.
|
|
7379
|
+
built.forEach(({ start }) => start === null || start === void 0 ? void 0 : start());
|
|
7125
7380
|
// Two sit side by side; three make a triangle with the representative on top.
|
|
7126
7381
|
iconStack.style.gridTemplateColumns = built.length === 3 ? "auto auto" : `repeat(${built.length}, auto)`;
|
|
7127
7382
|
if (built.length === 3) {
|
|
@@ -7138,7 +7393,7 @@ var EntityLabel;
|
|
|
7138
7393
|
}
|
|
7139
7394
|
for (const fit of stackedFits)
|
|
7140
7395
|
fit();
|
|
7141
|
-
if (
|
|
7396
|
+
if (badgeOnIcon) {
|
|
7142
7397
|
const badgeWidth = countElement.offsetWidth;
|
|
7143
7398
|
const iconWidth = iconElement.offsetWidth;
|
|
7144
7399
|
const narrow = iconWidth < iconElement.offsetHeight * 0.6;
|
|
@@ -7153,16 +7408,28 @@ var EntityLabel;
|
|
|
7153
7408
|
// Summary and badge changes must fit on their first visible frame, including after resize.
|
|
7154
7409
|
const ring = this.options.anchorRing;
|
|
7155
7410
|
const pointer = (layer.pointer || (cursorMotion.target ? cursorMotion.position : undefined) || (this.Transient && !this.Persistent ?
|
|
7156
|
-
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (
|
|
7411
|
+
(this.hoverFade.to === 0 ? this.retainedHoverPosition : (_e = this.visualRegister) === null || _e === void 0 ? void 0 : _e.GetLabelHoverPosition((_f = this.rego) === null || _f === void 0 ? void 0 : _f.entityId, (_g = this.rego) === null || _g === void 0 ? void 0 : _g.menuItemId)) : undefined));
|
|
7157
7412
|
const iconRing = frame.iconRadius ? Math.max(ICON_RING_MIN, frame.iconRadius + ICON_RING_PADDING) : 0;
|
|
7158
|
-
const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= Math.max((
|
|
7159
|
-
|
|
7413
|
+
const hovered = pointer && Math.hypot(pointer.x - frame.anchorX, pointer.y - frame.anchorY) <= Math.max((_h = ring === null || ring === void 0 ? void 0 : ring.hoverDistance) !== null && _h !== void 0 ? _h : 24, iconRing);
|
|
7414
|
+
if (look.iconPhoto && growPhoto) {
|
|
7415
|
+
// A rectangle leans on the leader's side of the label.
|
|
7416
|
+
if (photoShape === "rect") {
|
|
7417
|
+
const align = !leader ? "center" : placement.textSide === "left" ? "flex-end" : "flex-start";
|
|
7418
|
+
if (content.style.alignItems !== align) {
|
|
7419
|
+
content.style.alignItems = align;
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
// Grown in layout rather than scaled, so the border keeps its width, and the label, whose bottom
|
|
7423
|
+
// sits on the leader, grows up and sideways without changing the leader's length.
|
|
7424
|
+
growPhoto(Boolean(hovered) && !((_j = placement.members) === null || _j === void 0 ? void 0 : _j.length));
|
|
7425
|
+
}
|
|
7426
|
+
const normalRadius = (!leader || (ring === null || ring === void 0 ? void 0 : ring.enabled) === false ? 0 : Math.max(frame.distance <= ((_k = ring === null || ring === void 0 ? void 0 : ring.nearDistance) !== null && _k !== void 0 ? _k : 100) ? ((_l = ring === null || ring === void 0 ? void 0 : ring.nearRadius) !== null && _l !== void 0 ? _l : 10) : 0, hovered ? ((_m = ring === null || ring === void 0 ? void 0 : ring.hoverRadius) !== null && _m !== void 0 ? _m : (this.Transient && !this.Persistent ? 15.4 : 22)) : 0));
|
|
7160
7427
|
const targetRadius = normalRadius + (15.4 - normalRadius) * cursorMotion.amount;
|
|
7161
7428
|
// Around a glyph the ring keeps the glyph's size and only fades its stroke in, so it never covers the icon.
|
|
7162
7429
|
const targetOuter = iconRing || targetRadius || 2.5;
|
|
7163
7430
|
const targetInner = iconRing ? (targetRadius > 0 ? iconRing - 2 : iconRing) : Math.max(0, targetRadius - 2);
|
|
7164
7431
|
const now = Date.now();
|
|
7165
|
-
const duration = (
|
|
7432
|
+
const duration = (_o = ring === null || ring === void 0 ? void 0 : ring.transitionDuration) !== null && _o !== void 0 ? _o : 200;
|
|
7166
7433
|
const sharedRing = this.Transient && !this.Persistent && this.hoverFade.to === 1 ? this.hoverRingState : undefined;
|
|
7167
7434
|
let ringAnimation = sharedRing ? sharedRing.animation : this.ringAnimation;
|
|
7168
7435
|
if (!ringAnimation) {
|
|
@@ -7204,19 +7471,19 @@ var EntityLabel;
|
|
|
7204
7471
|
if (leader && angled) {
|
|
7205
7472
|
// The declutter owns the side; the fit only says which one had the room to begin with.
|
|
7206
7473
|
const run = Math.max(ANGLED_LEADER_RUN, radius + 4);
|
|
7207
|
-
const side = (
|
|
7474
|
+
const side = (_q = (_p = placement.textSide) !== null && _p !== void 0 ? _p : frame.leaderSide) !== null && _q !== void 0 ? _q : "right";
|
|
7208
7475
|
const beside = side === "left" ? frame.anchorX - run - width : frame.anchorX + run;
|
|
7209
7476
|
position.x = Math.max(0, Math.min(beside, this.viewer.canvas.clientWidth - width));
|
|
7210
7477
|
}
|
|
7211
7478
|
else if (leader && placement.textSide === "left") {
|
|
7212
7479
|
position.x = Math.max(0, Math.min(frame.anchorX - width, this.viewer.canvas.clientWidth - width));
|
|
7213
7480
|
}
|
|
7214
|
-
const gap = (
|
|
7481
|
+
const gap = (_s = (_r = this.options.pointerAvoidance) === null || _r === void 0 ? void 0 : _r.gap) !== null && _s !== void 0 ? _s : 12;
|
|
7215
7482
|
const containsPointer = (x, y, margin) => {
|
|
7216
7483
|
return pointer && pointer.x >= x - margin && pointer.x <= x + width + margin && pointer.y >= y - margin && pointer.y <= y + height + margin;
|
|
7217
7484
|
};
|
|
7218
7485
|
const passive = !selectOnClick || !this.visualRegister;
|
|
7219
|
-
if (passive && ((
|
|
7486
|
+
if (passive && ((_t = this.options.pointerAvoidance) === null || _t === void 0 ? void 0 : _t.enabled) !== false && pointer &&
|
|
7220
7487
|
(containsPointer(position.x, position.y, gap) || containsPointer(position.x + displacement.x, position.y + displacement.y, gap))) {
|
|
7221
7488
|
if (containsPointer(position.x + displacement.x, position.y + displacement.y, 0)) {
|
|
7222
7489
|
// Choose against the original box and retain the offset until the pointer leaves both boxes.
|
|
@@ -7234,7 +7501,7 @@ var EntityLabel;
|
|
|
7234
7501
|
else {
|
|
7235
7502
|
displacement = { x: 0, y: 0 };
|
|
7236
7503
|
}
|
|
7237
|
-
const nudgeDuration = (
|
|
7504
|
+
const nudgeDuration = (_v = (_u = this.options.pointerAvoidance) === null || _u === void 0 ? void 0 : _u.transitionDuration) !== null && _v !== void 0 ? _v : 200;
|
|
7238
7505
|
const animateNudge = () => {
|
|
7239
7506
|
const progress = now < nudge.start ? 0 : nudgeDuration > 0 ? Math.min(1, (now - nudge.start) / nudgeDuration) : 1;
|
|
7240
7507
|
const eased = progress * progress * (3 - 2 * progress);
|
|
@@ -7250,7 +7517,7 @@ var EntityLabel;
|
|
|
7250
7517
|
fromY: nudge.y,
|
|
7251
7518
|
targetX: displacement.x,
|
|
7252
7519
|
targetY: displacement.y,
|
|
7253
|
-
start: now + ((
|
|
7520
|
+
start: now + ((_x = (_w = this.options.pointerAvoidance) === null || _w === void 0 ? void 0 : _w.delay) !== null && _x !== void 0 ? _x : 180)
|
|
7254
7521
|
};
|
|
7255
7522
|
}
|
|
7256
7523
|
if (animateNudge() < 1 && (nudge.fromX !== nudge.targetX || nudge.fromY !== nudge.targetY)) {
|
|
@@ -7261,7 +7528,7 @@ var EntityLabel;
|
|
|
7261
7528
|
ele.style.left = `${coordinates.x + position.x * coordinates.scaleX}px`;
|
|
7262
7529
|
ele.style.top = `${coordinates.y + position.y * coordinates.scaleY}px`;
|
|
7263
7530
|
for (const clickTarget of Array.from(content.querySelectorAll('[role="button"]'))) {
|
|
7264
|
-
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(
|
|
7531
|
+
clickTarget.setAttribute("aria-label", placement.members.length ? `Select ${placement.entityIds.length} entities` : `Select ${(_z = (_y = this.options.text) !== null && _y !== void 0 ? _y : text) !== null && _z !== void 0 ? _z : (_0 = this.rego) === null || _0 === void 0 ? void 0 : _0.entityId}`);
|
|
7265
7532
|
}
|
|
7266
7533
|
if (leader) {
|
|
7267
7534
|
const nodeRadius = ringAnimation.outer;
|
|
@@ -7301,13 +7568,13 @@ var EntityLabel;
|
|
|
7301
7568
|
// Keep the stem inside the 126-degree arc, away from antialiased endpoints.
|
|
7302
7569
|
const arcFraction = 0.35;
|
|
7303
7570
|
const rotationLimit = Math.PI * arcFraction - Math.PI / 30;
|
|
7304
|
-
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((
|
|
7571
|
+
const targetOffset = hovered ? Math.max(-rotationLimit, Math.min(rotationLimit, wrappedAngle)) : ((_1 = ringAnimation.arcOffset) !== null && _1 !== void 0 ? _1 : 0);
|
|
7305
7572
|
const targetOpen = cursorMotion.target || (hovered && (ring === null || ring === void 0 ? void 0 : ring.enabled) !== false && targetRadius > 0) ? 1 : 0;
|
|
7306
|
-
const elapsed = Math.min(32, Math.max(0, now - ((
|
|
7573
|
+
const elapsed = Math.min(32, Math.max(0, now - ((_2 = ringAnimation.arcTime) !== null && _2 !== void 0 ? _2 : now)));
|
|
7307
7574
|
const blend = duration > 0 ? 1 - Math.exp(-elapsed / (duration / 3)) : 1;
|
|
7308
7575
|
const approach = (value, target) => Math.abs(value - target) < 0.001 ? target : value + (target - value) * blend;
|
|
7309
|
-
ringAnimation.arcOffset = approach((
|
|
7310
|
-
ringAnimation.arcOpen = approach((
|
|
7576
|
+
ringAnimation.arcOffset = approach((_3 = ringAnimation.arcOffset) !== null && _3 !== void 0 ? _3 : 0, targetOffset);
|
|
7577
|
+
ringAnimation.arcOpen = approach((_4 = ringAnimation.arcOpen) !== null && _4 !== void 0 ? _4 : 0, targetOpen);
|
|
7311
7578
|
ringAnimation.arcTime = now;
|
|
7312
7579
|
if (ringAnimation.arcOffset !== targetOffset || ringAnimation.arcOpen !== targetOpen) {
|
|
7313
7580
|
this.viewer.scene.requestRender();
|
|
@@ -7362,14 +7629,16 @@ var EntityLabel;
|
|
|
7362
7629
|
}
|
|
7363
7630
|
/*
|
|
7364
7631
|
* Builds one icon glyph from label options, with its background chip when asked for.
|
|
7365
|
-
*
|
|
7366
7632
|
* The returned fit squares the chip off once the glyph has a measurable size.
|
|
7367
7633
|
*/
|
|
7368
|
-
buildIcon(options, counter) {
|
|
7634
|
+
buildIcon(options, counter, placePhoto) {
|
|
7369
7635
|
var _a, _b, _c;
|
|
7370
7636
|
let fit;
|
|
7371
7637
|
const icon = document.createElement("span");
|
|
7372
7638
|
icon.className = "entity-label-icon";
|
|
7639
|
+
if (options.iconPhoto && options.iconUrl) {
|
|
7640
|
+
return this.buildPhoto(icon, options, counter, placePhoto);
|
|
7641
|
+
}
|
|
7373
7642
|
icon.style.cssText = `font-size:${(options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1)}px;line-height:1;`;
|
|
7374
7643
|
icon.style.textShadow = "0 1px 2px rgba(0, 0, 0, 0.25)";
|
|
7375
7644
|
const iconColor = typeof options.iconColor === "string" ? options.iconColor : undefined;
|
|
@@ -7422,6 +7691,8 @@ var EntityLabel;
|
|
|
7422
7691
|
}
|
|
7423
7692
|
this.viewer.scene.requestRender();
|
|
7424
7693
|
};
|
|
7694
|
+
// A photo is rarely square; fit it inside the icon's box rather than stretching it.
|
|
7695
|
+
image.style.objectFit = "contain";
|
|
7425
7696
|
image.onload = resize;
|
|
7426
7697
|
resize();
|
|
7427
7698
|
image.src = options.iconUrl;
|
|
@@ -7477,6 +7748,101 @@ var EntityLabel;
|
|
|
7477
7748
|
}
|
|
7478
7749
|
return { icon, fit };
|
|
7479
7750
|
}
|
|
7751
|
+
/*
|
|
7752
|
+
* Draws an entity's own photo at the size of an icon on its chip: a circle, or a rounded rectangle when too oblong for one.
|
|
7753
|
+
*/
|
|
7754
|
+
buildPhoto(icon, options, counter, placePhoto) {
|
|
7755
|
+
var _a;
|
|
7756
|
+
const size = (options.iconSize || 24) * ((_a = options.iconScale) !== null && _a !== void 0 ? _a : 1);
|
|
7757
|
+
// The glyph plus the chip's padding of a third either side, enlarged for a photo.
|
|
7758
|
+
const diameter = (size + 2 * (size / 3)) * PHOTO_SIZE_SCALE;
|
|
7759
|
+
// Enough pixels to stay sharp at the hover size.
|
|
7760
|
+
const pixels = Math.ceil(diameter * PHOTO_HOVER_SCALE * (window.devicePixelRatio || 1));
|
|
7761
|
+
const canvas = document.createElement("canvas");
|
|
7762
|
+
canvas.className = "entity-label-photo";
|
|
7763
|
+
canvas.style.cssText = "display:block;box-sizing:border-box;border:2px solid rgba(255,255,255,0.9);box-shadow:0 1px 3px rgba(0,0,0,0.35);";
|
|
7764
|
+
canvas.style.transition = ["width", "height", "opacity"].map(property => `${property} ${PHOTO_TRANSITION}ms ease-out`).join(",");
|
|
7765
|
+
icon.classList.add("entity-label-icon-photo");
|
|
7766
|
+
icon.style.display = options.iconScale === 0 ? "none" : "inline-flex";
|
|
7767
|
+
icon.appendChild(canvas);
|
|
7768
|
+
// Resting size, set once the photo's shape is known.
|
|
7769
|
+
let width = 0;
|
|
7770
|
+
let height = 0;
|
|
7771
|
+
let grown = false;
|
|
7772
|
+
const grow = (on) => {
|
|
7773
|
+
if (on === grown || !width) {
|
|
7774
|
+
return;
|
|
7775
|
+
}
|
|
7776
|
+
grown = on;
|
|
7777
|
+
const scale = on ? PHOTO_HOVER_SCALE : 1;
|
|
7778
|
+
canvas.style.width = `${width * scale}px`;
|
|
7779
|
+
canvas.style.height = `${height * scale}px`;
|
|
7780
|
+
this.renderFor(PHOTO_TRANSITION);
|
|
7781
|
+
};
|
|
7782
|
+
// Draws the photo in, or drops the icon when it would not load so the label stays as its text.
|
|
7783
|
+
const show = (photo) => {
|
|
7784
|
+
var _a;
|
|
7785
|
+
if (!photo.source) {
|
|
7786
|
+
icon.dataset.failed = "true";
|
|
7787
|
+
icon.style.display = "none";
|
|
7788
|
+
return false;
|
|
7789
|
+
}
|
|
7790
|
+
canvas.width = photo.source.width;
|
|
7791
|
+
canvas.height = photo.source.height;
|
|
7792
|
+
(_a = canvas.getContext("2d")) === null || _a === void 0 ? void 0 : _a.drawImage(photo.source, 0, 0);
|
|
7793
|
+
const longest = Math.max(photo.width, photo.height);
|
|
7794
|
+
const circle = PhotoShape(photo.width, photo.height) === "circle";
|
|
7795
|
+
width = circle ? diameter : diameter * photo.width / longest;
|
|
7796
|
+
height = circle ? diameter : diameter * photo.height / longest;
|
|
7797
|
+
canvas.style.width = `${width}px`;
|
|
7798
|
+
canvas.style.height = `${height}px`;
|
|
7799
|
+
canvas.style.borderRadius = circle ? "50%" : `${diameter * PHOTO_CORNER}px`;
|
|
7800
|
+
canvas.style.opacity = "1";
|
|
7801
|
+
placePhoto === null || placePhoto === void 0 ? void 0 : placePhoto(circle ? "circle" : "rect", grow);
|
|
7802
|
+
return true;
|
|
7803
|
+
};
|
|
7804
|
+
const photos = Photos.Get(this.viewer);
|
|
7805
|
+
const ready = photos.Peek(options.iconUrl, pixels);
|
|
7806
|
+
if (ready) {
|
|
7807
|
+
show(ready);
|
|
7808
|
+
return { icon };
|
|
7809
|
+
}
|
|
7810
|
+
canvas.style.width = "0px";
|
|
7811
|
+
canvas.style.height = "0px";
|
|
7812
|
+
canvas.style.opacity = "0";
|
|
7813
|
+
let arrived;
|
|
7814
|
+
const arriving = new Promise(resolve => arrived = resolve);
|
|
7815
|
+
let started = false;
|
|
7816
|
+
const start = () => {
|
|
7817
|
+
if (started) {
|
|
7818
|
+
return;
|
|
7819
|
+
}
|
|
7820
|
+
started = true;
|
|
7821
|
+
photos.Load(options.iconUrl, pixels).then(photo => {
|
|
7822
|
+
if (!this.disposed && this.counter === counter && show(photo)) {
|
|
7823
|
+
this.renderFor(PHOTO_TRANSITION);
|
|
7824
|
+
}
|
|
7825
|
+
arrived();
|
|
7826
|
+
});
|
|
7827
|
+
};
|
|
7828
|
+
return { icon, arriving, start };
|
|
7829
|
+
}
|
|
7830
|
+
/*
|
|
7831
|
+
* Keeps rendering while a CSS transition changes a label's size, since placement measures it every frame.
|
|
7832
|
+
*/
|
|
7833
|
+
renderFor(duration) {
|
|
7834
|
+
const end = Date.now() + duration;
|
|
7835
|
+
const step = () => {
|
|
7836
|
+
if (this.disposed || !this.viewer || this.viewer.isDestroyed()) {
|
|
7837
|
+
return;
|
|
7838
|
+
}
|
|
7839
|
+
this.viewer.scene.requestRender();
|
|
7840
|
+
if (Date.now() < end) {
|
|
7841
|
+
requestAnimationFrame(step);
|
|
7842
|
+
}
|
|
7843
|
+
};
|
|
7844
|
+
step();
|
|
7845
|
+
}
|
|
7480
7846
|
/**
|
|
7481
7847
|
* Removes label from the viewer and unsubscribes from events.
|
|
7482
7848
|
*/
|
|
@@ -7635,34 +8001,40 @@ var EntityLabelGroup;
|
|
|
7635
8001
|
].some(Array.isArray);
|
|
7636
8002
|
}
|
|
7637
8003
|
EntityLabelGroup.NeedsEntityData = NeedsEntityData;
|
|
7638
|
-
// How many entities
|
|
7639
|
-
const HYDRATE_AT_ONCE =
|
|
8004
|
+
// How many entities one read asks for, so a long hand-picked list does not arrive as one request.
|
|
8005
|
+
const HYDRATE_AT_ONCE = 100;
|
|
7640
8006
|
/*
|
|
7641
|
-
* Reads the entities behind items that name one without carrying it
|
|
8007
|
+
* Reads the entities behind items that name one without carrying it, attachments included,
|
|
8008
|
+
* so a label's default image needs no second request.
|
|
7642
8009
|
*/
|
|
7643
8010
|
async function Hydrate(items, api, signal) {
|
|
8011
|
+
var _a, _b;
|
|
7644
8012
|
if (!api) {
|
|
7645
8013
|
return items;
|
|
7646
8014
|
}
|
|
7647
8015
|
const hydrated = items.slice();
|
|
7648
|
-
|
|
8016
|
+
const missing = hydrated.map((item, at) => (item === null || item === void 0 ? void 0 : item.entityId) && !item.entity ? at : -1).filter(at => at >= 0);
|
|
8017
|
+
for (let start = 0; start < missing.length; start += HYDRATE_AT_ONCE) {
|
|
7649
8018
|
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
7650
8019
|
return hydrated;
|
|
7651
8020
|
}
|
|
7652
|
-
|
|
7653
|
-
|
|
7654
|
-
|
|
7655
|
-
|
|
7656
|
-
|
|
7657
|
-
|
|
8021
|
+
const chunk = missing.slice(start, start + HYDRATE_AT_ONCE);
|
|
8022
|
+
try {
|
|
8023
|
+
const { entities } = await Entity$1.GetListByIds({
|
|
8024
|
+
api, entityIds: chunk.map(at => hydrated[at].entityId), expandAttachments: true, migrated: true
|
|
8025
|
+
});
|
|
8026
|
+
const found = new Map((entities !== null && entities !== void 0 ? entities : []).map(entity => { var _a; return [(_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, entity]; }));
|
|
8027
|
+
for (const at of chunk) {
|
|
8028
|
+
const entity = found.get(hydrated[at].entityId);
|
|
7658
8029
|
if (entity) {
|
|
7659
|
-
|
|
8030
|
+
// The API leaves Attachment out when there are none; an empty list records that they were read.
|
|
8031
|
+
hydrated[at] = { ...hydrated[at], entity: { ...entity, Bruce: { ...entity.Bruce, Attachment: (_b = (_a = entity.Bruce) === null || _a === void 0 ? void 0 : _a.Attachment) !== null && _b !== void 0 ? _b : [] } } };
|
|
7660
8032
|
}
|
|
7661
8033
|
}
|
|
7662
|
-
|
|
7663
|
-
|
|
7664
|
-
|
|
7665
|
-
}
|
|
8034
|
+
}
|
|
8035
|
+
catch {
|
|
8036
|
+
// Entities that cannot be read keep their labels, just without attribute data.
|
|
8037
|
+
}
|
|
7666
8038
|
}
|
|
7667
8039
|
return hydrated;
|
|
7668
8040
|
}
|
|
@@ -7749,6 +8121,10 @@ var EntityLabelGroup;
|
|
|
7749
8121
|
return null;
|
|
7750
8122
|
}
|
|
7751
8123
|
}
|
|
8124
|
+
// How many entities one attachment read asks for.
|
|
8125
|
+
const IMAGES_AT_ONCE = 100;
|
|
8126
|
+
// How long on-screen requests gather before they are read together.
|
|
8127
|
+
const IMAGES_GATHER = 60;
|
|
7752
8128
|
/*
|
|
7753
8129
|
* Owns membership independently of manual labelled states, with atomic replacement and stale-load cancellation.
|
|
7754
8130
|
*/
|
|
@@ -7764,6 +8140,12 @@ var EntityLabelGroup;
|
|
|
7764
8140
|
this.definitions = new Map();
|
|
7765
8141
|
this.visibility = new Map();
|
|
7766
8142
|
this.errors = new Map();
|
|
8143
|
+
// Default image per entity, null once read and found to have none. Shared by every group.
|
|
8144
|
+
this.images = new Map();
|
|
8145
|
+
// Members whose labels came on screen and are waiting on, or being read for, their default image.
|
|
8146
|
+
this.imagesQueued = new Set();
|
|
8147
|
+
this.imagesAsking = new Set();
|
|
8148
|
+
this.imagesTimer = null;
|
|
7767
8149
|
}
|
|
7768
8150
|
notify() {
|
|
7769
8151
|
try {
|
|
@@ -7909,7 +8291,7 @@ var EntityLabelGroup;
|
|
|
7909
8291
|
return false;
|
|
7910
8292
|
}
|
|
7911
8293
|
Resolve(entityId, menuItemId) {
|
|
7912
|
-
var _a, _b, _c, _d, _e
|
|
8294
|
+
var _a, _b, _c, _d, _e;
|
|
7913
8295
|
const ids = Array.from(this.members.get(entityId) || []).reverse();
|
|
7914
8296
|
for (const id of ids) {
|
|
7915
8297
|
const group = this.active.get(id);
|
|
@@ -7919,61 +8301,145 @@ var EntityLabelGroup;
|
|
|
7919
8301
|
if (group.definition.menuItemId && group.definition.menuItemId !== menuItemId) {
|
|
7920
8302
|
continue;
|
|
7921
8303
|
}
|
|
8304
|
+
const options = this.compose(group, entityId, menuItemId);
|
|
7922
8305
|
const item = group.items.get(entityId);
|
|
7923
|
-
const
|
|
7924
|
-
|
|
7925
|
-
|
|
7926
|
-
|
|
7927
|
-
|
|
8306
|
+
const image = (_a = this.images.get(entityId)) !== null && _a !== void 0 ? _a : (Array.isArray((_c = (_b = item.entity) === null || _b === void 0 ? void 0 : _b.Bruce) === null || _c === void 0 ? void 0 : _c.Attachment) ? EntityLabel.GetDefaultImageUrl(item.entity, (_d = this.register.ApiGetters) === null || _d === void 0 ? void 0 : _d.GetBruceApi()) : null);
|
|
8307
|
+
if (image && !EntityLabel.HasIcon(options)) {
|
|
8308
|
+
(_e = options.photo) !== null && _e !== void 0 ? _e : (options.photo = image);
|
|
8309
|
+
}
|
|
8310
|
+
return { groupId: id, options, item };
|
|
8311
|
+
}
|
|
8312
|
+
return null;
|
|
8313
|
+
}
|
|
8314
|
+
/*
|
|
8315
|
+
* One member's label settings, with any image its own text links to but before its default image.
|
|
8316
|
+
*/
|
|
8317
|
+
compose(group, entityId, menuItemId) {
|
|
8318
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
|
|
8319
|
+
const item = group.items.get(entityId);
|
|
8320
|
+
const entity = (_a = item.entity) !== null && _a !== void 0 ? _a : { Bruce: { ID: entityId, Name: item.text } };
|
|
8321
|
+
let iconPriority = (_b = group.definition.options) === null || _b === void 0 ? void 0 : _b.iconPriority;
|
|
8322
|
+
if (Array.isArray(iconPriority)) {
|
|
8323
|
+
try {
|
|
8324
|
+
iconPriority = Calculator.GetNumber(iconPriority, entity);
|
|
8325
|
+
}
|
|
8326
|
+
catch {
|
|
8327
|
+
iconPriority = null;
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8330
|
+
let iconScale = (_c = group.definition.options) === null || _c === void 0 ? void 0 : _c.iconScale;
|
|
8331
|
+
if (Array.isArray(iconScale)) {
|
|
8332
|
+
try {
|
|
8333
|
+
iconScale = Calculator.GetNumber(iconScale, entity);
|
|
8334
|
+
}
|
|
8335
|
+
catch {
|
|
8336
|
+
iconScale = null;
|
|
8337
|
+
}
|
|
8338
|
+
if (!Number.isFinite(iconScale)) {
|
|
8339
|
+
iconScale = 1;
|
|
8340
|
+
}
|
|
8341
|
+
}
|
|
8342
|
+
if (iconScale != null) {
|
|
8343
|
+
iconScale = Math.max(0, iconScale);
|
|
8344
|
+
}
|
|
8345
|
+
const textSetting = (_d = group.definition.options) === null || _d === void 0 ? void 0 : _d.text;
|
|
8346
|
+
let resolvedText = resolveIcon(textSetting, entity);
|
|
8347
|
+
const expression = Array.isArray(textSetting);
|
|
8348
|
+
if (expression && !(resolvedText === null || resolvedText === void 0 ? void 0 : resolvedText.trim())) {
|
|
8349
|
+
resolvedText = undefined;
|
|
8350
|
+
}
|
|
8351
|
+
const fallbackText = [item.text, (_e = entity.Bruce) === null || _e === void 0 ? void 0 : _e.Name, (_f = this.register.GetRego({ entityId, menuItemId })) === null || _f === void 0 ? void 0 : _f.name, entityId]
|
|
8352
|
+
.find(text => typeof text === "string" && text.trim().length > 0);
|
|
8353
|
+
const options = {
|
|
8354
|
+
...group.definition.options,
|
|
8355
|
+
text: resolvedText !== null && resolvedText !== void 0 ? resolvedText : fallbackText,
|
|
8356
|
+
iconScale: iconScale,
|
|
8357
|
+
icon: resolveIcon((_g = group.definition.options) === null || _g === void 0 ? void 0 : _g.icon, entity),
|
|
8358
|
+
iconUrl: resolveIcon((_h = group.definition.options) === null || _h === void 0 ? void 0 : _h.iconUrl, entity),
|
|
8359
|
+
iconClass: resolveIcon((_j = group.definition.options) === null || _j === void 0 ? void 0 : _j.iconClass, entity),
|
|
8360
|
+
iconColor: resolveIcon((_k = group.definition.options) === null || _k === void 0 ? void 0 : _k.iconColor, entity),
|
|
8361
|
+
iconBackground: (_l = group.definition.options) === null || _l === void 0 ? void 0 : _l.iconBackground,
|
|
8362
|
+
iconPriority: Number.isFinite(iconPriority) ? iconPriority : item.iconPriority,
|
|
8363
|
+
iconStatus: (_o = resolveIcon((_m = group.definition.options) === null || _m === void 0 ? void 0 : _m.iconStatus, entity)) !== null && _o !== void 0 ? _o : item.iconStatus
|
|
8364
|
+
};
|
|
8365
|
+
if (options.attributes && item.entity && !(expression && resolvedText != null)) {
|
|
8366
|
+
options.text = [
|
|
8367
|
+
(_r = (_p = options.text) !== null && _p !== void 0 ? _p : (_q = item.entity.Bruce) === null || _q === void 0 ? void 0 : _q.Name) !== null && _r !== void 0 ? _r : entityId,
|
|
8368
|
+
...options.attributes.map(path => Entity$1.GetValue({ entity: item.entity, path })).filter(value => value != null).map(String)
|
|
8369
|
+
].join("\n");
|
|
8370
|
+
}
|
|
8371
|
+
if (!EntityLabel.HasIcon(options) && typeof options.text === "string") {
|
|
8372
|
+
const lines = options.text.split("\n");
|
|
8373
|
+
const at = lines.findIndex(line => EntityLabel.IsImageUrl(line));
|
|
8374
|
+
if (at >= 0) {
|
|
8375
|
+
const [url] = lines.splice(at, 1);
|
|
8376
|
+
options.text = lines.join("\n");
|
|
8377
|
+
options.photo = url.trim();
|
|
8378
|
+
}
|
|
8379
|
+
}
|
|
8380
|
+
return options;
|
|
8381
|
+
}
|
|
8382
|
+
/*
|
|
8383
|
+
* Queues a member's default image once its label is on screen, so members out of view never cost a read.
|
|
8384
|
+
* Requests arriving close together are read as one.
|
|
8385
|
+
*/
|
|
8386
|
+
RequestImage(entityId) {
|
|
8387
|
+
var _a, _b, _c, _d, _e, _f;
|
|
8388
|
+
const api = (_a = this.register.ApiGetters) === null || _a === void 0 ? void 0 : _a.GetBruceApi();
|
|
8389
|
+
if (!api || !entityId || this.images.has(entityId) || this.imagesAsking.has(entityId)) {
|
|
8390
|
+
return;
|
|
8391
|
+
}
|
|
8392
|
+
// Members read with their attachments already know their image, or that they have none.
|
|
8393
|
+
for (const id of this.members.get(entityId) || []) {
|
|
8394
|
+
if (Array.isArray((_e = (_d = (_c = (_b = this.active.get(id)) === null || _b === void 0 ? void 0 : _b.items.get(entityId)) === null || _c === void 0 ? void 0 : _c.entity) === null || _d === void 0 ? void 0 : _d.Bruce) === null || _e === void 0 ? void 0 : _e.Attachment)) {
|
|
8395
|
+
return;
|
|
8396
|
+
}
|
|
8397
|
+
}
|
|
8398
|
+
if (!this.members.has(entityId)) {
|
|
8399
|
+
return;
|
|
8400
|
+
}
|
|
8401
|
+
this.imagesAsking.add(entityId);
|
|
8402
|
+
this.imagesQueued.add(entityId);
|
|
8403
|
+
(_f = this.imagesTimer) !== null && _f !== void 0 ? _f : (this.imagesTimer = setTimeout(() => {
|
|
8404
|
+
this.imagesTimer = null;
|
|
8405
|
+
this.readImages(api).catch(error => console.error("Label images could not be loaded", error));
|
|
8406
|
+
}, IMAGES_GATHER));
|
|
8407
|
+
}
|
|
8408
|
+
async readImages(api) {
|
|
8409
|
+
var _a, _b;
|
|
8410
|
+
const ids = Array.from(this.imagesQueued);
|
|
8411
|
+
this.imagesQueued.clear();
|
|
8412
|
+
for (let start = 0; start < ids.length; start += IMAGES_AT_ONCE) {
|
|
8413
|
+
const chunk = ids.slice(start, start + IMAGES_AT_ONCE);
|
|
8414
|
+
let entities;
|
|
8415
|
+
try {
|
|
8416
|
+
entities = (_a = (await Entity$1.GetListByIds({ api, entityIds: chunk, expandAttachments: true, migrated: true })).entities) !== null && _a !== void 0 ? _a : [];
|
|
8417
|
+
}
|
|
8418
|
+
catch {
|
|
8419
|
+
// Left unread, so the next label to come on screen for these members asks again.
|
|
8420
|
+
entities = null;
|
|
8421
|
+
}
|
|
8422
|
+
if (this.register.Disposed) {
|
|
8423
|
+
return;
|
|
8424
|
+
}
|
|
8425
|
+
const found = new Map((entities !== null && entities !== void 0 ? entities : []).map(entity => { var _a; return [(_a = entity === null || entity === void 0 ? void 0 : entity.Bruce) === null || _a === void 0 ? void 0 : _a.ID, EntityLabel.GetDefaultImageUrl(entity, api)]; }));
|
|
8426
|
+
const gained = [];
|
|
8427
|
+
for (const id of chunk) {
|
|
8428
|
+
if (!this.imagesAsking.delete(id) || !entities) {
|
|
8429
|
+
continue;
|
|
7928
8430
|
}
|
|
7929
|
-
|
|
7930
|
-
|
|
8431
|
+
const url = (_b = found.get(id)) !== null && _b !== void 0 ? _b : null;
|
|
8432
|
+
this.images.set(id, url);
|
|
8433
|
+
if (url) {
|
|
8434
|
+
gained.push(id);
|
|
7931
8435
|
}
|
|
7932
8436
|
}
|
|
7933
|
-
|
|
7934
|
-
if (
|
|
7935
|
-
|
|
7936
|
-
|
|
7937
|
-
}
|
|
7938
|
-
catch {
|
|
7939
|
-
iconScale = null;
|
|
7940
|
-
}
|
|
7941
|
-
if (!Number.isFinite(iconScale)) {
|
|
7942
|
-
iconScale = 1;
|
|
7943
|
-
}
|
|
7944
|
-
}
|
|
7945
|
-
if (iconScale != null) {
|
|
7946
|
-
iconScale = Math.max(0, iconScale);
|
|
7947
|
-
}
|
|
7948
|
-
const textSetting = (_d = group.definition.options) === null || _d === void 0 ? void 0 : _d.text;
|
|
7949
|
-
let resolvedText = resolveIcon(textSetting, entity);
|
|
7950
|
-
const expression = Array.isArray(textSetting);
|
|
7951
|
-
if (expression && !(resolvedText === null || resolvedText === void 0 ? void 0 : resolvedText.trim())) {
|
|
7952
|
-
resolvedText = undefined;
|
|
7953
|
-
}
|
|
7954
|
-
const fallbackText = [item.text, (_e = entity.Bruce) === null || _e === void 0 ? void 0 : _e.Name, (_f = this.register.GetRego({ entityId, menuItemId })) === null || _f === void 0 ? void 0 : _f.name, entityId]
|
|
7955
|
-
.find(text => typeof text === "string" && text.trim().length > 0);
|
|
7956
|
-
const options = {
|
|
7957
|
-
...group.definition.options,
|
|
7958
|
-
text: resolvedText !== null && resolvedText !== void 0 ? resolvedText : fallbackText,
|
|
7959
|
-
iconScale: iconScale,
|
|
7960
|
-
icon: resolveIcon((_g = group.definition.options) === null || _g === void 0 ? void 0 : _g.icon, entity),
|
|
7961
|
-
iconUrl: resolveIcon((_h = group.definition.options) === null || _h === void 0 ? void 0 : _h.iconUrl, entity),
|
|
7962
|
-
iconClass: resolveIcon((_j = group.definition.options) === null || _j === void 0 ? void 0 : _j.iconClass, entity),
|
|
7963
|
-
iconColor: resolveIcon((_k = group.definition.options) === null || _k === void 0 ? void 0 : _k.iconColor, entity),
|
|
7964
|
-
iconBackground: (_l = group.definition.options) === null || _l === void 0 ? void 0 : _l.iconBackground,
|
|
7965
|
-
iconPriority: Number.isFinite(iconPriority) ? iconPriority : item.iconPriority,
|
|
7966
|
-
iconStatus: (_o = resolveIcon((_m = group.definition.options) === null || _m === void 0 ? void 0 : _m.iconStatus, entity)) !== null && _o !== void 0 ? _o : item.iconStatus
|
|
7967
|
-
};
|
|
7968
|
-
if (options.attributes && item.entity && !(expression && resolvedText != null)) {
|
|
7969
|
-
options.text = [
|
|
7970
|
-
(_r = (_p = options.text) !== null && _p !== void 0 ? _p : (_q = item.entity.Bruce) === null || _q === void 0 ? void 0 : _q.Name) !== null && _r !== void 0 ? _r : entityId,
|
|
7971
|
-
...options.attributes.map(path => Entity$1.GetValue({ entity: item.entity, path })).filter(value => value != null).map(String)
|
|
7972
|
-
].join("\n");
|
|
8437
|
+
// Reconfigured in place rather than through refresh, which would drop a standalone label and redraw it.
|
|
8438
|
+
if (gained.length) {
|
|
8439
|
+
this.register.ForceUpdate({ entityIds: gained });
|
|
8440
|
+
this.register.Viewer.scene.requestRender();
|
|
7973
8441
|
}
|
|
7974
|
-
return { groupId: id, options, item };
|
|
7975
8442
|
}
|
|
7976
|
-
return null;
|
|
7977
8443
|
}
|
|
7978
8444
|
RefreshEntity(entityId) {
|
|
7979
8445
|
var _a, _b, _c, _d, _e, _f, _g;
|
|
@@ -8031,6 +8497,11 @@ var EntityLabelGroup;
|
|
|
8031
8497
|
}
|
|
8032
8498
|
this.unindex(id);
|
|
8033
8499
|
this.active.delete(id);
|
|
8500
|
+
for (const entityId of group.items.keys()) {
|
|
8501
|
+
if (!this.members.has(entityId)) {
|
|
8502
|
+
this.images.delete(entityId);
|
|
8503
|
+
}
|
|
8504
|
+
}
|
|
8034
8505
|
this.refresh(new Set(group.items.keys()));
|
|
8035
8506
|
this.notify();
|
|
8036
8507
|
}
|
|
@@ -8039,6 +8510,7 @@ var EntityLabelGroup;
|
|
|
8039
8510
|
controller.abort();
|
|
8040
8511
|
}
|
|
8041
8512
|
this.pending.clear();
|
|
8513
|
+
this.clearImages();
|
|
8042
8514
|
const affected = new Set(this.members.keys());
|
|
8043
8515
|
this.active.clear();
|
|
8044
8516
|
this.members.clear();
|
|
@@ -8055,6 +8527,7 @@ var EntityLabelGroup;
|
|
|
8055
8527
|
for (const label of this.standalone.values()) {
|
|
8056
8528
|
label.Dispose();
|
|
8057
8529
|
}
|
|
8530
|
+
this.clearImages();
|
|
8058
8531
|
this.pending.clear();
|
|
8059
8532
|
this.standalone.clear();
|
|
8060
8533
|
this.standaloneItems.clear();
|
|
@@ -8064,6 +8537,13 @@ var EntityLabelGroup;
|
|
|
8064
8537
|
this.visibility.clear();
|
|
8065
8538
|
this.errors.clear();
|
|
8066
8539
|
}
|
|
8540
|
+
clearImages() {
|
|
8541
|
+
clearTimeout(this.imagesTimer);
|
|
8542
|
+
this.imagesTimer = null;
|
|
8543
|
+
this.imagesQueued.clear();
|
|
8544
|
+
this.imagesAsking.clear();
|
|
8545
|
+
this.images.clear();
|
|
8546
|
+
}
|
|
8067
8547
|
unindex(groupId) {
|
|
8068
8548
|
var _a;
|
|
8069
8549
|
for (const id of ((_a = this.active.get(groupId)) === null || _a === void 0 ? void 0 : _a.items.keys()) || []) {
|
|
@@ -12417,8 +12897,15 @@ var VisualsRegister;
|
|
|
12417
12897
|
createIfMissing: params.createIfMissing !== false
|
|
12418
12898
|
};
|
|
12419
12899
|
const doc = this.viewer.container.ownerDocument;
|
|
12420
|
-
|
|
12421
|
-
|
|
12900
|
+
// Only a label following the cursor should go when the cursor leaves the canvas.
|
|
12901
|
+
if (params.position) {
|
|
12902
|
+
doc.addEventListener("pointermove", this.CheckLabelHoverBounds, true);
|
|
12903
|
+
this.viewer.container.addEventListener("pointerleave", this.CheckLabelHoverBounds);
|
|
12904
|
+
}
|
|
12905
|
+
else {
|
|
12906
|
+
doc.removeEventListener("pointermove", this.CheckLabelHoverBounds, true);
|
|
12907
|
+
this.viewer.container.removeEventListener("pointerleave", this.CheckLabelHoverBounds);
|
|
12908
|
+
}
|
|
12422
12909
|
doc.defaultView.addEventListener("blur", this.ClearLabelHover);
|
|
12423
12910
|
this.SyncLabelHover();
|
|
12424
12911
|
this.viewer.scene.requestRender();
|
|
@@ -12554,6 +13041,13 @@ var VisualsRegister;
|
|
|
12554
13041
|
var _a;
|
|
12555
13042
|
return (_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.Resolve(entityId, menuItemId);
|
|
12556
13043
|
}
|
|
13044
|
+
/*
|
|
13045
|
+
* Called by a group label once it is on screen, so its entity's default image is read only when it can be seen.
|
|
13046
|
+
*/
|
|
13047
|
+
RequestLabelImage(entityId) {
|
|
13048
|
+
var _a;
|
|
13049
|
+
(_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.RequestImage(entityId);
|
|
13050
|
+
}
|
|
12557
13051
|
RefreshLabelGroupEntity(entityId) {
|
|
12558
13052
|
var _a, _b;
|
|
12559
13053
|
(_a = this.labelGroups) === null || _a === void 0 ? void 0 : _a.RefreshEntity(entityId);
|
|
@@ -27315,6 +27809,159 @@ var TilesetI3sRenderManager;
|
|
|
27315
27809
|
TilesetI3sRenderManager.Manager = Manager;
|
|
27316
27810
|
})(TilesetI3sRenderManager || (TilesetI3sRenderManager = {}));
|
|
27317
27811
|
|
|
27812
|
+
/**
|
|
27813
|
+
* Remaps a (assumed grayscale, server-rendered black-to-white) image's pixels into a gradient between minColor/maxColor,
|
|
27814
|
+
* preserving source alpha blended with the mask's own alpha.
|
|
27815
|
+
* @param imageData
|
|
27816
|
+
* @param minColor
|
|
27817
|
+
* @param maxColor
|
|
27818
|
+
*/
|
|
27819
|
+
function ApplyGrayscaleColorMask(imageData, minColor, maxColor) {
|
|
27820
|
+
const data = imageData.data;
|
|
27821
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
27822
|
+
// The texture is rendered black-to-white server-side, so any channel carries the value.
|
|
27823
|
+
const t = data[i] / 255;
|
|
27824
|
+
data[i] = Math.round(minColor.red + (maxColor.red - minColor.red) * t);
|
|
27825
|
+
data[i + 1] = Math.round(minColor.green + (maxColor.green - minColor.green) * t);
|
|
27826
|
+
data[i + 2] = Math.round(minColor.blue + (maxColor.blue - minColor.blue) * t);
|
|
27827
|
+
// Preserve source transparency (eg unrasterized pixels) blended with the mask's own alpha.
|
|
27828
|
+
const srcAlpha = data[i + 3] / 255;
|
|
27829
|
+
const maskAlpha = minColor.alpha + (maxColor.alpha - minColor.alpha) * t;
|
|
27830
|
+
data[i + 3] = Math.round(srcAlpha * maskAlpha * 255);
|
|
27831
|
+
}
|
|
27832
|
+
}
|
|
27833
|
+
/**
|
|
27834
|
+
* The colour a ramp gives a normalised value.
|
|
27835
|
+
* @param sorted stops in ascending position, normalised into the texture's value range
|
|
27836
|
+
*/
|
|
27837
|
+
function SampleRamp(sorted, t) {
|
|
27838
|
+
let upper = 0;
|
|
27839
|
+
while (upper < sorted.length && sorted[upper].position < t) {
|
|
27840
|
+
upper++;
|
|
27841
|
+
}
|
|
27842
|
+
if (upper === 0) {
|
|
27843
|
+
return sorted[0].color;
|
|
27844
|
+
}
|
|
27845
|
+
if (upper >= sorted.length) {
|
|
27846
|
+
return sorted[sorted.length - 1].color;
|
|
27847
|
+
}
|
|
27848
|
+
const a = sorted[upper - 1];
|
|
27849
|
+
const b = sorted[upper];
|
|
27850
|
+
const span = b.position - a.position;
|
|
27851
|
+
// Equal positions are a hard step rather than a division by zero, which is how a band
|
|
27852
|
+
// gets a crisp edge instead of a gradient into its neighbour.
|
|
27853
|
+
const f = span > 0 ? (t - a.position) / span : 1;
|
|
27854
|
+
return {
|
|
27855
|
+
red: a.color.red + (b.color.red - a.color.red) * f,
|
|
27856
|
+
green: a.color.green + (b.color.green - a.color.green) * f,
|
|
27857
|
+
blue: a.color.blue + (b.color.blue - a.color.blue) * f,
|
|
27858
|
+
alpha: a.color.alpha + (b.color.alpha - a.color.alpha) * f
|
|
27859
|
+
};
|
|
27860
|
+
}
|
|
27861
|
+
/**
|
|
27862
|
+
* A ramp baked into RGBA bytes, for a shader that cannot walk stops per fragment.
|
|
27863
|
+
* @param steps entries in the lookup, each covering an equal slice of the value range
|
|
27864
|
+
*/
|
|
27865
|
+
function RampLookup(stops, low, high, steps = 256) {
|
|
27866
|
+
const sorted = stops && stops.length > 0
|
|
27867
|
+
? stops.slice().sort((a, b) => a.position - b.position)
|
|
27868
|
+
: null;
|
|
27869
|
+
const out = new Uint8Array(steps * 4);
|
|
27870
|
+
for (let i = 0; i < steps; i++) {
|
|
27871
|
+
const t = steps > 1 ? i / (steps - 1) : 0;
|
|
27872
|
+
const color = sorted
|
|
27873
|
+
? SampleRamp(sorted, t)
|
|
27874
|
+
: {
|
|
27875
|
+
red: low.red + (high.red - low.red) * t,
|
|
27876
|
+
green: low.green + (high.green - low.green) * t,
|
|
27877
|
+
blue: low.blue + (high.blue - low.blue) * t,
|
|
27878
|
+
alpha: low.alpha + (high.alpha - low.alpha) * t
|
|
27879
|
+
};
|
|
27880
|
+
out[i * 4] = Math.round(color.red);
|
|
27881
|
+
out[i * 4 + 1] = Math.round(color.green);
|
|
27882
|
+
out[i * 4 + 2] = Math.round(color.blue);
|
|
27883
|
+
out[i * 4 + 3] = Math.round(Math.min(1, Math.max(0, color.alpha)) * 255);
|
|
27884
|
+
}
|
|
27885
|
+
return out;
|
|
27886
|
+
}
|
|
27887
|
+
function ApplyGradientStops(imageData, stops) {
|
|
27888
|
+
if (!stops || stops.length === 0) {
|
|
27889
|
+
return;
|
|
27890
|
+
}
|
|
27891
|
+
const sorted = stops.slice().sort((a, b) => a.position - b.position);
|
|
27892
|
+
const data = imageData.data;
|
|
27893
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
27894
|
+
const t = data[i] / 255;
|
|
27895
|
+
const color = SampleRamp(sorted, t);
|
|
27896
|
+
data[i] = Math.round(color.red);
|
|
27897
|
+
data[i + 1] = Math.round(color.green);
|
|
27898
|
+
data[i + 2] = Math.round(color.blue);
|
|
27899
|
+
const srcAlpha = data[i + 3] / 255;
|
|
27900
|
+
data[i + 3] = Math.round(srcAlpha * color.alpha * 255);
|
|
27901
|
+
}
|
|
27902
|
+
}
|
|
27903
|
+
/**
|
|
27904
|
+
* Draws the texture's own cell grid into the tinted pixels.
|
|
27905
|
+
*
|
|
27906
|
+
* Drawn in texture space rather than screen space so it stays with the cells it describes and needs
|
|
27907
|
+
* no per-frame work as the camera moves.
|
|
27908
|
+
*/
|
|
27909
|
+
function ApplyCellBorders(imageData, cellTexels, widthTexels, color) {
|
|
27910
|
+
if (!(cellTexels > 0) || !(widthTexels > 0) || !color || color.alpha <= 0) {
|
|
27911
|
+
return;
|
|
27912
|
+
}
|
|
27913
|
+
const { width, height, data } = imageData;
|
|
27914
|
+
const edge = Math.max(1, Math.round(widthTexels));
|
|
27915
|
+
for (let y = 0; y < height; y++) {
|
|
27916
|
+
const onRow = (y % cellTexels) < edge;
|
|
27917
|
+
for (let x = 0; x < width; x++) {
|
|
27918
|
+
if (!onRow && (x % cellTexels) >= edge) {
|
|
27919
|
+
continue;
|
|
27920
|
+
}
|
|
27921
|
+
const i = (y * width + x) * 4;
|
|
27922
|
+
// Only where something is already drawn, so a border cannot invent coverage the data
|
|
27923
|
+
// does not have.
|
|
27924
|
+
if (data[i + 3] === 0) {
|
|
27925
|
+
continue;
|
|
27926
|
+
}
|
|
27927
|
+
const a = color.alpha;
|
|
27928
|
+
data[i] = Math.round(data[i] * (1 - a) + color.red * a);
|
|
27929
|
+
data[i + 1] = Math.round(data[i + 1] * (1 - a) + color.green * a);
|
|
27930
|
+
data[i + 2] = Math.round(data[i + 2] * (1 - a) + color.blue * a);
|
|
27931
|
+
}
|
|
27932
|
+
}
|
|
27933
|
+
}
|
|
27934
|
+
function loadImage(src) {
|
|
27935
|
+
return new Promise((res, rej) => {
|
|
27936
|
+
const image = new Image();
|
|
27937
|
+
image.onload = () => res(image);
|
|
27938
|
+
image.onerror = (e) => rej(e);
|
|
27939
|
+
image.src = src;
|
|
27940
|
+
});
|
|
27941
|
+
}
|
|
27942
|
+
/*
|
|
27943
|
+
* Loads an image whose pixels will be read back or uploaded to WebGL, which needs a CORS-clean copy.
|
|
27944
|
+
*/
|
|
27945
|
+
async function loadImageForCanvas(url) {
|
|
27946
|
+
let response;
|
|
27947
|
+
try {
|
|
27948
|
+
response = await fetch(url);
|
|
27949
|
+
}
|
|
27950
|
+
catch {
|
|
27951
|
+
response = await fetch(url, { cache: "reload" });
|
|
27952
|
+
}
|
|
27953
|
+
if (!response.ok) {
|
|
27954
|
+
throw new Error(`Image request failed with ${response.status}: ${url}`);
|
|
27955
|
+
}
|
|
27956
|
+
const objectUrl = URL.createObjectURL(await response.blob());
|
|
27957
|
+
try {
|
|
27958
|
+
return await loadImage(objectUrl);
|
|
27959
|
+
}
|
|
27960
|
+
finally {
|
|
27961
|
+
URL.revokeObjectURL(objectUrl);
|
|
27962
|
+
}
|
|
27963
|
+
}
|
|
27964
|
+
|
|
27318
27965
|
var GoogleSearchRenderManager;
|
|
27319
27966
|
(function (GoogleSearchRenderManager) {
|
|
27320
27967
|
class Manager {
|
|
@@ -27621,45 +28268,39 @@ async function createBillboardImage(url, bgColor, size) {
|
|
|
27621
28268
|
return cacheData;
|
|
27622
28269
|
}
|
|
27623
28270
|
const IMAGE_PADDING = 8;
|
|
27624
|
-
const prom =
|
|
27625
|
-
|
|
27626
|
-
|
|
27627
|
-
|
|
27628
|
-
|
|
27629
|
-
|
|
27630
|
-
|
|
27631
|
-
|
|
27632
|
-
|
|
27633
|
-
|
|
27634
|
-
|
|
27635
|
-
|
|
27636
|
-
|
|
27637
|
-
|
|
27638
|
-
|
|
27639
|
-
|
|
27640
|
-
|
|
27641
|
-
|
|
27642
|
-
|
|
27643
|
-
|
|
27644
|
-
|
|
27645
|
-
|
|
27646
|
-
|
|
27647
|
-
|
|
27648
|
-
|
|
27649
|
-
context.arc(size / 2, size / 2, size / 2 - 1, 0, 2 * Math.PI, false);
|
|
27650
|
-
context.lineWidth = 1;
|
|
27651
|
-
context.strokeStyle = "rgba(0, 0, 0, 0.3)";
|
|
27652
|
-
context.stroke();
|
|
27653
|
-
res(canvas);
|
|
27654
|
-
};
|
|
27655
|
-
image.onerror = (e) => rej(e);
|
|
27656
|
-
image.src = URL.createObjectURL(blob);
|
|
27657
|
-
}
|
|
27658
|
-
catch (e) {
|
|
27659
|
-
rej(e);
|
|
27660
|
-
}
|
|
28271
|
+
const prom = loadImageForCanvas(url).then(image => {
|
|
28272
|
+
const canvas = document.createElement("canvas");
|
|
28273
|
+
canvas.width = size;
|
|
28274
|
+
canvas.height = size;
|
|
28275
|
+
const context = canvas.getContext("2d");
|
|
28276
|
+
// Calculate padding and available space.
|
|
28277
|
+
const availableSize = size - 2 * IMAGE_PADDING;
|
|
28278
|
+
// Calculate resized dimensions preserving aspect ratio.
|
|
28279
|
+
const { newWidth, newHeight } = calculateBillboardSpace(image.width, image.height, availableSize, availableSize);
|
|
28280
|
+
// Draw the circular background.
|
|
28281
|
+
context.beginPath();
|
|
28282
|
+
context.arc(size / 2, size / 2, size / 2, 0, 2 * Math.PI, false);
|
|
28283
|
+
context.fillStyle = bgColor;
|
|
28284
|
+
context.fill();
|
|
28285
|
+
// Draw the image centered within the circle.
|
|
28286
|
+
const offsetX = (size - newWidth) / 2;
|
|
28287
|
+
const offsetY = (size - newHeight) / 2;
|
|
28288
|
+
context.drawImage(image, offsetX, offsetY, newWidth, newHeight);
|
|
28289
|
+
// Draw a 2px darkened border around the circle.
|
|
28290
|
+
context.beginPath();
|
|
28291
|
+
context.arc(size / 2, size / 2, size / 2 - 1, 0, 2 * Math.PI, false);
|
|
28292
|
+
context.lineWidth = 1;
|
|
28293
|
+
context.strokeStyle = "rgba(0, 0, 0, 0.3)";
|
|
28294
|
+
context.stroke();
|
|
28295
|
+
return canvas;
|
|
27661
28296
|
});
|
|
27662
28297
|
_billboardCache.Set(key, prom);
|
|
28298
|
+
// A failure is not kept, so the icon is tried again rather than staying missing for the session.
|
|
28299
|
+
prom.catch(() => {
|
|
28300
|
+
if (_billboardCache.Get(key) === prom) {
|
|
28301
|
+
_billboardCache.Delete(key);
|
|
28302
|
+
}
|
|
28303
|
+
});
|
|
27663
28304
|
return prom;
|
|
27664
28305
|
}
|
|
27665
28306
|
|
|
@@ -43540,33 +44181,23 @@ const createImageBillboard = (url) => {
|
|
|
43540
44181
|
if (cacheData) {
|
|
43541
44182
|
return cacheData;
|
|
43542
44183
|
}
|
|
43543
|
-
const prom =
|
|
43544
|
-
|
|
43545
|
-
|
|
43546
|
-
|
|
43547
|
-
|
|
43548
|
-
|
|
43549
|
-
|
|
43550
|
-
|
|
43551
|
-
|
|
43552
|
-
const context = canvas.getContext("2d");
|
|
43553
|
-
context.drawImage(image, 0, 0);
|
|
43554
|
-
const data = {
|
|
43555
|
-
canvasDataUri: canvas.toDataURL("image/png"),
|
|
43556
|
-
height: image.height
|
|
43557
|
-
};
|
|
43558
|
-
res(data);
|
|
43559
|
-
};
|
|
43560
|
-
image.onerror = (e) => {
|
|
43561
|
-
rej(e);
|
|
43562
|
-
};
|
|
43563
|
-
image.src = URL.createObjectURL(blob);
|
|
43564
|
-
}
|
|
43565
|
-
catch (e) {
|
|
43566
|
-
rej(e);
|
|
43567
|
-
}
|
|
44184
|
+
const prom = loadImageForCanvas(url).then((image) => {
|
|
44185
|
+
const canvas = document.createElement("canvas");
|
|
44186
|
+
canvas.width = image.width;
|
|
44187
|
+
canvas.height = image.height;
|
|
44188
|
+
canvas.getContext("2d").drawImage(image, 0, 0);
|
|
44189
|
+
return {
|
|
44190
|
+
canvasDataUri: canvas.toDataURL("image/png"),
|
|
44191
|
+
height: image.height
|
|
44192
|
+
};
|
|
43568
44193
|
});
|
|
43569
44194
|
_billboardCache$1.Set(cacheKey, prom);
|
|
44195
|
+
// A failure is not kept, so the icon is tried again rather than staying missing for the session.
|
|
44196
|
+
prom.catch(() => {
|
|
44197
|
+
if (_billboardCache$1.Get(cacheKey) === prom) {
|
|
44198
|
+
_billboardCache$1.Delete(cacheKey);
|
|
44199
|
+
}
|
|
44200
|
+
});
|
|
43570
44201
|
return prom;
|
|
43571
44202
|
};
|
|
43572
44203
|
/**
|
|
@@ -45026,137 +45657,6 @@ function getZIndex(style, entity, tags) {
|
|
|
45026
45657
|
return EnsureNumber(zIndex);
|
|
45027
45658
|
}
|
|
45028
45659
|
|
|
45029
|
-
/**
|
|
45030
|
-
* Remaps a (assumed grayscale, server-rendered black-to-white) image's pixels into a gradient between minColor/maxColor,
|
|
45031
|
-
* preserving source alpha blended with the mask's own alpha.
|
|
45032
|
-
* @param imageData
|
|
45033
|
-
* @param minColor
|
|
45034
|
-
* @param maxColor
|
|
45035
|
-
*/
|
|
45036
|
-
function ApplyGrayscaleColorMask(imageData, minColor, maxColor) {
|
|
45037
|
-
const data = imageData.data;
|
|
45038
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
45039
|
-
// The texture is rendered black-to-white server-side, so any channel carries the value.
|
|
45040
|
-
const t = data[i] / 255;
|
|
45041
|
-
data[i] = Math.round(minColor.red + (maxColor.red - minColor.red) * t);
|
|
45042
|
-
data[i + 1] = Math.round(minColor.green + (maxColor.green - minColor.green) * t);
|
|
45043
|
-
data[i + 2] = Math.round(minColor.blue + (maxColor.blue - minColor.blue) * t);
|
|
45044
|
-
// Preserve source transparency (eg unrasterized pixels) blended with the mask's own alpha.
|
|
45045
|
-
const srcAlpha = data[i + 3] / 255;
|
|
45046
|
-
const maskAlpha = minColor.alpha + (maxColor.alpha - minColor.alpha) * t;
|
|
45047
|
-
data[i + 3] = Math.round(srcAlpha * maskAlpha * 255);
|
|
45048
|
-
}
|
|
45049
|
-
}
|
|
45050
|
-
/**
|
|
45051
|
-
* The colour a ramp gives a normalised value.
|
|
45052
|
-
* @param sorted stops in ascending position, normalised into the texture's value range
|
|
45053
|
-
*/
|
|
45054
|
-
function SampleRamp(sorted, t) {
|
|
45055
|
-
let upper = 0;
|
|
45056
|
-
while (upper < sorted.length && sorted[upper].position < t) {
|
|
45057
|
-
upper++;
|
|
45058
|
-
}
|
|
45059
|
-
if (upper === 0) {
|
|
45060
|
-
return sorted[0].color;
|
|
45061
|
-
}
|
|
45062
|
-
if (upper >= sorted.length) {
|
|
45063
|
-
return sorted[sorted.length - 1].color;
|
|
45064
|
-
}
|
|
45065
|
-
const a = sorted[upper - 1];
|
|
45066
|
-
const b = sorted[upper];
|
|
45067
|
-
const span = b.position - a.position;
|
|
45068
|
-
// Equal positions are a hard step rather than a division by zero, which is how a band
|
|
45069
|
-
// gets a crisp edge instead of a gradient into its neighbour.
|
|
45070
|
-
const f = span > 0 ? (t - a.position) / span : 1;
|
|
45071
|
-
return {
|
|
45072
|
-
red: a.color.red + (b.color.red - a.color.red) * f,
|
|
45073
|
-
green: a.color.green + (b.color.green - a.color.green) * f,
|
|
45074
|
-
blue: a.color.blue + (b.color.blue - a.color.blue) * f,
|
|
45075
|
-
alpha: a.color.alpha + (b.color.alpha - a.color.alpha) * f
|
|
45076
|
-
};
|
|
45077
|
-
}
|
|
45078
|
-
/**
|
|
45079
|
-
* A ramp baked into RGBA bytes, for a shader that cannot walk stops per fragment.
|
|
45080
|
-
* @param steps entries in the lookup, each covering an equal slice of the value range
|
|
45081
|
-
*/
|
|
45082
|
-
function RampLookup(stops, low, high, steps = 256) {
|
|
45083
|
-
const sorted = stops && stops.length > 0
|
|
45084
|
-
? stops.slice().sort((a, b) => a.position - b.position)
|
|
45085
|
-
: null;
|
|
45086
|
-
const out = new Uint8Array(steps * 4);
|
|
45087
|
-
for (let i = 0; i < steps; i++) {
|
|
45088
|
-
const t = steps > 1 ? i / (steps - 1) : 0;
|
|
45089
|
-
const color = sorted
|
|
45090
|
-
? SampleRamp(sorted, t)
|
|
45091
|
-
: {
|
|
45092
|
-
red: low.red + (high.red - low.red) * t,
|
|
45093
|
-
green: low.green + (high.green - low.green) * t,
|
|
45094
|
-
blue: low.blue + (high.blue - low.blue) * t,
|
|
45095
|
-
alpha: low.alpha + (high.alpha - low.alpha) * t
|
|
45096
|
-
};
|
|
45097
|
-
out[i * 4] = Math.round(color.red);
|
|
45098
|
-
out[i * 4 + 1] = Math.round(color.green);
|
|
45099
|
-
out[i * 4 + 2] = Math.round(color.blue);
|
|
45100
|
-
out[i * 4 + 3] = Math.round(Math.min(1, Math.max(0, color.alpha)) * 255);
|
|
45101
|
-
}
|
|
45102
|
-
return out;
|
|
45103
|
-
}
|
|
45104
|
-
function ApplyGradientStops(imageData, stops) {
|
|
45105
|
-
if (!stops || stops.length === 0) {
|
|
45106
|
-
return;
|
|
45107
|
-
}
|
|
45108
|
-
const sorted = stops.slice().sort((a, b) => a.position - b.position);
|
|
45109
|
-
const data = imageData.data;
|
|
45110
|
-
for (let i = 0; i < data.length; i += 4) {
|
|
45111
|
-
const t = data[i] / 255;
|
|
45112
|
-
const color = SampleRamp(sorted, t);
|
|
45113
|
-
data[i] = Math.round(color.red);
|
|
45114
|
-
data[i + 1] = Math.round(color.green);
|
|
45115
|
-
data[i + 2] = Math.round(color.blue);
|
|
45116
|
-
const srcAlpha = data[i + 3] / 255;
|
|
45117
|
-
data[i + 3] = Math.round(srcAlpha * color.alpha * 255);
|
|
45118
|
-
}
|
|
45119
|
-
}
|
|
45120
|
-
/**
|
|
45121
|
-
* Draws the texture's own cell grid into the tinted pixels.
|
|
45122
|
-
*
|
|
45123
|
-
* Drawn in texture space rather than screen space so it stays with the cells it describes and needs
|
|
45124
|
-
* no per-frame work as the camera moves.
|
|
45125
|
-
*/
|
|
45126
|
-
function ApplyCellBorders(imageData, cellTexels, widthTexels, color) {
|
|
45127
|
-
if (!(cellTexels > 0) || !(widthTexels > 0) || !color || color.alpha <= 0) {
|
|
45128
|
-
return;
|
|
45129
|
-
}
|
|
45130
|
-
const { width, height, data } = imageData;
|
|
45131
|
-
const edge = Math.max(1, Math.round(widthTexels));
|
|
45132
|
-
for (let y = 0; y < height; y++) {
|
|
45133
|
-
const onRow = (y % cellTexels) < edge;
|
|
45134
|
-
for (let x = 0; x < width; x++) {
|
|
45135
|
-
if (!onRow && (x % cellTexels) >= edge) {
|
|
45136
|
-
continue;
|
|
45137
|
-
}
|
|
45138
|
-
const i = (y * width + x) * 4;
|
|
45139
|
-
// Only where something is already drawn, so a border cannot invent coverage the data
|
|
45140
|
-
// does not have.
|
|
45141
|
-
if (data[i + 3] === 0) {
|
|
45142
|
-
continue;
|
|
45143
|
-
}
|
|
45144
|
-
const a = color.alpha;
|
|
45145
|
-
data[i] = Math.round(data[i] * (1 - a) + color.red * a);
|
|
45146
|
-
data[i + 1] = Math.round(data[i + 1] * (1 - a) + color.green * a);
|
|
45147
|
-
data[i + 2] = Math.round(data[i + 2] * (1 - a) + color.blue * a);
|
|
45148
|
-
}
|
|
45149
|
-
}
|
|
45150
|
-
}
|
|
45151
|
-
function loadImage(src) {
|
|
45152
|
-
return new Promise((res, rej) => {
|
|
45153
|
-
const image = new Image();
|
|
45154
|
-
image.onload = () => res(image);
|
|
45155
|
-
image.onerror = (e) => rej(e);
|
|
45156
|
-
image.src = src;
|
|
45157
|
-
});
|
|
45158
|
-
}
|
|
45159
|
-
|
|
45160
45660
|
var TextureFrameSeriesAnimator;
|
|
45161
45661
|
(function (TextureFrameSeriesAnimator) {
|
|
45162
45662
|
/**
|
|
@@ -49702,8 +50202,7 @@ async function resolveTextureValue(params) {
|
|
|
49702
50202
|
const cacheKey = "texture-" + url + "-" + (mask ? mask.type + "-" + mask.minColor + "-" + mask.maxColor : "none");
|
|
49703
50203
|
let prom = _textureCache.Get(cacheKey);
|
|
49704
50204
|
if (!prom) {
|
|
49705
|
-
prom = buildTintedTextureDataUri(url, mask)
|
|
49706
|
-
_textureCache.Set(cacheKey, prom);
|
|
50205
|
+
prom = cacheTexture(cacheKey, buildTintedTextureDataUri(url, mask));
|
|
49707
50206
|
}
|
|
49708
50207
|
return { dataUri: await prom, effective: textureTrace.effective };
|
|
49709
50208
|
}
|
|
@@ -49731,11 +50230,23 @@ async function resolveTextureCondition(params) {
|
|
|
49731
50230
|
const cacheKey = "texture-" + url + "-none";
|
|
49732
50231
|
let prom = _textureCache.Get(cacheKey);
|
|
49733
50232
|
if (!prom) {
|
|
49734
|
-
prom = buildTintedTextureDataUri(url)
|
|
49735
|
-
_textureCache.Set(cacheKey, prom);
|
|
50233
|
+
prom = cacheTexture(cacheKey, buildTintedTextureDataUri(url));
|
|
49736
50234
|
}
|
|
49737
50235
|
return { dataUri: await prom, effective: null };
|
|
49738
50236
|
}
|
|
50237
|
+
/*
|
|
50238
|
+
* Keeps a texture build for reuse, but drops a failed one so the texture is tried again next time.
|
|
50239
|
+
*/
|
|
50240
|
+
function cacheTexture(cacheKey, build) {
|
|
50241
|
+
const prom = build.catch(() => {
|
|
50242
|
+
if (_textureCache.Get(cacheKey) === prom) {
|
|
50243
|
+
_textureCache.Delete(cacheKey);
|
|
50244
|
+
}
|
|
50245
|
+
return null;
|
|
50246
|
+
});
|
|
50247
|
+
_textureCache.Set(cacheKey, prom);
|
|
50248
|
+
return prom;
|
|
50249
|
+
}
|
|
49739
50250
|
/**
|
|
49740
50251
|
* Fetches an image and, if a color mask is provided, remaps its (assumed grayscale) pixel values
|
|
49741
50252
|
* into a gradient between the mask's minColor/maxColor, preserving source alpha.
|
|
@@ -49743,16 +50254,7 @@ async function resolveTextureCondition(params) {
|
|
|
49743
50254
|
* @param mask
|
|
49744
50255
|
*/
|
|
49745
50256
|
async function buildTintedTextureDataUri(url, mask) {
|
|
49746
|
-
const
|
|
49747
|
-
const blob = await response.blob();
|
|
49748
|
-
const objectUrl = URL.createObjectURL(blob);
|
|
49749
|
-
let image;
|
|
49750
|
-
try {
|
|
49751
|
-
image = await loadImage(objectUrl);
|
|
49752
|
-
}
|
|
49753
|
-
finally {
|
|
49754
|
-
URL.revokeObjectURL(objectUrl);
|
|
49755
|
-
}
|
|
50257
|
+
const image = await loadImageForCanvas(url);
|
|
49756
50258
|
const canvas = document.createElement("canvas");
|
|
49757
50259
|
canvas.width = image.width;
|
|
49758
50260
|
canvas.height = image.height;
|
|
@@ -51310,7 +51812,7 @@ var StyleUtils;
|
|
|
51310
51812
|
StyleUtils.ApplyTypeStyle = ApplyTypeStyle;
|
|
51311
51813
|
})(StyleUtils || (StyleUtils = {}));
|
|
51312
51814
|
|
|
51313
|
-
const VERSION = "7.5.
|
|
51815
|
+
const VERSION = "7.5.6";
|
|
51314
51816
|
/**
|
|
51315
51817
|
* Updates the environment instance used by bruce-cesium to one specified.
|
|
51316
51818
|
* This can be used to ensure that the instance a parent is referencing is shared between bruce-cesium, bruce-models, and the parent app.
|