Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### ✨ Features and improvements
- Added translation to "Links" in debug modal
- Added bounds field and moved its Cypress coverage into the sources modal spec.
- Add support for hillshade's color arrays and relief-color elevation expression
- Change layers icons to make them a bit more distinct
- Remove `@mdi` packages in favor of `react-icons`
Expand Down
3 changes: 3 additions & 0 deletions cypress/e2e/maputnik-cypress-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ export default class MaputnikCypressHelper {
}
});
},
typeIntoWrappedInput: ($inputs: JQuery<HTMLElement>, index: number, value: string) => {
cy.wrap($inputs[index]).type("{selectall}" + value, { force: true });
},
dropFileByFixture: (fixture: string, dropzoneTestId: string) => {
this.helper.get.elementByTestId(dropzoneTestId).selectFile("cypress/fixtures/" + fixture, {
action: "drag-drop",
Expand Down
32 changes: 29 additions & 3 deletions cypress/e2e/maputnik-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ export class MaputnikDriver {
fixture: "rectangles-style.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "style-with-bounds.json",
response: {
fixture: "style-with-bounds.json",
},
});
this.helper.given.interceptAndMockResponse({
method: "GET",
url: baseUrl + "example-style-with-fonts.json",
Expand Down Expand Up @@ -220,16 +227,35 @@ export class MaputnikDriver {
.type(text, { parseSpecialCharSequences: false });
},

// Sets the first value in a property array (index 0).
// Use this to initialize the first element of an array field.
// Typically used before calling addValueToPropertyArray for subsequent values.
setValueToPropertyArray: (selector: string, value: string) => {
this.when.doWithin(selector, () => {
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
const inputs = this.helper.get.element("input");
inputs.then(($inputs) => {
Comment thread
HarelM marked this conversation as resolved.
this.helper.when.typeIntoWrappedInput($inputs, 0, value);
});
});
},

// Adds a value to the first empty input in a property array.
// Searches through all inputs and fills the first empty one.
// Use this to sequentially populate array fields after initialization.
addValueToPropertyArray: (selector: string, value: string) => {
this.when.doWithin(selector, () => {
this.helper.get.element(".maputnik-array-add-value").click({ force: true });
this.helper.get.element(".maputnik-array-block-content input").last().type("{selectall}"+value, {force: true });
const inputs = this.helper.get.element("input");
inputs.then(($inputs) => {
let targetIndex = 0;
Comment thread
HarelM marked this conversation as resolved.
for (let i = 0; i < $inputs.length; i++) {
const input = $inputs[i] as HTMLInputElement;
if (!input.value) {
targetIndex = i;
break;
}
}
this.helper.when.typeIntoWrappedInput($inputs, targetIndex, value);
});
});
},

Expand Down
105 changes: 105 additions & 0 deletions cypress/e2e/modals.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,111 @@ describe("modals", () => {
tileSize: 128,
});
});

describe("bounds field feature", () => {
describe("when loading a style with bounds set", () => {
it("should display all four bounds values correctly in the source editor", () => {
when.click("nav:open");
when.setValue("modal:open.url.input", "http://localhost:8888/style-with-bounds.json");
when.click("modal:open.url.button");
when.wait(200);

then(get.styleFromLocalStorage().then((style) => style.sources["bounded-vector"].bounds)).shouldDeepNestedInclude([-180, -85.051129, 180, 85.051129]);
then(get.styleFromLocalStorage().then((style) => style.sources["bounded-raster"].bounds)).shouldDeepNestedInclude([-122.4, 37.7, -122.3, 37.8]);
});
});

describe("when loading a style without bounds", () => {
it("should have empty bounds fields and not crash the UI", () => {
const sourceId = "test-no-bounds";
when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "tile_vector");
when.select("modal:sources.add.scheme_type", "xyz");
when.wait(100);
when.click("modal:sources.add.add_source");
when.wait(200);

const hasEmptyBounds = get.styleFromLocalStorage().then((style) => {
const source = style.sources[sourceId];
if (!source) return true;
return source.bounds === undefined || source.bounds.length === 0;
});

then(hasEmptyBounds).shouldEqual(true);
then(get.elementByTestId("modal:sources")).shouldBeVisible();
});
});

describe("when editing bounds in the UI", () => {
it("should reflect bounds changes in the JSON", () => {
const sourceId = "bounds-edit-test";
when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "tile_raster");
when.select("modal:sources.add.scheme_type", "xyz");
when.setValue("modal:sources.add.tile_size", "256");
when.wait(200);

when.setValueToPropertyArray("modal:sources.add.bounds", "-180");
when.addValueToPropertyArray("modal:sources.add.bounds", "-85.051129");
when.addValueToPropertyArray("modal:sources.add.bounds", "180");
when.addValueToPropertyArray("modal:sources.add.bounds", "85.051129");
when.wait(100);

when.click("modal:sources.add.add_source");
when.wait(200);

then(
get.styleFromLocalStorage().then((style) => style.sources[sourceId].bounds)
).shouldDeepNestedInclude([-180, -85.051129, 180, 85.051129]);
});
});

describe("bounds field validation", () => {
it("should accept valid longitude/latitude bounds values", () => {
const sourceId = "valid-bounds-test";
when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "tile_vector");
when.select("modal:sources.add.scheme_type", "xyz");
when.wait(200);

when.setValueToPropertyArray("modal:sources.add.bounds", "-125.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "25.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "-66.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "49.0");
when.wait(100);

when.click("modal:sources.add.add_source");
when.wait(200);

then(
get.styleFromLocalStorage().then((style) => style.sources[sourceId].bounds)
).shouldDeepNestedInclude([-125.0, 25.0, -66.0, 49.0]);
});
});

describe("tile url source with bounds", () => {
it("should support bounds on TileURL sources", () => {
const sourceId = "tileurl-bounds-test";
when.setValue("modal:sources.add.source_id", sourceId);
when.select("modal:sources.add.source_type", "tile_vector");
when.select("modal:sources.add.scheme_type", "xyz");
when.wait(200);

when.setValueToPropertyArray("modal:sources.add.bounds", "-10.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "-10.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "10.0");
when.addValueToPropertyArray("modal:sources.add.bounds", "10.0");
when.wait(100);

when.click("modal:sources.add.add_source");
when.wait(200);

then(
get.styleFromLocalStorage().then((style) => style.sources[sourceId].bounds)
).shouldDeepNestedInclude([-10.0, -10.0, 10.0, 10.0]);
});
});
});
});

describe("inspect", () => {
Expand Down
30 changes: 30 additions & 0 deletions cypress/fixtures/style-with-bounds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"id": "style-with-bounds",
"version": 8,
"name": "Style with Bounds",
"metadata": {
"maputnik:renderer": "mlgljs"
},
"sources": {
"bounded-vector": {
"type": "vector",
"tiles": ["https://example.com/{z}/{x}/{y}.pbf"],
"minzoom": 0,
"maxzoom": 14,
"bounds": [-180, -85.051129, 180, 85.051129]
},
"bounded-raster": {
"type": "raster",
"tiles": ["https://example.com/{z}/{x}/{y}.png"],
"tileSize": 256,
"bounds": [-122.4, 37.7, -122.3, 37.8]
}
},
"glyphs": "https://example.local/fonts/{fontstack}/{range}.pbf",
"layers": [
{
"id": "background",
"type": "background"
}
]
}
3 changes: 2 additions & 1 deletion src/components/InputArray.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type InputArrayProps = {
onChange?(value: (string | number | undefined)[] | undefined): unknown
"aria-label"?: string
label?: string
"data-wd-key"?: string
};

type InputArrayState = {
Expand Down Expand Up @@ -109,7 +110,7 @@ export default class InputArray extends React.Component<InputArrayProps, InputAr
});

return (
<div className="maputnik-array">
<div className="maputnik-array" data-wd-key={this.props["data-wd-key"]}>
{inputs}
</div>
);
Expand Down
14 changes: 14 additions & 0 deletions src/components/modals/ModalSourcesTypeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type TileURLSourceEditorProps = {
minzoom: number
maxzoom: number
scheme: "xyz" | "tms"
bounds?: [number, number, number, number]
}
onChange(...args: unknown[]): unknown
children?: React.ReactNode
Expand Down Expand Up @@ -108,6 +109,19 @@ class TileURLSourceEditor extends React.Component<TileURLSourceEditorProps> {
maxzoom: maxzoom
})}
/>
<FieldArray
label={t("Bounds")}
fieldSpec={latest.source_vector.bounds}
length={4}
type="number"
value={this.props.source.bounds || []}
default={[]}
onChange={(bounds: [number, number, number, number]) => this.props.onChange({
...this.props.source,
bounds: bounds
})}
data-wd-key="modal:sources.add.bounds"
/>
{this.props.children}
</div>;

Expand Down
1 change: 1 addition & 0 deletions src/locales/de/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "API-Schlüssel für Stadia Maps.",
"Base": "Basis",
"Bearing": "Ausrichtung",
"Bounds": "Grenzen",
"Cancel": "Abbrechen",
"Center": "Mittelpunkt",
"Choose Public Source": "Öffentliche Quelle auswählen",
Expand Down
1 change: 1 addition & 0 deletions src/locales/fr/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "Clé d'API pour Stadia Maps.",
"Base": "Base",
"Bearing": "Orientation",
"Bounds": "Limites",
"Cancel": "Annuler",
"Center": "Centre",
"Choose Public Source": "Choisir une source publique",
Expand Down
1 change: 1 addition & 0 deletions src/locales/he/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "API key for Stadia Maps",
"Base": "בסיס",
"Bearing": "כיוון",
"Bounds": "גבולות",
"Cancel": "ביטול",
"Center": "מרכז",
"Choose Public Source": "בחירת מקור ציבורי",
Expand Down
1 change: 1 addition & 0 deletions src/locales/it/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "Chiave API per Stadia Maps.",
"Base": "Base",
"Bearing": "Direzione",
"Bounds": "Delimitazione",
"Cancel": "Annulla",
"Center": "Centro",
"Choose Public Source": "Scegli una sorgente pubblica",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ja/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "Stadia Maps の API キー",
"Base": "ベース",
"Bearing": "方位",
"Bounds": "範囲",
"Cancel": "キャンセル",
"Center": "中央",
"Choose Public Source": "公開ソースから選択",
Expand Down
1 change: 1 addition & 0 deletions src/locales/ko/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "Stadia Maps용 API 키 입니다.",
"Base": "베이스",
"Bearing": "방위각",
"Bounds": "경계",
"Cancel": "취소",
"Center": "중심 좌표",
"Choose Public Source": "공개 소스 선택",
Expand Down
1 change: 1 addition & 0 deletions src/locales/zh/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"API key for Stadia Maps.": "Stadia Maps 的 API 密钥",
"Base": "基础",
"Bearing": "方位",
"Bounds": "边界",
"Cancel": "取消",
"Center": "中心",
"Choose Public Source": "选择公共源",
Expand Down
Loading