@unvt/charites 0.2.0 → 0.3.0

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.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What is it?
4
4
 
5
- - DevContainer is a Dockerfike and configuration file for remote development in Visual Studio Code or GitHub Codespaces
5
+ - DevContainer is a Dockerfile and a configuration file for remote development in Visual Studio Code or GitHub Codespaces
6
6
  - DevContainer allows development environments to be written and maintained as code
7
7
  - DevContainer allows everyone to develop in a common development environment
8
8
 
@@ -18,7 +18,7 @@ Please describe what you changed briefly.
18
18
  - [ ] No build errors after `npm run build`
19
19
  - [ ] No lint errors after `npm run lint`
20
20
  - [ ] No errors on using `charites help` globally
21
- - [ ] Make sure all the exsiting features working well
21
+ - [ ] Make sure all the existing features working well
22
22
  - [ ] Have you added at least one unit test if you are going to add new feature?
23
23
  - [ ] Have you updated documentation?
24
24
  <!-- ignore-task-list-end -->
@@ -0,0 +1,14 @@
1
+ changelog:
2
+ exclude:
3
+ labels:
4
+ - ignore-for-release
5
+ categories:
6
+ - title: Breaking Changes 🛠
7
+ labels:
8
+ - breaking-change
9
+ - title: Exciting New Features 🎉
10
+ labels:
11
+ - enhancement
12
+ - title: Other Changes
13
+ labels:
14
+ - '*'
@@ -15,11 +15,9 @@ on:
15
15
  jobs:
16
16
  build:
17
17
  runs-on: ubuntu-latest
18
-
19
18
  strategy:
20
19
  matrix:
21
20
  node-version: [14.x, 16.x, 18.x]
22
-
23
21
  steps:
24
22
  - uses: actions/checkout@v2
25
23
  - name: Use Node.js ${{ matrix.node-version }}
@@ -30,6 +28,33 @@ jobs:
30
28
  - run: npm run lint
31
29
  - run: npm test
32
30
 
31
+ e2e:
32
+ runs-on: ubuntu-latest
33
+ strategy:
34
+ matrix:
35
+ node-version: [18.x]
36
+ steps:
37
+ - uses: actions/checkout@v2
38
+ - name: Use Node.js ${{ matrix.node-version }}
39
+ uses: actions/setup-node@v1
40
+ with:
41
+ node-version: ${{ matrix.node-version }}
42
+ - name: Check and restore cache of playwright
43
+ uses: actions/cache@v3
44
+ id: playwright-cache
45
+ with:
46
+ path: |
47
+ ~/.cache/ms-playwright
48
+ key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
49
+ - run: npm install
50
+ - run: npx playwright install --with-deps chromium
51
+ if: steps.playwright-cache.outputs.cache-hit != 'true'
52
+ - run: npm run build
53
+ - run: chmod 777 dist/cli.js
54
+ - run: npm run test:e2e
55
+ env:
56
+ MAPBOX_ACCESS_TOKEN: ${{ secrets.MAPBOX_ACCESS_TOKEN }}
57
+
33
58
  publish:
34
59
  name: 'Publish npm package'
35
60
  runs-on: ubuntu-latest
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2021 The United Nations Vector Tile Toolkit
3
+ Copyright (c) 2022 The United Nations Vector Tile Toolkit
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/dist/cli/serve.js CHANGED
@@ -14,7 +14,7 @@ program
14
14
  .arguments('<source>')
15
15
  .description('serve your map locally')
16
16
  .option('--provider [provider]', 'your map service. e.g. `mapbox`, `geolonia`')
17
- .option('--mapbox-access-token [mapboxAccessToken]', 'Access Token for the Mapbox')
17
+ .option('--mapbox-access-token [mapboxAccessToken]', 'Your Mapbox Access Token (required if using the `mapbox` provider)')
18
18
  .option('--port [port]', 'Specify custom port')
19
19
  .action((source, serveOptions) => {
20
20
  const options = program.opts();
@@ -30,14 +30,19 @@ function serve(source, options) {
30
30
  if (!fs_1.default.existsSync(sourcePath)) {
31
31
  throw `${sourcePath}: No such file or directory`;
32
32
  }
33
+ const mapboxAccessToken = options.mapboxAccessToken || defaultValues_1.defaultValues.mapboxAccessToken;
34
+ if (provider === 'mapbox' && !mapboxAccessToken) {
35
+ throw `Provider is mapbox, but the Mapbox Access Token is not set. Please provide it using --mapbox-access-token, or set it in \`~/.charites/config.yml\` (see the Global configuration section of the documentation for more information)`;
36
+ }
33
37
  const server = http_1.default.createServer((req, res) => {
34
38
  const url = (req.url || '').replace(/\?.*/, '');
35
- const dir = path_1.default.join(defaultValues_1.defaultValues.providerDir, provider);
39
+ const defaultProviderDir = path_1.default.join(defaultValues_1.defaultValues.providerDir, 'default');
40
+ const providerDir = path_1.default.join(defaultValues_1.defaultValues.providerDir, provider);
36
41
  switch (url) {
37
42
  case '/':
38
43
  res.statusCode = 200;
39
44
  res.setHeader('Content-Type', 'text/html; charset=UTF-8');
40
- const content = fs_1.default.readFileSync(path_1.default.join(dir, 'index.html'), 'utf-8');
45
+ const content = fs_1.default.readFileSync(path_1.default.join(providerDir, 'index.html'), 'utf-8');
41
46
  res.end(content);
42
47
  break;
43
48
  case '/style.json':
@@ -56,14 +61,21 @@ function serve(source, options) {
56
61
  case '/app.css':
57
62
  res.statusCode = 200;
58
63
  res.setHeader('Content-Type', 'text/css; charset=UTF-8');
59
- const css = fs_1.default.readFileSync(path_1.default.join(dir, 'app.css'), 'utf-8');
64
+ const css = fs_1.default.readFileSync(path_1.default.join(defaultProviderDir, 'app.css'), 'utf-8');
60
65
  res.end(css);
61
66
  break;
67
+ case `/shared.js`:
68
+ res.statusCode = 200;
69
+ res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
70
+ const shared = fs_1.default.readFileSync(path_1.default.join(defaultProviderDir, 'shared.js'), 'utf-8');
71
+ const js = shared.replace('___PORT___', `${port}`);
72
+ res.end(js);
73
+ break;
62
74
  case `/app.js`:
63
75
  res.statusCode = 200;
64
76
  res.setHeader('Content-Type', 'application/javascript; charset=UTF-8');
65
77
  try {
66
- const app = fs_1.default.readFileSync(path_1.default.join(dir, 'app.js'), 'utf-8');
78
+ const app = fs_1.default.readFileSync(path_1.default.join(providerDir, 'app.js'), 'utf-8');
67
79
  const js = app
68
80
  .replace('___PORT___', `${port}`)
69
81
  .replace('___MAPBOX_ACCESS_TOKEN___', `${options.mapboxAccessToken || defaultValues_1.defaultValues.mapboxAccessToken}`);
@@ -35,11 +35,14 @@ const writeDecompositedYaml = (destinationPath, style) => {
35
35
  const layer = style.layers[i];
36
36
  const layerYml = js_yaml_1.default.dump(layer);
37
37
  const fileName = `${style.layers[i].id}.yml`;
38
- const dirName = path_1.default.join(path_1.default.dirname(destinationPath), 'layers');
39
- fs_1.default.mkdirSync(dirName, { recursive: true });
40
- fs_1.default.writeFileSync(path_1.default.join(dirName, fileName), layerYml);
38
+ const layersDirName = path_1.default.join(path_1.default.dirname(destinationPath), 'layers');
39
+ const filePath = path_1.default.join(layersDirName, fileName);
40
+ fs_1.default.mkdirSync(path_1.default.dirname(filePath), { recursive: true });
41
+ fs_1.default.writeFileSync(filePath, layerYml);
42
+ // ts-ignore is required here because !!inc/file string is not compatible with the Layer object type.
43
+ // We use path.posix.join to make sure the path uses / path separators, even when run on Windows.
41
44
  // @ts-ignore
42
- layers.push(`!!inc/file ${path_1.default.join('layers', fileName)}`);
45
+ layers.push(`!!inc/file ${path_1.default.posix.join('layers', fileName)}`);
43
46
  }
44
47
  style.layers = layers;
45
48
  fs_1.default.writeFileSync(destinationPath, js_yaml_1.default.dump(style).replace(/'\!\!inc\/file layers\/.+\.yml'/g, function (match) {
@@ -18,11 +18,11 @@
18
18
  # -- Project information -----------------------------------------------------
19
19
 
20
20
  project = 'charites'
21
- copyright = '2021, The United Nations Vector Tile Toolkit'
21
+ copyright = '2022, The United Nations Vector Tile Toolkit'
22
22
  author = 'Jin Igarashi'
23
23
 
24
24
  # The full version, including alpha/beta/rc tags
25
- release = '0.1.2'
25
+ release = '0.2.0'
26
26
 
27
27
 
28
28
  # -- General configuration ---------------------------------------------------
@@ -66,4 +66,4 @@ html_context = {
66
66
  "github_repo": "charites", # Repo name
67
67
  "github_version": "main", # Version
68
68
  "conf_py_path": "/docs/source/", # Path in the checkout to the docs root
69
- }
69
+ }
@@ -6,10 +6,10 @@
6
6
  Charites - Documentation
7
7
  ====================================
8
8
 
9
- :Date: 2021-12-08
9
+ :Date: 2022-11-16
10
10
  :Copyright: CC-BY-SA
11
11
  :Organization: The United Nations Vector Tile Toolkit
12
- :Version: 0.1.2
12
+ :Version: 0.2.0
13
13
  :Abstract: This document contains the complete documentation of Charites, an application to style vector tiles easily
14
14
 
15
15
  .. meta::
@@ -92,11 +92,11 @@ Realtime editor on browser
92
92
 
93
93
  Charites has three options for `serve` command.
94
94
 
95
- - `--provider` - `mapbox`, `geolonia`, or `default`. When not specified, default or the value in the configuration file will be used.
95
+ - ``--provider`` - `mapbox`, `geolonia`, or `default`. When not specified, default or the value in the configuration file will be used.
96
96
 
97
97
  - `mapbox` - The format linter runs against the Mapbox GL JS v2.x compatible specification.
98
98
  - `geolonia` and `default` - the format linter runs against the MapLibre GL JS compatible specification.
99
99
 
100
- - `--mapbox-access-token` - Set your access-token when styling for Mapbox.
100
+ - ``--mapbox-access-token`` - Set your access-token when styling for Mapbox.
101
101
 
102
- - `--port` - Set http server's port number. When not specified, use 8080 port.
102
+ - ``--port`` - Set http server's port number. When not specified, use 8080 port.
@@ -29,14 +29,14 @@ Build `style.json` from `style.yml`:
29
29
 
30
30
  charites build style.yml style.json
31
31
 
32
- Add `-c` or `--compact-output` to minify the JSON
32
+ Add ``-c`` or ``--compact-output`` to minify the JSON
33
33
 
34
34
  .. code-block:: bash
35
35
 
36
36
  charites build style.yml style.json -c
37
37
 
38
38
 
39
- Add `--sprite-input` and `--sprite-output` to build svg files for map icons.
39
+ Add ``--sprite-input`` and ``--sprite-output`` to build svg files for map icons.
40
40
 
41
41
  .. code-block:: bash
42
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unvt/charites",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "charites": "dist/cli.js"
@@ -11,6 +11,7 @@
11
11
  "lint": "eslint --fix .",
12
12
  "test": "npm run build && mocha -r ts-node/register test/*.ts",
13
13
  "test:watch": "npm test -- --watch --watch-files src/**/*.ts --watch-files test/**/*.ts",
14
+ "test:e2e": "npx playwright test",
14
15
  "command": "./node_modules/.bin/ts-node ./src/cli.ts"
15
16
  },
16
17
  "author": "",
@@ -18,7 +19,7 @@
18
19
  "dependencies": {
19
20
  "@mapbox/mapbox-gl-style-spec": "^13.22.0",
20
21
  "@maplibre/maplibre-gl-style-spec": "^14.0.2",
21
- "@unvt/sprite-one": "^0.0.6",
22
+ "@unvt/sprite-one": "^0.0.8",
22
23
  "@types/jsonminify": "^0.4.1",
23
24
  "axios": "^0.24.0",
24
25
  "commander": "^8.2.0",
@@ -31,7 +32,9 @@
31
32
  "yaml-include": "^1.2.1"
32
33
  },
33
34
  "devDependencies": {
35
+ "@playwright/test": "^1.28.1",
34
36
  "@types/chai": "^4.2.11",
37
+ "@types/chai-as-promised": "^7.1.5",
35
38
  "@types/fs-extra": "^9.0.13",
36
39
  "@types/js-yaml": "^4.0.3",
37
40
  "@types/mocha": "^7.0.2",
@@ -40,6 +43,7 @@
40
43
  "@typescript-eslint/eslint-plugin": "^4.16.1",
41
44
  "@typescript-eslint/parser": "^4.16.1",
42
45
  "chai": "^4.2.0",
46
+ "chai-as-promised": "^7.1.1",
43
47
  "eslint": "^7.21.0",
44
48
  "eslint-config-prettier": "^6.15.0",
45
49
  "eslint-plugin-prettier": "^3.3.1",
@@ -0,0 +1,29 @@
1
+ // playwright.config.ts
2
+ import type { PlaywrightTestConfig } from '@playwright/test'
3
+
4
+ const config: PlaywrightTestConfig = {
5
+ testDir: 'test/playwright',
6
+ use: {
7
+ browserName: 'chromium',
8
+ headless: true,
9
+ },
10
+ workers: 3,
11
+ webServer: [
12
+ {
13
+ command: 'dist/cli.js serve test/data/style.yml --port 8080',
14
+ port: 8080,
15
+ },
16
+ {
17
+ command:
18
+ 'dist/cli.js serve test/data/style.yml --provider geolonia --port 8088',
19
+ port: 8088,
20
+ },
21
+ {
22
+ command:
23
+ 'dist/cli.js serve test/data/style.yml --provider mapbox --port 8888 --mapbox-access-token ' +
24
+ process.env.MAPBOX_ACCESS_TOKEN,
25
+ port: 8888,
26
+ },
27
+ ],
28
+ }
29
+ export default config
@@ -1,50 +1,32 @@
1
- const map = new maplibregl.Map({
2
- container: 'map',
3
- hash: true,
4
- style: `http://${window.location.host}/style.json`,
5
- })
1
+ ;(async () => {
2
+ const { style, center, zoom } = await window._charites.parseMapStyle()
3
+ const options = {
4
+ container: 'map',
5
+ hash: true,
6
+ style,
7
+ }
8
+ if (center) options.center = center
9
+ if (zoom) options.zoom = zoom
10
+ const map = new maplibregl.Map(options)
6
11
 
7
- const socket = new WebSocket('ws://localhost:___PORT___')
12
+ window._charites.initializeWebSocket((message) => {
13
+ map.setStyle(JSON.parse(message.data))
14
+ })
8
15
 
9
- socket.addEventListener('message', (message) => {
10
- map.setStyle(JSON.parse(message.data))
11
- })
16
+ map.addControl(new maplibregl.NavigationControl(), 'top-right')
12
17
 
13
- map.addControl(new maplibregl.NavigationControl(), 'top-right')
18
+ map.addControl(
19
+ new MaplibreLegendControl(
20
+ {},
21
+ {
22
+ showDefault: true,
23
+ showCheckbox: true,
24
+ onlyRendered: true,
25
+ reverseOrder: true,
26
+ },
27
+ ),
28
+ 'bottom-left',
29
+ )
14
30
 
15
- map.addControl(
16
- new watergis.MaplibreLegendControl(
17
- {},
18
- {
19
- showDefault: true,
20
- showCheckbox: true,
21
- onlyRendered: true,
22
- reverseOrder: true,
23
- },
24
- ),
25
- 'bottom-left',
26
- )
27
-
28
- const showTileBoundaries = document.getElementById('showTileBoundaries')
29
- const setShowTileBoundaries = function () {
30
- const checked = showTileBoundaries.checked
31
- map.showTileBoundaries = checked
32
- }
33
- setShowTileBoundaries()
34
- showTileBoundaries.addEventListener('click', setShowTileBoundaries)
35
-
36
- const showCollisionBoxes = document.getElementById('showCollisionBoxes')
37
- const setShowCollisionBoxes = function () {
38
- const checked = showCollisionBoxes.checked
39
- map.showCollisionBoxes = checked
40
- }
41
- setShowCollisionBoxes()
42
- showCollisionBoxes.addEventListener('click', setShowCollisionBoxes)
43
-
44
- const showPadding = document.getElementById('showPadding')
45
- const setShowPadding = function () {
46
- const checked = showPadding.checked
47
- map.showPadding = checked
48
- }
49
- setShowPadding()
50
- showPadding.addEventListener('click', setShowPadding)
31
+ window._charites.setupDebugCheckboxes(map)
32
+ })()
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html id="charites-default">
3
3
  <head>
4
4
  <link rel="stylesheet" href="/app.css" />
5
5
  <link href='https://unpkg.com/maplibre-gl@1.15.2/dist/maplibre-gl.css' rel='stylesheet' />
@@ -17,6 +17,7 @@
17
17
  <label><input type="checkbox" id="showPadding">show Padding</label>
18
18
  </div>
19
19
  <div id="map"></div>
20
+ <script type="text/javascript" src="/shared.js"></script>
20
21
  <script type="text/javascript" src="/app.js"></script>
21
22
  </body>
22
23
  </html>
@@ -0,0 +1,67 @@
1
+ ;(async () => {
2
+ window._charites = {
3
+ initializeWebSocket: function (onmessage) {
4
+ const socket = new WebSocket(`ws://${window.location.host}`)
5
+
6
+ socket.addEventListener('message', onmessage)
7
+ },
8
+ parseMapStyle: async function () {
9
+ const mapStyleUrl = `http://${window.location.host}/style.json`
10
+ const mapStyleRes = await fetch(mapStyleUrl)
11
+ const mapStyleJson = await mapStyleRes.json()
12
+
13
+ // detect center & zoom from map style json
14
+ let center = mapStyleJson.hasOwnProperty('center')
15
+ ? mapStyleJson.center
16
+ : undefined
17
+ let zoom = mapStyleJson.hasOwnProperty('zoom')
18
+ ? mapStyleJson.zoom
19
+ : undefined
20
+
21
+ // detect center & zoom from tile json
22
+ if (center === undefined || zoom === undefined) {
23
+ for (const sourceName in mapStyleJson.sources) {
24
+ if (
25
+ mapStyleJson.sources[sourceName].type === 'vector' &&
26
+ mapStyleJson.sources[sourceName].hasOwnProperty('url')
27
+ ) {
28
+ const mapTileUrl = mapStyleJson.sources[sourceName].url
29
+ const mapTileRes = await fetch(mapTileUrl)
30
+ const mapTileJson = await mapTileRes.json()
31
+ if (center === undefined) {
32
+ const bounds = mapTileJson.bounds
33
+ center = mapTileJson.center
34
+ ? mapTileJson.center
35
+ : [(bounds[0] + bounds[2]) / 2, (bounds[1] + bounds[3]) / 2]
36
+ }
37
+ if (zoom === undefined) {
38
+ zoom = (mapTileJson.minzoom + mapTileJson.maxzoom) / 2
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ return {
45
+ style: mapStyleUrl,
46
+ center,
47
+ zoom,
48
+ }
49
+ },
50
+ setupDebugCheckboxes: function (map) {
51
+ const properties = [
52
+ 'showTileBoundaries',
53
+ 'showCollisionBoxes',
54
+ 'showPadding',
55
+ ]
56
+ for (const property of properties) {
57
+ const control = document.getElementById(property)
58
+ const clickHandler = function () {
59
+ const checked = control.checked
60
+ map[property] = checked
61
+ }
62
+ clickHandler()
63
+ control.addEventListener('click', clickHandler)
64
+ }
65
+ },
66
+ }
67
+ })()
@@ -1,35 +1,30 @@
1
- const map = new geolonia.Map({
2
- container: '#map',
3
- hash: true,
4
- style: `http://${window.location.host}/style.json`,
5
- })
1
+ ;(async () => {
2
+ const { style, center, zoom } = await window._charites.parseMapStyle()
3
+ const options = {
4
+ container: 'map',
5
+ hash: true,
6
+ style,
7
+ }
8
+ if (center) options.center = center
9
+ if (zoom) options.zoom = zoom
10
+ const map = new geolonia.Map(options)
6
11
 
7
- const socket = new WebSocket('ws://localhost:___PORT___')
12
+ window._charites.initializeWebSocket((message) => {
13
+ map.setStyle(JSON.parse(message.data))
14
+ })
8
15
 
9
- socket.addEventListener('message', (message) => {
10
- map.setStyle(JSON.parse(message.data))
11
- })
16
+ map.addControl(
17
+ new MaplibreLegendControl(
18
+ {},
19
+ {
20
+ showDefault: true,
21
+ showCheckbox: true,
22
+ onlyRendered: true,
23
+ reverseOrder: true,
24
+ },
25
+ ),
26
+ 'bottom-left',
27
+ )
12
28
 
13
- const showTileBoundaries = document.getElementById('showTileBoundaries')
14
- const setShowTileBoundaries = function () {
15
- const checked = showTileBoundaries.checked
16
- map.showTileBoundaries = checked
17
- }
18
- setShowTileBoundaries()
19
- showTileBoundaries.addEventListener('click', setShowTileBoundaries)
20
-
21
- const showCollisionBoxes = document.getElementById('showCollisionBoxes')
22
- const setShowCollisionBoxes = function () {
23
- const checked = showCollisionBoxes.checked
24
- map.showCollisionBoxes = checked
25
- }
26
- setShowCollisionBoxes()
27
- showCollisionBoxes.addEventListener('click', setShowCollisionBoxes)
28
-
29
- const showPadding = document.getElementById('showPadding')
30
- const setShowPadding = function () {
31
- const checked = showPadding.checked
32
- map.showPadding = checked
33
- }
34
- setShowPadding()
35
- showPadding.addEventListener('click', setShowPadding)
29
+ window._charites.setupDebugCheckboxes(map)
30
+ })()
@@ -1,8 +1,9 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html id="charites-geolonia">
3
3
  <head>
4
4
  <link rel="stylesheet" href="/app.css" />
5
5
  <title>Charites Live Preview</title>
6
+ <link href='https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.css' rel='stylesheet' />
6
7
  </head>
7
8
  <body>
8
9
  <div class="overlay">
@@ -14,6 +15,8 @@
14
15
  </div>
15
16
  <div id="map"></div>
16
17
  <script type="text/javascript" src="https://cdn.geolonia.com/v1/embed?geolonia-api-key=YOUR-API-KEY"></script>
18
+ <script src="https://watergis.github.io/maplibre-gl-legend/maplibre-gl-legend.js"></script>
19
+ <script type="text/javascript" src="/shared.js"></script>
17
20
  <script type="text/javascript" src="/app.js"></script>
18
21
  </body>
19
22
  </html>
@@ -1,53 +1,35 @@
1
- mapboxgl.accessToken = '___MAPBOX_ACCESS_TOKEN___'
2
-
3
- const map = new mapboxgl.Map({
4
- container: 'map',
5
- hash: true,
6
- style: `http://${window.location.host}/style.json`,
7
- })
8
-
9
- const socket = new WebSocket('ws://localhost:___PORT___')
10
-
11
- socket.addEventListener('message', (message) => {
12
- map.setStyle(JSON.parse(message.data))
13
- })
14
-
15
- map.addControl(new mapboxgl.NavigationControl(), 'top-right')
16
-
17
- map.addControl(
18
- new watergis.MapboxLegendControl(
19
- {},
20
- {
21
- showDefault: true,
22
- showCheckbox: true,
23
- onlyRendered: true,
24
- reverseOrder: true,
25
- accesstoken: mapboxgl.accessToken,
26
- },
27
- ),
28
- 'bottom-left',
29
- )
30
-
31
- const showTileBoundaries = document.getElementById('showTileBoundaries')
32
- const setShowTileBoundaries = function () {
33
- const checked = showTileBoundaries.checked
34
- map.showTileBoundaries = checked
35
- }
36
- setShowTileBoundaries()
37
- showTileBoundaries.addEventListener('click', setShowTileBoundaries)
38
-
39
- const showCollisionBoxes = document.getElementById('showCollisionBoxes')
40
- const setShowCollisionBoxes = function () {
41
- const checked = showCollisionBoxes.checked
42
- map.showCollisionBoxes = checked
43
- }
44
- setShowCollisionBoxes()
45
- showCollisionBoxes.addEventListener('click', setShowCollisionBoxes)
46
-
47
- const showPadding = document.getElementById('showPadding')
48
- const setShowPadding = function () {
49
- const checked = showPadding.checked
50
- map.showPadding = checked
51
- }
52
- setShowPadding()
53
- showPadding.addEventListener('click', setShowPadding)
1
+ ;(async () => {
2
+ mapboxgl.accessToken = '___MAPBOX_ACCESS_TOKEN___'
3
+
4
+ const { style, center, zoom } = await window._charites.parseMapStyle()
5
+ const options = {
6
+ container: 'map',
7
+ hash: true,
8
+ style,
9
+ }
10
+ if (center) options.center = center
11
+ if (zoom) options.zoom = zoom
12
+ const map = new mapboxgl.Map(options)
13
+
14
+ window._charites.initializeWebSocket((message) => {
15
+ map.setStyle(JSON.parse(message.data))
16
+ })
17
+
18
+ map.addControl(new mapboxgl.NavigationControl(), 'top-right')
19
+
20
+ map.addControl(
21
+ new MapboxLegendControl(
22
+ {},
23
+ {
24
+ showDefault: true,
25
+ showCheckbox: true,
26
+ onlyRendered: true,
27
+ reverseOrder: true,
28
+ accesstoken: mapboxgl.accessToken,
29
+ },
30
+ ),
31
+ 'bottom-left',
32
+ )
33
+
34
+ window._charites.setupDebugCheckboxes(map)
35
+ })()
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html id="charites-mapbox">
3
3
  <head>
4
4
  <link rel="stylesheet" href="/app.css" />
5
5
  <link href='https://api.mapbox.com/mapbox-gl-js/v2.5.0/mapbox-gl.css' rel='stylesheet' />
@@ -17,6 +17,7 @@
17
17
  <label><input type="checkbox" id="showPadding">show Padding</label>
18
18
  </div>
19
19
  <div id="map"></div>
20
+ <script type="text/javascript" src="/shared.js"></script>
20
21
  <script type="text/javascript" src="/app.js"></script>
21
22
  </body>
22
23
  </html>
package/src/cli/serve.ts CHANGED
@@ -15,7 +15,7 @@ program
15
15
  )
16
16
  .option(
17
17
  '--mapbox-access-token [mapboxAccessToken]',
18
- 'Access Token for the Mapbox',
18
+ 'Your Mapbox Access Token (required if using the `mapbox` provider)',
19
19
  )
20
20
  .option('--port [port]', 'Specify custom port')
21
21
  .action((source: string, serveOptions: serveOptions) => {
@@ -36,15 +36,25 @@ export function serve(source: string, options: serveOptions) {
36
36
  throw `${sourcePath}: No such file or directory`
37
37
  }
38
38
 
39
+ const mapboxAccessToken =
40
+ options.mapboxAccessToken || defaultValues.mapboxAccessToken
41
+ if (provider === 'mapbox' && !mapboxAccessToken) {
42
+ throw `Provider is mapbox, but the Mapbox Access Token is not set. Please provide it using --mapbox-access-token, or set it in \`~/.charites/config.yml\` (see the Global configuration section of the documentation for more information)`
43
+ }
44
+
39
45
  const server = http.createServer((req, res) => {
40
46
  const url = (req.url || '').replace(/\?.*/, '')
41
- const dir = path.join(defaultValues.providerDir, provider)
47
+ const defaultProviderDir = path.join(defaultValues.providerDir, 'default')
48
+ const providerDir = path.join(defaultValues.providerDir, provider)
42
49
 
43
50
  switch (url) {
44
51
  case '/':
45
52
  res.statusCode = 200
46
53
  res.setHeader('Content-Type', 'text/html; charset=UTF-8')
47
- const content = fs.readFileSync(path.join(dir, 'index.html'), 'utf-8')
54
+ const content = fs.readFileSync(
55
+ path.join(providerDir, 'index.html'),
56
+ 'utf-8',
57
+ )
48
58
  res.end(content)
49
59
  break
50
60
  case '/style.json':
@@ -62,14 +72,27 @@ export function serve(source: string, options: serveOptions) {
62
72
  case '/app.css':
63
73
  res.statusCode = 200
64
74
  res.setHeader('Content-Type', 'text/css; charset=UTF-8')
65
- const css = fs.readFileSync(path.join(dir, 'app.css'), 'utf-8')
75
+ const css = fs.readFileSync(
76
+ path.join(defaultProviderDir, 'app.css'),
77
+ 'utf-8',
78
+ )
66
79
  res.end(css)
67
80
  break
81
+ case `/shared.js`:
82
+ res.statusCode = 200
83
+ res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
84
+ const shared = fs.readFileSync(
85
+ path.join(defaultProviderDir, 'shared.js'),
86
+ 'utf-8',
87
+ )
88
+ const js = shared.replace('___PORT___', `${port}`)
89
+ res.end(js)
90
+ break
68
91
  case `/app.js`:
69
92
  res.statusCode = 200
70
93
  res.setHeader('Content-Type', 'application/javascript; charset=UTF-8')
71
94
  try {
72
- const app = fs.readFileSync(path.join(dir, 'app.js'), 'utf-8')
95
+ const app = fs.readFileSync(path.join(providerDir, 'app.js'), 'utf-8')
73
96
  const js = app
74
97
  .replace('___PORT___', `${port}`)
75
98
  .replace(
@@ -47,11 +47,15 @@ const writeDecompositedYaml = (
47
47
  const layer = style.layers[i]
48
48
  const layerYml = YAML.dump(layer)
49
49
  const fileName = `${style.layers[i].id}.yml`
50
- const dirName = path.join(path.dirname(destinationPath), 'layers')
51
- fs.mkdirSync(dirName, { recursive: true })
52
- fs.writeFileSync(path.join(dirName, fileName), layerYml)
50
+ const layersDirName = path.join(path.dirname(destinationPath), 'layers')
51
+ const filePath = path.join(layersDirName, fileName)
52
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
53
+ fs.writeFileSync(filePath, layerYml)
54
+
55
+ // ts-ignore is required here because !!inc/file string is not compatible with the Layer object type.
56
+ // We use path.posix.join to make sure the path uses / path separators, even when run on Windows.
53
57
  // @ts-ignore
54
- layers.push(`!!inc/file ${path.join('layers', fileName)}`)
58
+ layers.push(`!!inc/file ${path.posix.join('layers', fileName)}`)
55
59
  }
56
60
 
57
61
  style.layers = layers
@@ -1,4 +1,5 @@
1
- import { assert } from 'chai'
1
+ import chai from 'chai'
2
+ import chaiAsPromised from 'chai-as-promised'
2
3
  import path from 'path'
3
4
  import fs from 'fs'
4
5
  import os from 'os'
@@ -6,6 +7,10 @@ import os from 'os'
6
7
  import { build, buildWatch } from '../src/commands/build'
7
8
  import { defaultValues } from '../src/lib/defaultValues'
8
9
 
10
+ chai.use(chaiAsPromised)
11
+ chai.should()
12
+ const assert = chai.assert
13
+
9
14
  describe('Test for the `build.ts`.', () => {
10
15
  const styleYaml = path.join(__dirname, 'data/style.yml')
11
16
  const iconSource = path.join(__dirname, 'data/icons')
@@ -38,7 +43,12 @@ describe('Test for the `build.ts`.', () => {
38
43
 
39
44
  // The file should exists.
40
45
  assert.deepEqual(true, !!fs.statSync(styleJson))
41
- assert.deepEqual(8, JSON.parse(fs.readFileSync(styleJson, 'utf-8')).version)
46
+
47
+ const fixtureStyleJson = path.join(__dirname, 'data/style.json')
48
+ assert.equal(
49
+ fs.readFileSync(styleJson, 'utf-8'),
50
+ fs.readFileSync(fixtureStyleJson, 'utf-8'),
51
+ )
42
52
  })
43
53
 
44
54
  it('Should minify `data/style.yml` to JSON.', async () => {
@@ -101,18 +111,18 @@ describe('Test for the `build.ts`.', () => {
101
111
  })
102
112
  })
103
113
 
104
- it('Should not create sprite when input directory is not exist.', async () => {
114
+ it('Should not create sprite when input directory is not exist.', () => {
105
115
  const noExistInputDir = path.join(__dirname, 'data/hellooooo')
106
116
 
107
- try {
108
- await build(styleYaml, styleJson, {
109
- provider: defaultValues.provider,
110
- spriteInput: noExistInputDir,
111
- spriteOutput: tmpdir,
112
- })
113
- } catch (error) {
114
- assert.deepEqual(true, !!error) // It should have something error.
115
- }
117
+ const promise = build(styleYaml, styleJson, {
118
+ provider: defaultValues.provider,
119
+ spriteInput: noExistInputDir,
120
+ spriteOutput: tmpdir,
121
+ })
122
+ return assert.isRejected(
123
+ promise,
124
+ /No such directory. Please specify valid icon input directory. For more help run charites build --help/,
125
+ )
116
126
  })
117
127
 
118
128
  it('Should watch `*.yml` and convert it to JSON', async () => {
@@ -1,4 +1,5 @@
1
- import { assert } from 'chai'
1
+ import chai from 'chai'
2
+ import chaiAsPromised from 'chai-as-promised'
2
3
  import path from 'path'
3
4
  import fs from 'fs'
4
5
  import { copyFixturesFile, copyFixturesDir } from './util/copyFixtures'
@@ -7,11 +8,15 @@ import { makeTempDir } from './util/makeTempDir'
7
8
  import charites from './util/charitesCmd'
8
9
 
9
10
  let tmpdir = ''
11
+ chai.use(chaiAsPromised)
12
+ chai.should()
13
+ const assert = chai.assert
10
14
 
11
15
  describe('Test for the `charites build`', () => {
12
16
  beforeEach(async function () {
13
17
  tmpdir = makeTempDir()
14
18
  copyFixturesFile('style.yml', tmpdir)
19
+ copyFixturesFile('error.yml', tmpdir)
15
20
  copyFixturesDir('layers', tmpdir)
16
21
  copyFixturesDir('icons', tmpdir)
17
22
  })
@@ -62,39 +67,42 @@ describe('Test for the `charites build`', () => {
62
67
  assert.isTrue(fs.existsSync(path.join(tmpdir, 'basic-white.json')))
63
68
  })
64
69
 
65
- it('charites build style.yml style.json --sprite-url http://localhost:8080', async () => {
66
- try {
67
- // prettier-ignore
68
- await exec(`${charites} build style.yml style.json --sprite-url http://localhost:8080`, tmpdir)
69
- } catch (error) {
70
- assert.deepEqual(error.stdout, '')
71
- assert.deepEqual(error.stderr, 'Invalid sprite url format.\n')
72
- }
70
+ it('charites build style.yml style.json --sprite-url http://localhost:8080', () => {
71
+ const promise = exec(
72
+ `${charites} build style.yml style.json --sprite-url http://localhost:8080`,
73
+ tmpdir,
74
+ )
75
+ return assert.isRejected(promise, /Invalid sprite url format.\n/)
73
76
  })
74
77
 
75
- it('charites build style.yml style.json --sprite-input noExistDirname', async () => {
76
- try {
77
- // prettier-ignore
78
- await exec(`${charites} build style.yml style.json --sprite-input noExistDirname`, tmpdir)
79
- } catch (error) {
80
- assert.deepEqual(error.stdout, '')
81
- assert.deepEqual(
82
- error.stderr,
83
- 'noExistDirname: No such directory. Please specify valid icon input directory. For more help run charites build --help\n',
84
- )
85
- }
78
+ it('charites build style.yml style.json --sprite-input noExistDirname', () => {
79
+ const promise = exec(
80
+ `${charites} build style.yml style.json --sprite-input noExistDirname`,
81
+ tmpdir,
82
+ )
83
+ return assert.isRejected(
84
+ promise,
85
+ /noExistDirname: No such directory. Please specify valid icon input directory. For more help run charites build --help\n/,
86
+ )
86
87
  })
87
88
 
88
- it('charites build style.yml style.json --sprite-output noExistDirname', async () => {
89
- try {
90
- // prettier-ignore
91
- await exec(`${charites} build style.yml style.json --sprite-output noExistDirname`, tmpdir)
92
- } catch (error) {
93
- assert.deepEqual(error.stdout, '')
94
- assert.deepEqual(
95
- error.stderr,
96
- 'noExistDirname: No such directory. Please specify valid icon output directory. For more help run charites build --help\n',
97
- )
98
- }
89
+ it('charites build style.yml style.json --sprite-output noExistDirname', (done) => {
90
+ const promise = exec(
91
+ `${charites} build style.yml style.json --sprite-output noExistDirname`,
92
+ tmpdir,
93
+ )
94
+ promise.should.be.rejected
95
+ .then(function () {
96
+ return assert.isRejected(
97
+ promise,
98
+ /noExistDirname: No such directory. Please specify valid icon output directory. For more help run charites build --help\n/,
99
+ )
100
+ })
101
+ .should.notify(done)
102
+ })
103
+
104
+ it('charites build print error message', () => {
105
+ const promise = exec(`${charites} build error.yml`, tmpdir)
106
+ return assert.isRejected(promise, /missing required property "sources"/)
99
107
  })
100
108
  })
@@ -14,8 +14,9 @@
14
14
  {
15
15
  "id": "background",
16
16
  "type": "background",
17
- "paint": null,
18
- "background-color": "rgba(19, 28, 54, 1)"
17
+ "paint": {
18
+ "background-color": "rgba(19, 28, 54, 1)"
19
+ }
19
20
  },
20
21
  {
21
22
  "id": "water",
@@ -30,4 +31,4 @@
30
31
  }
31
32
  }
32
33
  ]
33
- }
34
+ }
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with maplibre', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8080/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with geolonia', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8088/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -0,0 +1,13 @@
1
+ import { test, expect } from '@playwright/test'
2
+
3
+ test('Charites Live Preview with mapbox', async ({ page }) => {
4
+ // collect errors on the page
5
+ const pageErrors: Error[] = []
6
+ page.on('pageerror', (exception) => pageErrors.push(exception))
7
+
8
+ await page.goto('http://localhost:8888/', { waitUntil: 'networkidle' })
9
+ await page.waitForTimeout(1000)
10
+ const title = await page.title()
11
+ expect(title).toBe('Charites Live Preview')
12
+ expect(pageErrors).toMatchObject([])
13
+ })
@@ -1,17 +0,0 @@
1
- html, body, #map
2
- {
3
- width: 100vw;
4
- height: 100vh;
5
- padding: 0;
6
- margin: 0;
7
- }
8
-
9
- .overlay {
10
- width: 200px;
11
- position: absolute;
12
- top: 10;
13
- left: 10;
14
- margin: 5px;
15
- z-index: 90;
16
- background-color: rgba(255, 255, 255, 0.6);
17
- }
@@ -1,17 +0,0 @@
1
- html, body, #map
2
- {
3
- width: 100vw;
4
- height: 100vh;
5
- padding: 0;
6
- margin: 0;
7
- }
8
-
9
- .overlay {
10
- width: 200px;
11
- position: absolute;
12
- top: 10;
13
- left: 10;
14
- margin: 5px;
15
- z-index: 90;
16
- background-color: rgba(255, 255, 255, 0.6);
17
- }