Skip to content

Commit

Permalink
Read MapBox API token at runtime (#11889)
Browse files Browse the repository at this point in the history
* Read mapbox API token at runtime, fix scaling issue

* Load styles the same way as scripts

* remove mapbox dependency

* Update changelog

(cherry picked from commit c2059be)
  • Loading branch information
vitvakatu authored and jdunkerley committed Dec 18, 2024
1 parent 518a0f5 commit e8ff34e
Show file tree
Hide file tree
Showing 8 changed files with 96 additions and 43 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Next Next Release

#### Enso IDE

- [ENSO_IDE_MAPBOX_API_TOKEN environment variable should be provided to enable
GeoMap visualization][11889].
- [Round ‘Add component’ button under the component menu replaced by a small
button protruding from the output port.][11836].

[11889]: https://github.com/enso-org/enso/pull/11889
[11836]: https://github.com/enso-org/enso/pull/11836

#### Enso Language & Runtime

- [Intersection types & type checks][11600]
Expand Down
1 change: 1 addition & 0 deletions app/gui/env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ declare global {
readonly projectManagementApi?: ProjectManagementApi
readonly fileBrowserApi?: FileBrowserApi
readonly versionInfo?: VersionInfo
readonly mapBoxApiToken?: () => string
toggleDevtools: () => void
/**
* If set to `true`, animations will be disabled.
Expand Down
2 changes: 1 addition & 1 deletion app/gui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
"@types/d3": "^7.4.0",
"@types/hash-sum": "^1.0.0",
"@types/jsdom": "^21.1.1",
"@types/mapbox-gl": "^2.7.13",
"@types/mapbox-gl": "^3.4.1",
"@types/shuffle-seed": "^1.1.0",
"@types/tar": "^6.1.4",
"@types/wicg-file-system-access": "^2023.10.2",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ export const defaultPreprocessor = [
] as const
export const scripts = [
// mapbox-gl does not have an ESM release.
'https://api.tiles.mapbox.com/mapbox-gl-js/v2.15.0/mapbox-gl.js',
'https://api.tiles.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.js',
// The deck.gl scripting API is not available in the ESM module.
'https://cdn.jsdelivr.net/npm/deck.gl@8.9.27/dist.min.js',
'https://cdn.jsdelivr.net/npm/deck.gl@9.0.38/dist.min.js',
]
export const styles = ['https://api.tiles.mapbox.com/mapbox-gl-js/v3.8.0/mapbox-gl.css']
/**
* Provides a mapbox & deck.gl-based map visualization.
Expand Down Expand Up @@ -100,8 +101,8 @@ const props = defineProps<{ data: Data }>()
* Mapbox API access token.
* All the limits of API are listed here: https://docs.mapbox.com/api/#rate-limits
*/
const TOKEN = import.meta.env.ENSO_IDE_MAPBOX_API_TOKEN
if (TOKEN == null) {
const TOKEN = window.mapBoxApiToken?.()
if (!TOKEN) {
console.warn(
'Mapbox API token is missing, to use Geo Map visualization please provide ENSO_IDE_MAPBOX_API_TOKEN.',
)
Expand Down Expand Up @@ -170,9 +171,15 @@ function updateState(data: Data) {
}
const center = centerPoint()
function safeOrDefault(value: number | undefined, defaultValue: number): number {
if (value === 0 || value == null || Number.isNaN(value)) return defaultValue
return value
}
const width = safeOrDefault(mapNode.value?.clientWidth, 600)
const height = safeOrDefault(mapNode.value?.clientHeight, 400)
const viewPort = new deck.WebMercatorViewport({
width: mapNode.value?.clientWidth ?? 600,
height: mapNode.value?.clientHeight ?? 400,
width,
height,
longitude: center.longitude,
latitude: center.latitude,
zoom: DEFAULT_MAP_ZOOM,
Expand Down Expand Up @@ -229,6 +236,16 @@ function initDeckGl() {
initialViewState: viewState.value,
controller: controller.value,
}) as any
// Hotfix for https://github.com/mapbox/mapbox-gl-js/issues/13355
;(deckgl.value as any)._map.map._updateContainerDimensions = function () {
if (!this._container) return
const width = this._container.offsetWidth || 400
const height = this._container.offsetHeight || 300
this._containerWidth = width
this._containerHeight = height
}
} catch (error) {
console.error(error)
resetState()
Expand Down
79 changes: 48 additions & 31 deletions app/gui/src/project-view/stores/visualization/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
type VisualizationId,
} from '@/stores/visualization/metadata'
import type { VisualizationModule } from '@/stores/visualization/runtimeTypes'
import { assert } from '@/util/assert'
import type { Opt } from '@/util/data/opt'
import { isUrlString } from '@/util/data/urlString'
import { isIconName } from '@/util/iconName'
Expand Down Expand Up @@ -103,40 +104,47 @@ export const [provideVisualizationStore, useVisualizationStore] = createContextS
})
}

const loadedScripts = new Set<string>()
const loadedStyles = new Set<string>()
const scriptsNode = document.head.appendChild(document.createElement('div'))
scriptsNode.classList.add('visualization-scripts')
const loadedScripts = new Set<string>()
function loadScripts(module: VisualizationModule) {

function loadAsync(
urls: string[],
type: 'scripts' | 'styles',
container: HTMLElement,
loaded: Set<string>,
) {
const promises: Promise<void>[] = []
if ('scripts' in module && module.scripts) {
if (!Array.isArray(module.scripts)) {
console.warn('Visualiation scripts should be an array:', module.scripts)
}
const scripts = Array.isArray(module.scripts) ? module.scripts : [module.scripts]
for (const url of scripts) {
if (typeof url !== 'string') {
console.warn('Visualization script should be a string, skipping URL:', url)
} else if (!loadedScripts.has(url)) {
loadedScripts.add(url)
const node = document.createElement('script')
for (const url of urls) {
if (!loaded.has(url)) {
loaded.add(url)
const nodeKind = type === 'scripts' ? 'script' : 'link'
const node = document.createElement(nodeKind)
if (type === 'styles') {
assert(node instanceof HTMLLinkElement)
node.href = url
node.rel = 'stylesheet'
} else if (type === 'scripts') {
assert(node instanceof HTMLScriptElement)
node.src = url
// Some resources still set only "Access-Control-Allow-Origin" in the response.
// We need to explicitly make a request CORS - see https://resourcepolicy.fyi
node.crossOrigin = 'anonymous'
promises.push(
new Promise<void>((resolve, reject) => {
node.addEventListener('load', () => {
resolve()
node.remove()
})
node.addEventListener('error', () => {
reject()
node.remove()
})
}),
)
scriptsNode.appendChild(node)
}
// Some resources still set only "Access-Control-Allow-Origin" in the response.
// We need to explicitly make a request CORS - see https://resourcepolicy.fyi
node.crossOrigin = 'anonymous'
promises.push(
new Promise<void>((resolve, reject) => {
node.addEventListener('load', () => {
resolve()
node.remove()
})
node.addEventListener('error', () => {
reject()
node.remove()
})
}),
)
container.appendChild(node)
}
}
return Promise.allSettled(promises)
Expand Down Expand Up @@ -168,7 +176,12 @@ export const [provideVisualizationStore, useVisualizationStore] = createContextS
await projectRoot,
await proj.dataConnection,
).then(async (viz) => {
await loadScripts(viz)
const styles = viz.styles ?? []
const scripts = viz.scripts ?? []
await Promise.allSettled([
loadAsync(styles, 'styles', document.head, loadedStyles),
loadAsync(scripts, 'scripts', scriptsNode, loadedScripts),
])
return viz
})
if (key) cache.set(key, vizPromise)
Expand Down Expand Up @@ -279,7 +292,11 @@ export const [provideVisualizationStore, useVisualizationStore] = createContextS
async function resolveBuiltinVisualization(type: string) {
const module = builtinVisualizationsByName[type]
if (!module) throw new Error(`Unknown visualization type: ${type}`)
await loadScripts(module)
const { scripts, styles } = module
await Promise.allSettled([
loadAsync(styles ?? [], 'styles', document.head, loadedStyles),
loadAsync(scripts ?? [], 'scripts', scriptsNode, loadedScripts),
])
return module
}

Expand Down
1 change: 1 addition & 0 deletions app/ide-desktop/client/src/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ declare global {
readonly fileBrowserApi?: FileBrowserApi
readonly projectManagementApi?: ProjectManagementApi
readonly versionInfo?: VersionInfo
readonly mapBoxApiToken: () => string
toggleDevtools: () => void
}

Expand Down
7 changes: 7 additions & 0 deletions app/ide-desktop/client/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const NAVIGATION_API_KEY = 'navigationApi'
const MENU_API_KEY = 'menuApi'
const SYSTEM_API_KEY = 'systemApi'
const VERSION_INFO_KEY = 'versionInfo'
const MAPBOX_API_TOKEN_KEY = 'mapBoxApiToken'

// =========================
// === exposeInMainWorld ===
Expand Down Expand Up @@ -205,3 +206,9 @@ exposeInMainWorld(SYSTEM_API_KEY, {
// ====================

exposeInMainWorld(VERSION_INFO_KEY, debug.VERSION_INFO)

// ==================
// === MapBox API ===
// ==================

exposeInMainWorld(MAPBOX_API_TOKEN_KEY, () => process.env.ENSO_IDE_MAPBOX_API_TOKEN || '')
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit e8ff34e

Please sign in to comment.