Fix missing font auto complete menu (#1491)

## Launch Checklist

This fixes the position of the autocomplete list - CSS fix mostly.
It also changes the loading of the metadata to be using promises instead
of callbacks.

I couldn't reproduce the following issue, so I'll be closing it for now.
- Fixes #945

Before:
<img width="563" height="577" alt="image"
src="https://github.com/user-attachments/assets/6ea264e1-cb85-4a8a-8df5-ab50e7815333"
/>

After:
<img width="563" height="577" alt="image"
src="https://github.com/user-attachments/assets/27fa2901-dd96-44fd-9774-363fd4c5ed98"
/>


 - [x] Briefly describe the changes in this PR.
 - [x] Link to related issues.
- [x] Include before/after visuals or gifs if this PR includes visual
changes.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Harel M
2025-11-06 16:31:14 +02:00
committed by GitHub
parent ce3953ea9c
commit 0fbba4b362
6 changed files with 95 additions and 33 deletions

View File

@@ -207,6 +207,20 @@ describe("layer editor", () => {
it("compound filter");
});
describe("layout", () => {
it("text-font", () => {
when.setStyle("font");
when.collapseGroupInLayerEditor();
when.collapseGroupInLayerEditor(1);
when.collapseGroupInLayerEditor(2);
when.doWithin("spec-field:text-font", () => {
get.element(".maputnik-autocomplete input").first().click();
});
then(get.element(".maputnik-autocomplete-menu-item")).shouldBeVisible();
then(get.element(".maputnik-autocomplete-menu-item")).shouldHaveLength(3);
});
});
describe("paint", () => {
it("expand/collapse");
it("color");

View File

@@ -85,6 +85,13 @@ export class MaputnikDriver {
fixture: "rectangles-style.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "example-style-with-fonts.json",
response: {
fixture: "example-style-with-fonts.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: "*example.local/*",
@@ -95,6 +102,11 @@ export class MaputnikDriver {
url: "*example.com/*",
response: [],
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: "https://www.glyph-server.com/*",
response: ["Font 1", "Font 2", "Font 3"],
});
},
};
@@ -114,7 +126,7 @@ export class MaputnikDriver {
this.helper.when.wait(200);
},
setStyle: (
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "",
styleProperties: "geojson" | "raster" | "both" | "layer" | "rectangles" | "font" | "",
zoom?: number
) => {
const url = new URL(baseUrl);
@@ -134,6 +146,9 @@ export class MaputnikDriver {
case "rectangles":
url.searchParams.set("style", baseUrl + "rectangles-style.json");
break;
case "font":
url.searchParams.set("style", baseUrl + "example-style-with-fonts.json");
break;
}
if (zoom) {

View File

@@ -0,0 +1,38 @@
{
"id": "test-style",
"version": 8,
"name": "Test Style",
"metadata": {
"maputnik:renderer": "mlgljs"
},
"sources": {
"example": {
"type": "geojson",
"data": {
"type": "FeatureCollection",
"features":[{
"type": "Feature",
"properties": {
"name": "Dinagat Islands"
},
"geometry":{
"type": "Point",
"coordinates": [125.6, 10.1]
}
}]
}
}
},
"glyphs": "https://www.glyph-server.com/fonts/{fontstack}/{range}.pbf",
"sprites": "https://example.local/fonts/{fontstack}/{range}.pbf",
"layers": [
{
"id": "label",
"type": "symbol",
"source": "example",
"layout": {
"text-font": ["Font"]
}
}
]
}

View File

@@ -294,13 +294,13 @@ export default class App extends React.Component<any, AppState> {
const accessToken = metadata["maputnik:openmaptiles_access_token"] || tokens.openmaptiles;
const glyphUrl = (typeof urlTemplate === "string")? urlTemplate.replace("{key}", accessToken): urlTemplate;
downloadGlyphsMetadata(glyphUrl, fonts => {
downloadGlyphsMetadata(glyphUrl).then(fonts => {
this.setState({ spec: updateRootSpec(this.state.spec, "glyphs", fonts)});
});
}
updateIcons(baseUrl: string) {
downloadSpriteMetadata(baseUrl, icons => {
downloadSpriteMetadata(baseUrl).then(icons => {
this.setState({ spec: updateRootSpec(this.state.spec, "sprite", icons)});
});
}

View File

@@ -1,44 +1,39 @@
import npmurl from "url";
function loadJSON(url: string, defaultValue: any, cb: (...args: any[]) => void) {
fetch(url, {
mode: "cors",
credentials: "same-origin"
})
.then((response) => {
if (!response.ok) {
throw new Error("Failed to load metadata for " + url);
}
return response.json();
})
.then((body) => {
cb(body);
})
.catch(() => {
console.warn("Can not load metadata for " + url + ", using default value " + defaultValue);
cb(defaultValue);
async function loadJSON<T>(url: string, defaultValue: T): Promise<T> {
try {
const response = await fetch(url, {
mode: "cors",
credentials: "same-origin"
});
if (!response.ok) {
throw new Error("Failed to load metadata for " + url);
}
return await response.json();
} catch {
console.warn("Can not load metadata for " + url + ", using default value " + defaultValue);
return defaultValue;
}
}
export function downloadGlyphsMetadata(urlTemplate: string, cb: (...args: any[]) => void) {
if(!urlTemplate) return cb([]);
export async function downloadGlyphsMetadata(urlTemplate: string): Promise<string[]> {
if(!urlTemplate) return [];
// Special handling because Tileserver GL serves the fontstacks metadata differently
// https://github.com/klokantech/tileserver-gl/pull/104#issuecomment-274444087
const urlObj = npmurl.parse(urlTemplate);
const normPathPart = "/%7Bfontstack%7D/%7Brange%7D.pbf";
const urlObj = new URL(urlTemplate);
const normPathPart = "/" + encodeURIComponent("{fontstack}") + "/" + encodeURIComponent("{range}") + ".pbf";
if(urlObj.pathname === normPathPart) {
urlObj.pathname = "/fontstacks.json";
} else {
urlObj.pathname = urlObj.pathname!.replace(normPathPart, ".json");
}
const url = npmurl.format(urlObj);
loadJSON(url, [], cb);
const url = urlObj.toString();
const fonts = await loadJSON(url, [] as string[]);
return [...new Set(fonts)];
}
export function downloadSpriteMetadata(baseUrl: string, cb: (...args: any[]) => void) {
if(!baseUrl) return cb([]);
export async function downloadSpriteMetadata(baseUrl: string): Promise<string[]> {
if(!baseUrl) return [];
const url = baseUrl + ".json";
loadJSON(url, {}, glyphs => cb(Object.keys(glyphs)));
const glyphs = await loadJSON(url, {} as Record<string, string>);
return Object.keys(glyphs);
}

View File

@@ -198,7 +198,7 @@
border: none;
padding: 2px 0;
margin-right: 10px;
position: absolute;
position: absolute !important;
overflow: auto;
max-height: 50%;
background: vars.$color-gray;