leaflet-rotate 0.2.8 → 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.
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ pull_request:
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ test:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+ - uses: actions/setup-node@v4
16
+ with:
17
+ node-version: 20
18
+ cache: npm
19
+ - run: npm ci
20
+ - run: npx playwright install --with-deps chromium
21
+ - run: npm test
package/README.md CHANGED
@@ -30,7 +30,7 @@ cd ./leaflet-rotate
30
30
  npm i # install dependencies
31
31
  npm run dev # start dev server at: http://localhost:8080
32
32
  npm run build # generate "dist" files (once)
33
- npm run test # test all "*.spec.js" files (once)
33
+ npm run test # build and run browser tests
34
34
  ```
35
35
 
36
36
  After that you can start developing inside the `src` and `test` folders (eg. open "http://localhost:8080/test" in your browser to preview changes).
@@ -41,6 +41,10 @@ After that you can start developing inside the `src` and `test` folders (eg. ope
41
41
 
42
42
  Be aware that this library overrides notable parts of leaflet core via the [L.Class.include()](https://leafletjs.com/examples/extending/extending-1-classes.html) function in order to make the rotate feature usable as a standalone plug-in.
43
43
 
44
+ For [Leaflet.heat](https://github.com/Leaflet/Leaflet.heat) or [heatmap.js's Leaflet plugin](https://github.com/pa7/heatmap.js/tree/master/plugins/leaflet-heatmap), load `misc/leaflet-heat-rotate.js` after the heatmap plugin. It adapts heat redraw coordinates to the rotated pane.
45
+
46
+ Markers with `rotateWithView: true` can use `keepUpright: true` to remain readable while preserving their line alignment.
47
+
44
48
  Initial changes to this project have been apported by comparing the following branches: [https://github.com/Leaflet/Leaflet/compare/main...fnicollet:rotate-master](https://github.com/Leaflet/Leaflet/compare/main...fnicollet:rotate-master) (ref: [bac6c7d](https://github.com/fnicollet/Leaflet/tree/4ab6342f74516e7087dcd2ae786c721f36addf9e))
45
49
 
46
50
  ---
package/build/build.js ADDED
@@ -0,0 +1,69 @@
1
+ import { mkdir, readFile } from 'node:fs/promises';
2
+ import { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { build as esbuildBuild, context, transform } from 'esbuild';
5
+
6
+ const options = {
7
+ entryPoints: ['src/index.js'],
8
+ bundle: true,
9
+ format: 'iife',
10
+ globalName: 'leafletRotate',
11
+ sourcemap: true,
12
+ outdir: 'dist',
13
+ entryNames: 'leaflet-rotate-src',
14
+ };
15
+ const minifiedOptions = { ...options, entryNames: 'leaflet-rotate', minify: true };
16
+ const debugMinifiedOptions = {
17
+ entryPoints: ['lib/debug.js'],
18
+ outfile: 'lib/debug.min.js',
19
+ minify: true,
20
+ plugins: [{
21
+ name: 'minify-debug-templates',
22
+ setup(build) {
23
+ build.onLoad({ filter: /[\\/]lib[\\/]debug\.js$/ }, async ({ path }) => {
24
+ const source = await readFile(path, 'utf8');
25
+ const style = source.match(/<style>([\s\S]*?)<\/style>/);
26
+ if (!style) throw new Error(`Could not find debug stylesheet in ${path}`);
27
+
28
+ const { code } = await transform(style[1], { loader: 'css', minify: true });
29
+ const contents = source
30
+ .replace(style[0], `<style>${code.trim()}</style>`)
31
+ .replace(/`([^`]*<[^`]*?)`/gs, (template, html) => {
32
+ if (html.includes('${')) return template;
33
+ return `\`${html.replace(/<!--[\s\S]*?-->/g, '').replace(/>\s+</g, '><').trim()}\``;
34
+ });
35
+
36
+ return {
37
+ contents,
38
+ loader: 'js',
39
+ watchFiles: [path],
40
+ };
41
+ });
42
+ },
43
+ }],
44
+ };
45
+
46
+ export async function build({ watch = false } = {}) {
47
+ await mkdir('dist', { recursive: true });
48
+
49
+ if (!watch) {
50
+ await Promise.all([
51
+ esbuildBuild(options),
52
+ esbuildBuild(minifiedOptions),
53
+ esbuildBuild(debugMinifiedOptions),
54
+ ]);
55
+ return;
56
+ }
57
+
58
+ const builders = await Promise.all([
59
+ context(options),
60
+ context(minifiedOptions),
61
+ context(debugMinifiedOptions),
62
+ ]);
63
+ await Promise.all(builders.map(builder => builder.watch()));
64
+ return () => Promise.all(builders.map(builder => builder.dispose()));
65
+ }
66
+
67
+ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
68
+ await build();
69
+ }
package/build/dev.js ADDED
@@ -0,0 +1,24 @@
1
+ import { build } from './build.js';
2
+ import { startServer } from './server.js';
3
+
4
+ const server = await startServer(8080);
5
+ let stopBuild;
6
+
7
+ try {
8
+ stopBuild = await build({ watch: true });
9
+ console.log('Serving on http://127.0.0.1:8080');
10
+ } catch (error) {
11
+ await new Promise((resolve, reject) => server.close(err => err ? reject(err) : resolve()));
12
+ throw error;
13
+ }
14
+
15
+ let stopping = false;
16
+ async function stop() {
17
+ if (stopping) return;
18
+ stopping = true;
19
+ await stopBuild();
20
+ await new Promise((resolve, reject) => server.close(error => error ? reject(error) : resolve()));
21
+ }
22
+
23
+ process.on('SIGINT', stop);
24
+ process.on('SIGTERM', stop);
@@ -0,0 +1,104 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { readdir, stat } from 'node:fs/promises';
3
+ import { createServer } from 'node:http';
4
+ import { extname, resolve, sep } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const root = resolve(fileURLToPath(new URL('..', import.meta.url)));
8
+ const types = {
9
+ '.css': 'text/css; charset=utf-8',
10
+ '.geojson': 'application/geo+json',
11
+ '.gif': 'image/gif',
12
+ '.gpx': 'application/gpx+xml',
13
+ '.html': 'text/html; charset=utf-8',
14
+ '.ico': 'image/x-icon',
15
+ '.jpeg': 'image/jpeg',
16
+ '.jpg': 'image/jpeg',
17
+ '.js': 'text/javascript; charset=utf-8',
18
+ '.json': 'application/json; charset=utf-8',
19
+ '.kml': 'application/vnd.google-earth.kml+xml',
20
+ '.map': 'application/json; charset=utf-8',
21
+ '.md': 'text/markdown; charset=utf-8',
22
+ '.png': 'image/png',
23
+ '.svg': 'image/svg+xml',
24
+ '.tcx': 'application/vnd.garmin.tcx+xml',
25
+ '.txt': 'text/plain; charset=utf-8',
26
+ '.webp': 'image/webp',
27
+ '.xml': 'text/xml; charset=utf-8',
28
+ };
29
+
30
+ const escapeHtml = value => value.replace(/[&<>"']/g, char => ({
31
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
32
+ })[char]);
33
+
34
+ export function startServer(port = 8080) {
35
+ const server = createServer(async (request, response) => {
36
+ if (request.method !== 'GET' && request.method !== 'HEAD') {
37
+ response.writeHead(405, { Allow: 'GET, HEAD' }).end();
38
+ return;
39
+ }
40
+
41
+ try {
42
+ const pathname = decodeURIComponent(new URL(request.url, 'http://localhost').pathname);
43
+ if (pathname.includes('\0')) {
44
+ response.writeHead(400).end('Bad request');
45
+ return;
46
+ }
47
+ const file = resolve(root, `.${pathname}`);
48
+ if (file !== root && !file.startsWith(root + sep)) {
49
+ response.writeHead(403).end();
50
+ return;
51
+ }
52
+
53
+ const info = await stat(file);
54
+ const target = info.isDirectory() ? resolve(file, 'index.html') : file;
55
+ let content;
56
+ try {
57
+ content = await stat(target);
58
+ } catch (error) {
59
+ if (!info.isDirectory() || error.code !== 'ENOENT') throw error;
60
+ const entries = await readdir(file, { withFileTypes: true });
61
+ const base = new URL(request.url, 'http://localhost').pathname.replace(/\/?$/, '/');
62
+ const links = entries.map(entry => {
63
+ const suffix = entry.isDirectory() ? '/' : '';
64
+ const href = `${base}${encodeURIComponent(entry.name)}${suffix}`;
65
+ return `<li><a href="${escapeHtml(href)}">${escapeHtml(entry.name)}${suffix}</a></li>`;
66
+ }).join('');
67
+ response.writeHead(200, { 'Content-Type': types['.html'] });
68
+ response.end(request.method === 'HEAD' ? undefined : `<ul>${links}</ul>`);
69
+ return;
70
+ }
71
+ if (!content.isFile()) {
72
+ response.writeHead(404).end();
73
+ return;
74
+ }
75
+ response.writeHead(200, {
76
+ 'Content-Type': types[extname(target).toLowerCase()] ?? 'application/octet-stream',
77
+ });
78
+ if (request.method === 'HEAD') response.end();
79
+ else createReadStream(target).on('error', error => {
80
+ console.error(error);
81
+ response.destroy(error);
82
+ }).pipe(response);
83
+ } catch (error) {
84
+ if (error instanceof URIError || error.code === 'ERR_INVALID_URL') {
85
+ response.writeHead(400).end('Bad request');
86
+ return;
87
+ }
88
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR' || error.code === 'EISDIR') {
89
+ response.writeHead(404).end('Not found');
90
+ return;
91
+ }
92
+ console.error(error);
93
+ response.writeHead(500).end('Internal server error');
94
+ }
95
+ });
96
+
97
+ return new Promise((resolveServer, reject) => {
98
+ server.once('error', reject);
99
+ server.listen(port, '127.0.0.1', () => {
100
+ server.off('error', reject);
101
+ resolveServer(server);
102
+ });
103
+ });
104
+ }
package/cucumber.mjs ADDED
@@ -0,0 +1,5 @@
1
+ export default {
2
+ paths: ['test/features/**/*.feature'],
3
+ import: ['test/support/**/*.js', 'test/steps/**/*.js'],
4
+ format: ['progress'],
5
+ };