Add database, settings, etc; Update templates; Modernize JS
This commit is contained in:
parent
247ed94362
commit
704d52805f
159
.gitignore
vendored
Normal file
159
.gitignore
vendored
Normal file
@ -0,0 +1,159 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/#use-with-ide
|
||||
.pdm.toml
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
.idea/
|
||||
|
||||
# Local test DB
|
||||
mapmakr.sqlite
|
@ -1,15 +1,70 @@
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Depends, FastAPI, HTTPException, Request, Response
|
||||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from mapmakr.models import Marker, MarkerRead, MarkerCreate, MarkerUpdate, create_db_tables, \
|
||||
get_session
|
||||
from mapmakr.settings import settings
|
||||
|
||||
|
||||
ROOT = Path(__file__).parent
|
||||
|
||||
app = FastAPI()
|
||||
app.mount('/static', StaticFiles(directory='static'), name='static')
|
||||
app.mount('/static', StaticFiles(directory=str(ROOT / 'static')), name='static')
|
||||
templates = Jinja2Templates(directory=str(ROOT / 'templates'))
|
||||
|
||||
|
||||
@app.on_event('startup')
|
||||
def on_startup():
|
||||
create_db_tables()
|
||||
|
||||
|
||||
@app.get('/', response_class=HTMLResponse)
|
||||
async def index():
|
||||
content = (ROOT / 'templates' / 'index.html').open().read()
|
||||
return HTMLResponse(content=content, status_code=200)
|
||||
async def index(request: Request):
|
||||
return templates.TemplateResponse('index.html', {'request': request,
|
||||
'site_title': settings.site_title})
|
||||
|
||||
|
||||
@app.post('/marker', tags=['markers'], response_model=MarkerRead)
|
||||
async def create_marker(marker: MarkerCreate, session: Session = Depends(get_session)) -> Marker:
|
||||
db_marker = Marker.from_orm(marker)
|
||||
session.add(db_marker)
|
||||
session.commit()
|
||||
session.refresh(db_marker)
|
||||
return db_marker
|
||||
|
||||
|
||||
@app.patch('/marker/{marker_id}', tags=['markers'], response_model=MarkerRead)
|
||||
async def update_marker(marker_id: int, marker: MarkerUpdate,
|
||||
session: Session = Depends(get_session)) -> Marker:
|
||||
db_marker = session.get(Marker, marker_id)
|
||||
if not db_marker:
|
||||
raise HTTPException(status_code=404, detail='Marker not found')
|
||||
marker_data = marker.dict(exclude_unset=True)
|
||||
for key, value in marker_data.items():
|
||||
setattr(db_marker, key, value)
|
||||
session.add(db_marker)
|
||||
session.commit()
|
||||
session.refresh(db_marker)
|
||||
return db_marker
|
||||
|
||||
|
||||
@app.get('/marker', tags=['markers'], response_model=List[MarkerRead])
|
||||
async def read_markers(session: Session = Depends(get_session)) -> list[MarkerRead]:
|
||||
markers = session.exec(select(Marker)).all()
|
||||
return markers
|
||||
|
||||
|
||||
@app.delete('/marker/{marker_id}', tags=['markers'])
|
||||
async def delete_marker(marker_id: int, session: Session = Depends(get_session)):
|
||||
db_marker = session.get(Marker, marker_id)
|
||||
if not db_marker:
|
||||
raise HTTPException(status_code=404, detail='Marker not found')
|
||||
session.delete(db_marker)
|
||||
session.commit()
|
||||
return Response(status_code=200)
|
||||
|
12
mapmakr/database.py
Normal file
12
mapmakr/database.py
Normal file
@ -0,0 +1,12 @@
|
||||
from sqlmodel import create_engine
|
||||
|
||||
from mapmakr.settings import settings
|
||||
|
||||
_engine = None
|
||||
|
||||
|
||||
def get_engine():
|
||||
global _engine
|
||||
if not _engine:
|
||||
_engine = create_engine(settings.database_uri, echo=settings.database_echo)
|
||||
return _engine
|
43
mapmakr/models.py
Normal file
43
mapmakr/models.py
Normal file
@ -0,0 +1,43 @@
|
||||
from decimal import Decimal
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
from sqlalchemy.schema import Column
|
||||
from sqlalchemy.types import JSON
|
||||
from sqlmodel import Field, SQLModel, Session
|
||||
|
||||
from mapmakr.database import get_engine
|
||||
|
||||
|
||||
class MarkerBase(SQLModel):
|
||||
name: str
|
||||
longitude: Decimal = Field(default=0)
|
||||
latitude: Decimal = Field(default=0)
|
||||
options: Dict[str, Any] = Field(sa_column=Column(JSON))
|
||||
|
||||
|
||||
class Marker(MarkerBase, table=True): # type: ignore[call-arg]
|
||||
id: Optional[int] = Field(default=None, primary_key=True)
|
||||
|
||||
|
||||
class MarkerCreate(MarkerBase):
|
||||
pass
|
||||
|
||||
|
||||
class MarkerRead(MarkerBase):
|
||||
id: int
|
||||
|
||||
|
||||
class MarkerUpdate(SQLModel):
|
||||
name: Optional[str] = None
|
||||
longitude: Optional[Decimal] = None
|
||||
latitude: Optional[Decimal] = None
|
||||
options: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
def create_db_tables():
|
||||
SQLModel.metadata.create_all(get_engine())
|
||||
|
||||
|
||||
def get_session():
|
||||
with Session(get_engine()) as session:
|
||||
yield session
|
10
mapmakr/settings.py
Normal file
10
mapmakr/settings.py
Normal file
@ -0,0 +1,10 @@
|
||||
from pydantic import BaseSettings
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
database_uri: str = 'sqlite:///mapmakr.sqlite'
|
||||
database_echo: bool = False
|
||||
site_title: str = 'mapmakr'
|
||||
|
||||
|
||||
settings = Settings()
|
@ -1,172 +1,136 @@
|
||||
/* */
|
||||
let map, drawnItems, drawControl;
|
||||
|
||||
|
||||
var map;
|
||||
var drawnItems;
|
||||
var statsControl;
|
||||
var drawControl;
|
||||
|
||||
|
||||
// http://stackoverflow.com/a/18324384/661150
|
||||
function callAjax(url, callback)
|
||||
{
|
||||
var xmlhttp;
|
||||
// compatible with IE7+, Firefox, Chrome, Opera, Safari
|
||||
xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.onreadystatechange = function(){
|
||||
if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
|
||||
callback(xmlhttp.responseText);
|
||||
}
|
||||
}
|
||||
xmlhttp.open("GET", url, true);
|
||||
xmlhttp.send();
|
||||
}
|
||||
|
||||
|
||||
function gitmap_reload() {
|
||||
callAjax("read.cgi", (function(req) {
|
||||
var map = window.map;
|
||||
drawnItems.clearLayers();
|
||||
|
||||
var objects = JSON.parse(req).objects;
|
||||
var num_objects = objects.length;
|
||||
for (var i=0; i<num_objects; i++) {
|
||||
var gitmap_file = objects[i].file;
|
||||
var gitmap_object = objects[i].contents;
|
||||
var marker = L.marker(gitmap_object.location);
|
||||
for (var opt in Object.keys(gitmap_object.options)) {
|
||||
marker.options[opt] = gitmap_object.options[opt];
|
||||
}
|
||||
var popup = '';
|
||||
if (gitmap_file.match(/^office/i)) {
|
||||
marker.options.icon = L.AwesomeMarkers.icon({icon: 'building', prefix: 'fa', markerColor: 'red'});
|
||||
popup = gitmap_file;
|
||||
} else {
|
||||
marker.options.icon = L.AwesomeMarkers.icon({icon: 'person', prefix: 'ion'});
|
||||
text1 = '<a href="http://rover.redhat.com/people/profile/'+gitmap_file+'" target="_blank">roster</a>';
|
||||
text2 = '';
|
||||
popup = gitmap_file+' '+text1+' '+text2;
|
||||
}
|
||||
marker.options.id=gitmap_file;
|
||||
marker.bindLabel(gitmap_file, { noHide: true });
|
||||
marker.bindPopup(popup);
|
||||
drawnItems.addLayer(marker);
|
||||
}
|
||||
|
||||
if (drawControl == null) {
|
||||
function reload_map() {
|
||||
fetch("/marker").then(response => response.json()).then(markers => {
|
||||
// const map = window.map;
|
||||
drawnItems.clearLayers();
|
||||
markers.forEach(marker => {
|
||||
let mapMarker = L.marker({"lat": marker.latitude, "lng": marker.longitude});
|
||||
mapMarker.options["id"] = marker.id;
|
||||
mapMarker.options["title"] = marker.name;
|
||||
for (const opt in marker.options) {
|
||||
mapMarker.options[opt] = marker.options[opt];
|
||||
}
|
||||
mapMarker.bindLabel(marker.name, {noHide: true});
|
||||
mapMarker.bindPopup(marker.name);
|
||||
drawnItems.addLayer(mapMarker);
|
||||
});
|
||||
if (!drawControl) {
|
||||
// add drawControl only after some drawnItems exist, so
|
||||
// it is not shown disabled
|
||||
drawControl = new L.Control.Draw({
|
||||
draw: {
|
||||
position: 'topleft',
|
||||
polygon: false,
|
||||
rectangle: false,
|
||||
polyline: false,
|
||||
circle: false
|
||||
},
|
||||
edit: {
|
||||
featureGroup: drawnItems
|
||||
}
|
||||
});
|
||||
map.addControl(drawControl);
|
||||
}
|
||||
}));
|
||||
callAjax("summary.cgi", function(req) {
|
||||
var sc = window.statsControl;
|
||||
var leaflet = '<a href="http://leafletjs.com">Leaflet</a>';
|
||||
sc.setPrefix (req + ' | ' + leaflet);
|
||||
});
|
||||
draw: {
|
||||
position: 'topleft',
|
||||
polygon: false,
|
||||
rectangle: false,
|
||||
polyline: false,
|
||||
circle: false
|
||||
},
|
||||
edit: {
|
||||
featureGroup: drawnItems
|
||||
}
|
||||
});
|
||||
console.log(map);
|
||||
console.log(drawControl);
|
||||
map.addControl(drawControl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setup_map() {
|
||||
let osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
||||
|
||||
function gitmap_setup() {
|
||||
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
||||
osmAttrib = '© <a href="http://openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
||||
osm = L.tileLayer(osmUrl, {maxZoom: 18, attribution: osmAttrib});
|
||||
map = new L.Map('mapmakr', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 2 });
|
||||
|
||||
map = new L.Map('map', {layers: [osm], attributionControl: false, center: new L.LatLng(0, 0), zoom: 2 });
|
||||
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45});
|
||||
map.addLayer(drawnItems);
|
||||
reload_map();
|
||||
|
||||
drawnItems = new L.MarkerClusterGroup({maxClusterRadius: 45}); // new L.FeatureGroup();
|
||||
map.addLayer(drawnItems);
|
||||
gitmap_reload();
|
||||
const refreshButton = new L.easyButton('icon ion-refresh', function() {reload_map();});
|
||||
refreshButton.button.style.fontSize = '18px';
|
||||
map.addControl(refreshButton);
|
||||
|
||||
// drawcontrol formerly added here
|
||||
const helpButton = new L.easyButton('icon ion-help-circled', function() {open("about.html");});
|
||||
helpButton.button.style.fontSize = '18px';
|
||||
map.addControl(helpButton);
|
||||
|
||||
var refreshButton = new L.easyButton('icon ion-refresh', function() {gitmap_reload();});
|
||||
refreshButton.button.style.fontSize = '18px';
|
||||
map.addControl (refreshButton);
|
||||
const controlSearch = new L.Control.Search({layer: drawnItems, propertyName: 'id', initial: false, zoom: 12});
|
||||
map.addControl(controlSearch);
|
||||
|
||||
var helpButton = new L.easyButton('icon ion-help-circled', function() {open("about.html");});
|
||||
helpButton.button.style.fontSize = '18px';
|
||||
map.addControl (helpButton);
|
||||
statsControl = new L.control.attribution({position:'bottomright', prefix:''});
|
||||
map.addControl(statsControl);
|
||||
|
||||
var controlSearch = new L.Control.Search({layer: drawnItems,
|
||||
propertyName: 'id',
|
||||
initial: false,
|
||||
zoom: 12});
|
||||
map.addControl( controlSearch );
|
||||
scaleControl = new L.control.scale();
|
||||
map.addControl(scaleControl);
|
||||
|
||||
statsControl = new L.control.attribution({position:'bottomright', prefix:''});
|
||||
map.addControl(statsControl);
|
||||
terminator = new L.terminator();
|
||||
terminator.options.opacity = 0.2;
|
||||
terminator.options.fillOpacity = 0.2;
|
||||
terminator.addTo(map);
|
||||
setInterval(() => updateTerminator(terminator), 60000); // 1 minute
|
||||
|
||||
scaleControl = new L.control.scale();
|
||||
map.addControl(scaleControl);
|
||||
function updateTerminator(t) {
|
||||
var t2 = L.terminator();
|
||||
t.setLatLngs(t2.getLatLngs());
|
||||
t.redraw();
|
||||
}
|
||||
|
||||
terminator = new L.terminator();
|
||||
terminator.options.opacity=0.2;
|
||||
terminator.options.fillOpacity=0.2;
|
||||
terminator.addTo(map);
|
||||
setInterval(function() {updateTerminator(terminator);}, 60000); // 1 minute
|
||||
function updateTerminator(t) {
|
||||
var t2 = L.terminator();
|
||||
t.setLatLngs(t2.getLatLngs());
|
||||
t.redraw();
|
||||
// called after new marker is created
|
||||
map.on('draw:created', function (e) {
|
||||
let type = e.layerType, layer = e.layer;
|
||||
if (type !== 'marker') {
|
||||
return;
|
||||
}
|
||||
|
||||
// called after new marker is created
|
||||
map.on('draw:created', function (e) {
|
||||
var type = e.layerType,
|
||||
layer = e.layer;
|
||||
if (type !== 'marker') return;
|
||||
let name = prompt("Marker name:", "");
|
||||
if (name) {
|
||||
layer.options.id = null;
|
||||
layer.options.title = name;
|
||||
layer.bindPopup(name);
|
||||
drawnItems.addLayer(layer);
|
||||
let requestOptions = {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"name": name,
|
||||
"latitude": layer.getLatLng()["lat"],
|
||||
"longitude": layer.getLatLng()["lng"],
|
||||
"options": {}
|
||||
})
|
||||
};
|
||||
fetch("/marker", requestOptions).then(() => reload_map());
|
||||
}
|
||||
});
|
||||
|
||||
var userid = prompt("Marker name (e.g., kerberos userid):", "");
|
||||
if (userid != null) {
|
||||
layer.options.id=userid;
|
||||
layer.bindPopup(userid);
|
||||
drawnItems.addLayer(layer);
|
||||
callAjax("write.cgi?op=new"+
|
||||
"&id="+encodeURIComponent(userid)+
|
||||
"&location="+encodeURIComponent(JSON.stringify(layer.getLatLng()))+
|
||||
"&options="+encodeURIComponent("{}"),
|
||||
function(req){alert("response:\n"+req);
|
||||
gitmap_reload();});
|
||||
}
|
||||
map.on('draw:edited', function (e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
let type = layer.layerType, id = layer.options.id;
|
||||
let requestOptions = {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
"name": layer.options.name,
|
||||
"latitude": layer.getLatLng()["lat"],
|
||||
"longitude": layer.getLatLng()["lng"]
|
||||
})
|
||||
};
|
||||
fetch("/marker/" + id, requestOptions).then(() => reload_map());
|
||||
});
|
||||
});
|
||||
|
||||
map.on('draw:edited', function (e) {
|
||||
e.layers.eachLayer(function (layer) {
|
||||
var type = layer.layerType;
|
||||
var userid = layer.options.id;
|
||||
callAjax("write.cgi?op=edit"+
|
||||
"&id="+encodeURIComponent(userid)+
|
||||
"&location="+encodeURIComponent(JSON.stringify(layer.getLatLng()))+
|
||||
"&options="+encodeURIComponent("{}"),
|
||||
function(req){alert("response:\n"+req);
|
||||
gitmap_reload();});
|
||||
});
|
||||
map.on('draw:deleted', function (e) {
|
||||
e.layers.eachLayer(layer => {
|
||||
let type = layer.layerType, id = layer.options.id;
|
||||
fetch("/marker/" + id, {method: "DELETE"}).then(() => reload_map());
|
||||
});
|
||||
|
||||
map.on('draw:deleted', function (e) {
|
||||
e.layers.eachLayer(function (layer) {
|
||||
var type = layer.layerType;
|
||||
var userid = layer.options.id;
|
||||
callAjax("write.cgi?op=delete"+
|
||||
"&id="+encodeURIComponent(userid),
|
||||
function(req){alert("response:\n"+req);
|
||||
gitmap_reload();});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
|
||||
});
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
<html><head><title>about gitmap</title></head>
|
||||
<html><head><title>about mapmakr</title></head>
|
||||
<body>
|
||||
|
||||
<h1>about gitmap</h1>
|
||||
<h1>about mapmakr</h1>
|
||||
|
||||
<p>gitmap is a simple, small, 100% free/open-source, collaborative map
|
||||
<p>mapmakr is a simple, small, 100% free/open-source, collaborative map
|
||||
editor that runs mostly in a web browser. It integrates community
|
||||
FOSS projects and data sources.</p>
|
||||
|
||||
<b><a href="index.html">run gitmap</a></b>
|
||||
<b><a href="index.html">run mapmakr</a></b>
|
||||
|
||||
<h1>FAQ</h1>
|
||||
|
||||
@ -18,7 +18,7 @@ installation is for locating Red Hat remotees / offices. Other installations
|
||||
may store whatever they like.</p>
|
||||
|
||||
<h2>Who?</h2>
|
||||
<p><a href="mailto:fche@redhat.com">fche</a> (gitmap integration scripts),
|
||||
<p><a href="mailto:fche@redhat.com">fche</a> (mapmakr integration scripts),
|
||||
<a href="http://www.openstreetmap.org">Open Streetmap</a> (background map),
|
||||
<a href="http://www.leafletjs.com">Leaflet</a> (map rendering javascript library + plugins),
|
||||
and others.</p>
|
||||
@ -55,7 +55,7 @@ Click on doomed markers. Click on "save" or "cancel".</p>
|
||||
|
||||
<h2>How does it work?</h2>
|
||||
|
||||
<p><a href="https://gitlab.cee.redhat.com/fche/gitmap">Check it out.</a></p>
|
||||
<p><a href="https://gitlab.cee.redhat.com/fche/mapmakr">Check it out.</a></p>
|
||||
<p>The front-end consists of very small HTML + JavaScript files that
|
||||
load Leaflet and OSM data from the Internet into your browser. The
|
||||
front-end also loads marker data from our own servers via HTTP CGI.
|
||||
@ -77,8 +77,8 @@ the job, and easily.</p>
|
||||
|
||||
<h2>Where to send patches?</h2>
|
||||
|
||||
<p>Find gitmap sources
|
||||
at <tt>https://gitlab.cee.redhat.com/fche/gitmap.git</tt>.
|
||||
<p>Find mapmakr sources
|
||||
at <tt>https://gitlab.cee.redhat.com/fche/mapmakr.git</tt>.
|
||||
One or two third-party javascript libraries are stored there; others
|
||||
are served from upstream projects' external CDNs. Feel free to fork /
|
||||
experiment / deploy. Send patches to <tt>fche</tt> if desired.</p>
|
||||
@ -91,7 +91,7 @@ experiment / deploy. Send patches to <tt>fche</tt> if desired.</p>
|
||||
</ol>
|
||||
Patches are welcome!</p>
|
||||
|
||||
<p>Please note that the version of the gitmap tree
|
||||
<p>Please note that the version of the mapmakr tree
|
||||
in <tt>gitlab.cee</tt> contains snapshots of the real live
|
||||
personal data RH folks have chosen to share. It is obviously
|
||||
confidential.</p>
|
||||
|
@ -1,30 +1,29 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>gitmap editor</title>
|
||||
<title>{{site_title}}</title>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.css" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.css" />
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css"/>
|
||||
<link rel="stylesheet" href="https://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.css" />
|
||||
<link rel="stylesheet" href="3rdparty/leaflet-search.css" />
|
||||
<link rel="stylesheet" href="3rdparty/leaflet-easybutton.css" />
|
||||
<link rel="stylesheet" href="3rdparty/leaflet-label.css" />
|
||||
<link rel="stylesheet" href="3rdparty/MarkerCluster.css"/>
|
||||
<link rel="stylesheet" href="3rdparty/MarkerCluster.Default.css"/>
|
||||
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-search.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-easybutton.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/leaflet-label.css') }}" />
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.css') }}"/>
|
||||
<link rel="stylesheet" href="{{ url_for('static', path='/3rdparty/MarkerCluster.Default.css') }}"/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="mapmakr" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.5/leaflet.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/0.2.3/leaflet.draw.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/Leaflet.awesome-markers/2.0.2/leaflet.awesome-markers.min.js"></script>
|
||||
<script src="3rdparty/leaflet-search.js"></script>
|
||||
<script src="3rdparty/leaflet-easybutton.js"></script>
|
||||
<script src="3rdparty/leaflet-label.js"></script>
|
||||
<script src="3rdparty/L.Terminator.js"></script>
|
||||
<script src="3rdparty/leaflet.markercluster.js"></script>
|
||||
<script src="mapmakr.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="map" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>
|
||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-search.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-easybutton.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='/3rdparty/leaflet-label.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='/3rdparty/L.Terminator.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='/3rdparty/leaflet.markercluster.js') }}"></script>
|
||||
<script src="{{ url_for('static', path='/js/mapmakr.js') }}"></script>
|
||||
<script>setup_map();</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "mapmakr"
|
||||
description = 'An interactive map where you can place your own markers'
|
||||
readme = "README.md"
|
||||
readme = "README.rst"
|
||||
requires-python = ">=3.7"
|
||||
license = "MIT"
|
||||
keywords = []
|
||||
@ -26,7 +26,8 @@ classifiers = [
|
||||
dependencies = [
|
||||
"FastAPI",
|
||||
"Jinja2",
|
||||
"uvicorn[standard]"
|
||||
"uvicorn[standard]",
|
||||
"SQLModel",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user