Initial commit
This commit is contained in:
commit
dc29454df7
18 changed files with 2275 additions and 0 deletions
23
.gitignore
vendored
Normal file
23
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
node_modules
|
||||||
|
|
||||||
|
# Output
|
||||||
|
.output
|
||||||
|
.vercel
|
||||||
|
.netlify
|
||||||
|
.wrangler
|
||||||
|
/.svelte-kit
|
||||||
|
/build
|
||||||
|
|
||||||
|
# OS
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# Env
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
|
# Vite
|
||||||
|
vite.config.js.timestamp-*
|
||||||
|
vite.config.ts.timestamp-*
|
||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
engine-strict=true
|
||||||
38
README.md
Normal file
38
README.md
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# sv
|
||||||
|
|
||||||
|
Everything you need to build a Svelte project, powered by [`sv`](https://github.com/sveltejs/cli).
|
||||||
|
|
||||||
|
## Creating a project
|
||||||
|
|
||||||
|
If you're seeing this, you've probably already done this step. Congrats!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create a new project in the current directory
|
||||||
|
npx sv create
|
||||||
|
|
||||||
|
# create a new project in my-app
|
||||||
|
npx sv create my-app
|
||||||
|
```
|
||||||
|
|
||||||
|
## Developing
|
||||||
|
|
||||||
|
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# or start the server and open the app in a new browser tab
|
||||||
|
npm run dev -- --open
|
||||||
|
```
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
To create a production version of your app:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
You can preview the production build with `npm run preview`.
|
||||||
|
|
||||||
|
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||||
390
build.js
Normal file
390
build.js
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
const compile = require( 'svelte/compiler' ).compile
|
||||||
|
|
||||||
|
const chokidar = require( 'chokidar' );
|
||||||
|
const esbuild = require( 'esbuild' );
|
||||||
|
const {readdirSync, statSync, existsSync, writeFileSync, readFileSync} = require( 'fs' );
|
||||||
|
const {join, basename, resolve, dirname, relative} = require( 'path' );
|
||||||
|
const sveltePlugin = require( 'esbuild-svelte' );
|
||||||
|
const {sum} = require( 'lodash' );
|
||||||
|
const parse5 = require( 'parse5' );
|
||||||
|
const notifier = require('node-notifier');
|
||||||
|
|
||||||
|
process.on('uncaughtException', error => {
|
||||||
|
notifier.notify({
|
||||||
|
title: 'Error occurs',
|
||||||
|
message: `${error}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const [watch, serve, minify, debug, logVars] = ['--watch', '--serve', '--minify', '--debug', '--log-vars'].map( s =>
|
||||||
|
process.argv.includes( s )
|
||||||
|
);
|
||||||
|
const debug_console_log = ( args, returnIndex = 0 ) => (debug && console.log( ...args ), args[ returnIndex ]);
|
||||||
|
|
||||||
|
const ignorePath = new Set( [
|
||||||
|
'node_modules',
|
||||||
|
'.vscode',
|
||||||
|
'.idea',
|
||||||
|
'.git',
|
||||||
|
'.gitignore',
|
||||||
|
'build.js',
|
||||||
|
'package-lock.json',
|
||||||
|
'package.json',
|
||||||
|
'README.md',
|
||||||
|
'build.js',
|
||||||
|
'pullpush.sh',
|
||||||
|
] );
|
||||||
|
|
||||||
|
// find page candidates
|
||||||
|
function findPages( dir = '.', sink = [] ) {
|
||||||
|
if( ignorePath.has( dir.replace( './', '' ).replace( '.\\', '' ) ) ) {
|
||||||
|
debug && console.log( 'skip:', dir );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = readdirSync( dir ).filter( f => f[ 0 ]!=='_' );
|
||||||
|
const svelteFiles = files.filter( f => f.endsWith( '.svelte' ) && statSync( join( dir, f ) ).isFile() );
|
||||||
|
svelteFiles.forEach( f => sink.push( join( dir, f ) ) );
|
||||||
|
|
||||||
|
files
|
||||||
|
.filter( f => !svelteFiles.includes( f ) )
|
||||||
|
.map( f => join( dir, f ) )
|
||||||
|
.filter( f => statSync( f ).isDirectory() )
|
||||||
|
.forEach( f => findPages( f, sink ) );
|
||||||
|
return sink;
|
||||||
|
}
|
||||||
|
|
||||||
|
const _zId_prefix = `z_placeholder_${Math.floor( Math.random() * 1000000000 ).toString( 16 )}_`;
|
||||||
|
const _zReplacer = s => debug_console_log( ['z-replace:', s, `"${_zId_prefix}${Buffer.from( s ).toString( 'base64' )}"`], 2 );
|
||||||
|
|
||||||
|
const zPlaceholderReplacer = content =>
|
||||||
|
|
||||||
|
content?.replace( /\#\{\s*\w+\s*\}/gs, _zReplacer ) // #{ key }
|
||||||
|
.replace( /\/\*\!\s*\w+\s*\*\//gs, _zReplacer ) // map /*! mapKey */
|
||||||
|
.replace( /\[\s*\/\*\s*\w+\s*\*\/\s*\]/gs, _zReplacer ) // map [/* mapKey */]
|
||||||
|
.replace( /\{\s*\/\*\s*\w+\s*\*\/\s*\}/gs, _zReplacer ); // map {/* mapKey */}
|
||||||
|
|
||||||
|
global.zPlaceholderReplacer = zPlaceholderReplacer;
|
||||||
|
|
||||||
|
const zPlaceholderRestore = ( content, sink ) =>
|
||||||
|
content?.replace( new RegExp( `("|')${_zId_prefix}(\\w+=*)\\1`, 'g' ), ( _, _2, s ) => {
|
||||||
|
s = Buffer.from( s, 'base64' ).toString( 'ascii' );
|
||||||
|
sink.push( s );
|
||||||
|
debug && console.log( 'z-restore', _, s );
|
||||||
|
return s;
|
||||||
|
} );
|
||||||
|
|
||||||
|
const svelteJsPathResolver = {
|
||||||
|
name: 'svelteJsPathResolver',
|
||||||
|
setup( build ) {
|
||||||
|
const options = {filter: /\.svelte\.(ts)$/};
|
||||||
|
|
||||||
|
build.onResolve( options, ( {path, resolveDir} ) => ({path: join( resolveDir, path )}) );
|
||||||
|
build.onLoad( options, ( {path} ) => {
|
||||||
|
return {
|
||||||
|
contents: `
|
||||||
|
import App from "./${basename( path ).replace( /\.ts$/, '' )}";
|
||||||
|
export const app = new App({ target: document.getElementById("app") });
|
||||||
|
`,
|
||||||
|
loader: 'ts',
|
||||||
|
resolveDir: dirname( path ),
|
||||||
|
};
|
||||||
|
} );
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function createBuilder( entryPoints ) {
|
||||||
|
console.log( 'pages:', entryPoints );
|
||||||
|
|
||||||
|
return esbuild.build( {
|
||||||
|
entryPoints: entryPoints.map( s => s + '.ts' ),
|
||||||
|
bundle: true,
|
||||||
|
outdir: '.',
|
||||||
|
write: false,
|
||||||
|
plugins: [svelteJsPathResolver, sveltePlugin( require( './svelte.config' ) )],
|
||||||
|
incremental: !!watch,
|
||||||
|
sourcemap: false,
|
||||||
|
minify,
|
||||||
|
} )
|
||||||
|
}
|
||||||
|
|
||||||
|
function layoutFor( path, content = {} ) {
|
||||||
|
path = (() => {
|
||||||
|
let temp = join( path, '..', '_layout.html' );
|
||||||
|
|
||||||
|
while( true ) {
|
||||||
|
if( existsSync( temp ) ) return temp;
|
||||||
|
if( resolve( __dirname )===resolve( dirname( temp ) ) ) return;
|
||||||
|
|
||||||
|
temp = join( temp, '../..', '_layout.html' );
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
layoutFor.cache = layoutFor.cache || {};
|
||||||
|
|
||||||
|
const defaultKey = '_DEFAULT_LAYOUT';
|
||||||
|
if( !path && layoutFor.cache[ defaultKey ] ) return layoutFor.cache[ defaultKey ];
|
||||||
|
|
||||||
|
let cache = layoutFor.cache[ path ];
|
||||||
|
const m = statSync( path ).mtimeMs;
|
||||||
|
if( cache && m===cache.m ) return cache;
|
||||||
|
|
||||||
|
const tree = parse5.parse(
|
||||||
|
path
|
||||||
|
? readFileSync( path, 'utf-8' )
|
||||||
|
: `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>#{title}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>layout not found, please create <b>_layout.html</b></h1>
|
||||||
|
<slot></slot>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
);
|
||||||
|
|
||||||
|
let slot = null;
|
||||||
|
let body = null;
|
||||||
|
|
||||||
|
let stack = [...tree.childNodes];
|
||||||
|
|
||||||
|
while( stack.length && (slot==null || body==null) ) {
|
||||||
|
const t = stack.pop();
|
||||||
|
if( t.nodeName==='body' ) body = t;
|
||||||
|
if( t.nodeName==='slot' || (t.nodeName==='#comment' && t.data?.trim()==='content_goes_here') ) slot = t;
|
||||||
|
|
||||||
|
t.childNodes && (stack = [...stack, ...t.childNodes]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !body ) throw new Error( 'body not found' );
|
||||||
|
|
||||||
|
const appKEY = `${Math.random()}-APP-${Math.random()}`;
|
||||||
|
if( slot ) {
|
||||||
|
slot.nodeName = 'main';
|
||||||
|
slot.tagName = 'main';
|
||||||
|
delete slot.data;
|
||||||
|
slot.attrs = [
|
||||||
|
{name: 'id', value: 'app'},
|
||||||
|
...(slot.attrs || [])?.filter( t => t.name!=='id' )
|
||||||
|
];
|
||||||
|
slot.childNodes = [{nodeName: '#text', value: appKEY}]
|
||||||
|
} else {
|
||||||
|
body.childNodes.push( {
|
||||||
|
nodeName: 'main',
|
||||||
|
tagName: 'main',
|
||||||
|
attrs: [{name: 'id', value: 'app'}],
|
||||||
|
childNodes: [{nodeName: '#text', value: appKEY}],
|
||||||
|
namespaceURI: body.namespaceURI,
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove main content
|
||||||
|
// content showing only for seo friendly
|
||||||
|
body.childNodes.push( {
|
||||||
|
nodeName: 'script',
|
||||||
|
tagName: 'script',
|
||||||
|
attrs: [],
|
||||||
|
childNodes: [{nodeName: '#text', value: "document.getElementById('app').innerHTML=''"}],
|
||||||
|
namespaceURI: body.namespaceURI,
|
||||||
|
} )
|
||||||
|
|
||||||
|
const jsKEY = `${Math.random()}-JS-${Math.random()}`;
|
||||||
|
const cssKEY = `${Math.random()}-CSS-${Math.random()}`;
|
||||||
|
const comments = {
|
||||||
|
nodeName: '#comment',
|
||||||
|
data: '',
|
||||||
|
};
|
||||||
|
const cssVarsComments = {
|
||||||
|
nodeName: '#comment',
|
||||||
|
data: '',
|
||||||
|
};
|
||||||
|
const jsVarsComments = {
|
||||||
|
nodeName: '#comment',
|
||||||
|
data: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
body.childNodes = [
|
||||||
|
...body.childNodes,
|
||||||
|
comments,
|
||||||
|
{
|
||||||
|
nodeName: '#text',
|
||||||
|
value: '\n',
|
||||||
|
},
|
||||||
|
logVars && cssVarsComments,
|
||||||
|
{
|
||||||
|
nodeName: '#text',
|
||||||
|
value: '\n',
|
||||||
|
},
|
||||||
|
logVars && jsVarsComments,
|
||||||
|
{
|
||||||
|
nodeName: '#text',
|
||||||
|
value: '\n',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'style',
|
||||||
|
tagName: 'style',
|
||||||
|
attrs: [],
|
||||||
|
childNodes: [{nodeName: '#text', value: cssKEY}],
|
||||||
|
namespaceURI: body.namespaceURI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: '#text',
|
||||||
|
value: '\n',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: 'script',
|
||||||
|
tagName: 'script',
|
||||||
|
attrs: [],
|
||||||
|
childNodes: [{nodeName: '#text', value: jsKEY}],
|
||||||
|
namespaceURI: body.namespaceURI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nodeName: '#text',
|
||||||
|
value: '\n',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
debug && console.log( 'build layout for:', path || defaultKey );
|
||||||
|
|
||||||
|
return (layoutFor.cache[ path || defaultKey ] = ( {js, css} ) => {
|
||||||
|
const cssVars = [],
|
||||||
|
jsVars = [];
|
||||||
|
js = zPlaceholderRestore( js, jsVars ) || '';
|
||||||
|
css = zPlaceholderRestore( css, cssVars ) || '';
|
||||||
|
|
||||||
|
//comments.data = `BUILD TIME: ${new Date().toISOString()}`;
|
||||||
|
cssVarsComments.data = cssVars.length ? `--- CSS z-vars --- \n${cssVars.join( '\n' )}` : '';
|
||||||
|
jsVarsComments.data = jsVars.length ? `--- JS z-vars --- \n${jsVars.join( '\n' )}` : '';
|
||||||
|
|
||||||
|
let html = content.html || '';
|
||||||
|
const innerCss = (content.css || {}).code || '';
|
||||||
|
|
||||||
|
return parse5.serialize( tree ).
|
||||||
|
replace( cssKEY, css + innerCss ).
|
||||||
|
replace( jsKEY, js ).
|
||||||
|
replace( appKEY, html ).
|
||||||
|
replaceAll("fakecss:"+__dirname,'fakecss:.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
let watcherReady = false;
|
||||||
|
|
||||||
|
watch && console.log( 'first build start' );
|
||||||
|
let pages = findPages();
|
||||||
|
let builder = await createBuilder( pages );
|
||||||
|
|
||||||
|
const compiledFiles = new Set();
|
||||||
|
let cache = {};
|
||||||
|
|
||||||
|
function saveFiles( files = builder, layoutChanged = false ) {
|
||||||
|
const output = {};
|
||||||
|
let unchanged = 0;
|
||||||
|
// path = bla.svelte.js or bla.svelte.css
|
||||||
|
for( const {path, text} of files.outputFiles ) {
|
||||||
|
const ext = /\.(\w+)$/.exec( path )?.[ 1 ];
|
||||||
|
if( ext!=='css' && ext!=='js' ) throw new Error( 'unknown ext:' + ext );
|
||||||
|
|
||||||
|
// bla.js or bla.css
|
||||||
|
const key = path.replace( /\.svelte\.\w+$/, '' );
|
||||||
|
|
||||||
|
output[ key ] = output[ key ] || {};
|
||||||
|
output[ key ][ ext ] = text
|
||||||
|
|
||||||
|
if( cache[ path ]===text && !layoutChanged ) {
|
||||||
|
unchanged += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cache[ path ] = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// do nothing if nothing's changed
|
||||||
|
if( unchanged===files.outputFiles.length ) return;
|
||||||
|
|
||||||
|
if( Object.keys( output ).length===0 ) return console.log( 'no changes' );
|
||||||
|
|
||||||
|
// for each .html files need to be generated
|
||||||
|
Object.entries( output ).forEach( ( [path, data] ) => {
|
||||||
|
const renderedSvelte = compile( path + '.svelte' );
|
||||||
|
|
||||||
|
const content = layoutFor( path, renderedSvelte )( data );
|
||||||
|
|
||||||
|
path = resolve( path + '.html' );
|
||||||
|
compiledFiles.add( path );
|
||||||
|
console.log( 'compiled:', relative( resolve( __dirname ), path ) );
|
||||||
|
writeFileSync( path, content );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
saveFiles();
|
||||||
|
watch && console.log( 'first build end' );
|
||||||
|
|
||||||
|
if( watch ) {
|
||||||
|
const pagesPaths = new Set( pages.map( p => resolve( p ) ) );
|
||||||
|
|
||||||
|
let timeRef = null;
|
||||||
|
|
||||||
|
function changeListener( path, stats, type, watcher ) {
|
||||||
|
switch (type) {
|
||||||
|
case 'change':
|
||||||
|
notifier.notify({
|
||||||
|
title: 'Change occurs',
|
||||||
|
message: `Change occurs in "${path}"`
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'add':
|
||||||
|
notifier.notify({
|
||||||
|
title: 'File added',
|
||||||
|
message: `Added file "${path}"`
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'unlink':
|
||||||
|
notifier.notify({
|
||||||
|
title: 'File remove',
|
||||||
|
message: `Removed file "${path}"`
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( compiledFiles.has( resolve( path ) ) ) return;
|
||||||
|
console.log( type + ':', path.replace( __dirname, '' ) );
|
||||||
|
|
||||||
|
const svelteFile = path[ 0 ]!=='_' && path.endsWith( '.svelte' );
|
||||||
|
|
||||||
|
let pagesChanged = true;
|
||||||
|
if( svelteFile && type==='add' ) pagesPaths.add( resolve( path ) );
|
||||||
|
else if( svelteFile && type==='unlink' ) pagesPaths.delete( resolve( path ) );
|
||||||
|
else pagesChanged = false;
|
||||||
|
|
||||||
|
let layoutChanged = path.endsWith( '_layout.html' );
|
||||||
|
|
||||||
|
if( timeRef ) clearTimeout( timeRef );
|
||||||
|
timeRef = setTimeout( async () => {
|
||||||
|
pagesChanged
|
||||||
|
? saveFiles( (builder = await createBuilder( Array.from( pagesPaths, p => relative( __dirname, p ) ) )), layoutChanged )
|
||||||
|
: saveFiles( await builder.rebuild(), layoutChanged );
|
||||||
|
}, 200 );
|
||||||
|
}
|
||||||
|
|
||||||
|
const watcher = chokidar
|
||||||
|
.watch( '.', {ignored: s => ignorePath.has( s ) || ignorePath.has( join( './', s ) ), ignoreInitial: true} )
|
||||||
|
.on( 'change', ( path, stats ) => changeListener( path, stats, 'change', watcher ) )
|
||||||
|
.on( 'add', ( path, stats ) => changeListener( path, stats, 'add', watcher ) )
|
||||||
|
.on( 'unlink', ( path, stats ) => changeListener( path, stats, 'unlink', watcher ) )
|
||||||
|
.on( 'ready', () => {
|
||||||
|
console.log( `watching ${sum( Object.values( watcher.getWatched() ).map( t => t.length ) )} files/dirs for changes` );
|
||||||
|
watcherReady = true;
|
||||||
|
} )
|
||||||
|
.on( 'error', err => console.log( 'ERROR:', err ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
const FiveServer = require( 'five-server' ).default;
|
||||||
|
serve &&
|
||||||
|
(await new FiveServer().start( {
|
||||||
|
open: true,
|
||||||
|
workspace: __dirname,
|
||||||
|
ignore: [...ignorePath, /\.(js|ts|svelte)$/, /\_layout\.html$/],
|
||||||
|
wait: 500,
|
||||||
|
} ));
|
||||||
|
})();
|
||||||
19
jsconfig.json
Normal file
19
jsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"extends": "./.svelte-kit/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"moduleResolution": "bundler"
|
||||||
|
}
|
||||||
|
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||||
|
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||||
|
//
|
||||||
|
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||||
|
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||||
|
}
|
||||||
1403
package-lock.json
generated
Normal file
1403
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
27
package.json
Normal file
27
package.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "project",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite dev",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"prepare": "svelte-kit sync || echo ''",
|
||||||
|
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
||||||
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@sveltejs/adapter-auto": "^4.0.0",
|
||||||
|
"@sveltejs/kit": "^2.16.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
||||||
|
"svelte": "^5.0.0",
|
||||||
|
"svelte-check": "^4.0.0",
|
||||||
|
"typescript": "^5.0.0",
|
||||||
|
"vite": "^6.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"leaflet": "^1.9.4",
|
||||||
|
"svelte-map-leaflet": "^0.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/app.css
Normal file
3
src/app.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
// See https://svelte.dev/docs/kit/types#app.d.ts
|
||||||
|
// for information about these interfaces
|
||||||
|
declare global {
|
||||||
|
namespace App {
|
||||||
|
// interface Error {}
|
||||||
|
// interface Locals {}
|
||||||
|
// interface PageData {}
|
||||||
|
// interface PageState {}
|
||||||
|
// interface Platform {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {};
|
||||||
12
src/app.html
Normal file
12
src/app.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
%sveltekit.head%
|
||||||
|
</head>
|
||||||
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
1
src/lib/index.js
Normal file
1
src/lib/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
// place files you want to import through the `$lib` alias in this folder.
|
||||||
7
src/routes/+page.svelte
Normal file
7
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<script>
|
||||||
|
import Map from './leaflet.svelte';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<Map />
|
||||||
|
</main>
|
||||||
1
src/routes/+page.ts
Normal file
1
src/routes/+page.ts
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export const ssr =false;
|
||||||
310
src/routes/leaflet.svelte
Normal file
310
src/routes/leaflet.svelte
Normal file
|
|
@ -0,0 +1,310 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import * as L from 'leaflet';
|
||||||
|
import 'leaflet/dist/leaflet.css';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {{ removeLayer: (arg0: any) => void; setView: (arg0: number[], arg1: any) => void; getZoom: () => any; on: (arg0: string, arg1: (e: any) => void) => void; }}
|
||||||
|
*/
|
||||||
|
let map;
|
||||||
|
let mouseLat = 0;
|
||||||
|
let mouseLng = 0;
|
||||||
|
/**
|
||||||
|
* @type {null}
|
||||||
|
*/
|
||||||
|
let marker = null;
|
||||||
|
|
||||||
|
let startPoint = 'Custom';
|
||||||
|
let startHeight = 0;
|
||||||
|
let startTime = '13:13';
|
||||||
|
let startDate = new Date(2025, 2, 24);
|
||||||
|
let ascentRate = 5;
|
||||||
|
let burstAltitude = 30000;
|
||||||
|
let flightProfile = 'Normal';
|
||||||
|
let descentRate = 5;
|
||||||
|
let forecastMode = 'Single';
|
||||||
|
let inputLat = '56.3576';
|
||||||
|
let inputLng = '39.8666';
|
||||||
|
|
||||||
|
const updateMapPosition = () => {
|
||||||
|
const lat = parseFloat(inputLat);
|
||||||
|
const lng = parseFloat(inputLng);
|
||||||
|
|
||||||
|
if (isNaN(lat)) {
|
||||||
|
alert("Please enter a valid latitude");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isNaN(lng)) {
|
||||||
|
alert("Please enter a valid longitude");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lat < -90 || lat > 90) {
|
||||||
|
alert("Latitude must be between -90 and 90");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (lng < -180 || lng > 180) {
|
||||||
|
alert("Longitude must be between -180 and 180");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove existing marker if it exists
|
||||||
|
if (marker) {
|
||||||
|
map.removeLayer(marker);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new marker
|
||||||
|
marker = L.marker([lat, lng]).addTo(map)
|
||||||
|
.bindPopup("Launch Point");
|
||||||
|
|
||||||
|
// Center map on new coordinates
|
||||||
|
map.setView([lat, lng], map.getZoom());
|
||||||
|
};
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
map = L.map('map').setView([51.505, -0.09], 13);
|
||||||
|
|
||||||
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||||
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
|
||||||
|
}).addTo(map);
|
||||||
|
|
||||||
|
map.on('mousemove', (e) => {
|
||||||
|
mouseLat = e.latlng.lat.toFixed(6);
|
||||||
|
mouseLng = e.latlng.lng.toFixed(6);
|
||||||
|
});
|
||||||
|
|
||||||
|
marker = L.marker([56.3576, 39.8666]).addTo(map)
|
||||||
|
.bindPopup(() => {
|
||||||
|
return `
|
||||||
|
<b>Launch Point</b><br>, Lat: ${marker.getLatLng().lat.toFixed(6)}<br>, Long: ${marker.getLatLng().lng.toFixed(6)}<br>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="map-container">
|
||||||
|
<div id="map"></div>
|
||||||
|
<div class="coordinates-display">
|
||||||
|
Lat: {mouseLat}, Long: {mouseLng}
|
||||||
|
</div>
|
||||||
|
<div class="control-panel">
|
||||||
|
<div class="control-header">Launch Point</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Start Point:</span>
|
||||||
|
<select bind:value={startPoint}>
|
||||||
|
<option>Custom</option>
|
||||||
|
<option>Preset 1</option>
|
||||||
|
<option>Preset 2</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Latitude/Longitude:</span>
|
||||||
|
<div class="coordinate-inputs">
|
||||||
|
<input type="text" bind:value={inputLat} placeholder="Latitude">
|
||||||
|
<p>/</p>
|
||||||
|
<input type="text" bind:value={inputLng} placeholder="Longitude">
|
||||||
|
<button on:click={updateMapPosition} class="update-button">✓</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<button class="map-button">Specify on map</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Launch Height (m):</span>
|
||||||
|
<input type="number" bind:value={startHeight}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Launch Time (UTC):</span>
|
||||||
|
<input type="time" bind:value={startTime}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Launch Date:</span>
|
||||||
|
<input type="date" bind:value={startDate}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Ascent Rate (m/s):</span>
|
||||||
|
<input type="number" bind:value={ascentRate}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Burst/Drift Altitude (m):</span>
|
||||||
|
<input type="number" bind:value={burstAltitude}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Flight Profile:</span>
|
||||||
|
<select bind:value={flightProfile}>
|
||||||
|
<option>Normal</option>
|
||||||
|
<option>Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row buttons">
|
||||||
|
<button>Open Burst Calculator</button>
|
||||||
|
<button>Set Custom Flight Profile</button>
|
||||||
|
<button>Show Last Altitude Graph</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Descent Rate (m/s):</span>
|
||||||
|
<input type="number" bind:value={descentRate}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<span>Forecast Mode (help):</span>
|
||||||
|
<div class="radio-group">
|
||||||
|
<label>
|
||||||
|
<input type="radio" bind:group={forecastMode} value="Single"> Single
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
<input type="radio" bind:group={forecastMode} value="Multiple"> Multiple
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-row">
|
||||||
|
<button class="primary-button">Get Forecast</button>
|
||||||
|
<button>Save Point</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.map-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinates-display {
|
||||||
|
position: absolute;
|
||||||
|
top: 10px;
|
||||||
|
right: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 1000; /* Ensure it's above the map */
|
||||||
|
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.control-panel {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
z-index: 1000;
|
||||||
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||||
|
width: 320px;
|
||||||
|
max-height: 80vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-header {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row span {
|
||||||
|
width: 160px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-row input[type="number"],
|
||||||
|
.control-row input[type="date"],
|
||||||
|
.control-row input[type="time"],
|
||||||
|
.control-row select {
|
||||||
|
width: 120px;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinate-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinate-inputs input {
|
||||||
|
width: 70px !important;
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.update-button {
|
||||||
|
padding: 3px 8px;
|
||||||
|
background: #4CAF50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 5px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons button {
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primary-button {
|
||||||
|
background: #4CAF50 !important;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #3e8e41 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-group label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
13
svelte.config.js
Normal file
13
svelte.config.js
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
import adapter from '@sveltejs/adapter-auto';
|
||||||
|
|
||||||
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
|
const config = {
|
||||||
|
kit: {
|
||||||
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
8
tailwind.config.js
Normal file
8
tailwind.config.js
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
|
theme: {
|
||||||
|
extend: {}
|
||||||
|
},
|
||||||
|
plugins: []
|
||||||
|
};
|
||||||
6
vite.config.js
Normal file
6
vite.config.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [sveltekit()]
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue