mirror of
https://github.com/Freika/dawarich.git
synced 2026-01-11 09:41:40 -05:00
commit
42e6345e63
16 changed files with 109 additions and 72 deletions
|
|
@ -1 +1 @@
|
||||||
0.25.9
|
0.25.10
|
||||||
|
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -68,5 +68,7 @@
|
||||||
|
|
||||||
/config/credentials/production.key
|
/config/credentials/production.key
|
||||||
/config/credentials/production.yml.enc
|
/config/credentials/production.yml.enc
|
||||||
|
/config/credentials/staging.key
|
||||||
|
/config/credentials/staging.yml.enc
|
||||||
|
|
||||||
Makefile
|
Makefile
|
||||||
|
|
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
|
|
@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
# 0.25.10 - 2025-05-02
|
||||||
|
|
||||||
|
## Added
|
||||||
|
|
||||||
|
- Vector maps are supported in non-self-hosted mode.
|
||||||
|
|
||||||
|
## Removed
|
||||||
|
|
||||||
|
- Sample points are no longer being imported automatically for new users.
|
||||||
|
|
||||||
# 0.25.9 - 2025-04-29
|
# 0.25.9 - 2025-04-29
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -40,6 +40,7 @@ export default class extends BaseController {
|
||||||
console.log("Map controller connected");
|
console.log("Map controller connected");
|
||||||
|
|
||||||
this.apiKey = this.element.dataset.api_key;
|
this.apiKey = this.element.dataset.api_key;
|
||||||
|
this.selfHosted = this.element.dataset.self_hosted === "true";
|
||||||
this.markers = JSON.parse(this.element.dataset.coordinates);
|
this.markers = JSON.parse(this.element.dataset.coordinates);
|
||||||
this.timezone = this.element.dataset.timezone;
|
this.timezone = this.element.dataset.timezone;
|
||||||
this.userSettings = JSON.parse(this.element.dataset.user_settings);
|
this.userSettings = JSON.parse(this.element.dataset.user_settings);
|
||||||
|
|
@ -425,7 +426,7 @@ export default class extends BaseController {
|
||||||
|
|
||||||
baseMaps() {
|
baseMaps() {
|
||||||
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
let selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||||
let maps = createAllMapLayers(this.map, selectedLayerName);
|
let maps = createAllMapLayers(this.map, selectedLayerName, this.selfHosted);
|
||||||
|
|
||||||
// Add custom map if it exists in settings
|
// Add custom map if it exists in settings
|
||||||
if (this.userSettings.maps && this.userSettings.maps.url) {
|
if (this.userSettings.maps && this.userSettings.maps.url) {
|
||||||
|
|
@ -448,8 +449,28 @@ export default class extends BaseController {
|
||||||
maps[this.userSettings.maps.name] = customLayer;
|
maps[this.userSettings.maps.name] = customLayer;
|
||||||
} else {
|
} else {
|
||||||
// If no custom map is set, ensure a default layer is added
|
// If no custom map is set, ensure a default layer is added
|
||||||
const defaultLayer = maps[selectedLayerName] || maps["OpenStreetMap"] || maps["Atlas"];
|
// First check if maps object has any entries
|
||||||
defaultLayer.addTo(this.map);
|
if (Object.keys(maps).length === 0) {
|
||||||
|
// Fallback to OSM if no maps are configured
|
||||||
|
maps["OpenStreetMap"] = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now try to get the selected layer or fall back to alternatives
|
||||||
|
const defaultLayer = maps[selectedLayerName] || Object.values(maps)[0];
|
||||||
|
|
||||||
|
if (defaultLayer) {
|
||||||
|
defaultLayer.addTo(this.map);
|
||||||
|
} else {
|
||||||
|
console.error("Could not find any default map layer");
|
||||||
|
// Ultimate fallback - create and add OSM layer directly
|
||||||
|
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
|
||||||
|
maxZoom: 19,
|
||||||
|
attribution: "© <a href='http://www.openstreetmap.org/copyright'>OpenStreetMap</a>"
|
||||||
|
}).addTo(this.map);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return maps;
|
return maps;
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export default class extends BaseController {
|
||||||
// Add base map layer
|
// Add base map layer
|
||||||
const selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
const selectedLayerName = this.userSettings.preferred_map_layer || "OpenStreetMap";
|
||||||
const maps = this.baseMaps();
|
const maps = this.baseMaps();
|
||||||
const defaultLayer = maps[selectedLayerName] || maps["OpenStreetMap"] || maps["Atlas"];
|
const defaultLayer = maps[selectedLayerName] || Object.values(maps)[0];
|
||||||
defaultLayer.addTo(this.map);
|
defaultLayer.addTo(this.map);
|
||||||
|
|
||||||
// Add scale control to bottom right
|
// Add scale control to bottom right
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,34 @@
|
||||||
// Import the maps configuration
|
// Import the maps configuration
|
||||||
// In non-self-hosted mode, we need to mount external maps_config.js to the container
|
// In non-self-hosted mode, we need to mount external maps_config.js to the container
|
||||||
import { mapsConfig } from './maps_config';
|
import { mapsConfig as vectorMapsConfig } from './vector_maps_config';
|
||||||
|
import { mapsConfig as rasterMapsConfig } from './raster_maps_config';
|
||||||
|
|
||||||
export function createMapLayer(map, selectedLayerName, layerKey) {
|
export function createMapLayer(map, selectedLayerName, layerKey, selfHosted) {
|
||||||
const config = mapsConfig[layerKey];
|
const config = selfHosted === "true" ? rasterMapsConfig[layerKey] : vectorMapsConfig[layerKey];
|
||||||
|
|
||||||
if (!config) {
|
if (!config) {
|
||||||
console.warn(`No configuration found for layer: ${layerKey}`);
|
console.warn(`No configuration found for layer: ${layerKey}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer = L.tileLayer(config.url, {
|
let layer;
|
||||||
maxZoom: config.maxZoom,
|
console.log("isSelfhosted: ", selfHosted)
|
||||||
attribution: config.attribution,
|
if (selfHosted === "true") {
|
||||||
// Add any other config properties that might be needed
|
layer = L.tileLayer(config.url, {
|
||||||
});
|
maxZoom: config.maxZoom,
|
||||||
|
attribution: config.attribution,
|
||||||
|
crossOrigin: true,
|
||||||
|
// Add any other config properties that might be needed
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
layer = protomapsL.leafletLayer(
|
||||||
|
{
|
||||||
|
url: config.url,
|
||||||
|
flavor: config.flavor,
|
||||||
|
crossOrigin: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedLayerName === layerKey) {
|
if (selectedLayerName === layerKey) {
|
||||||
return layer.addTo(map);
|
return layer.addTo(map);
|
||||||
|
|
@ -24,11 +38,11 @@ export function createMapLayer(map, selectedLayerName, layerKey) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to create all map layers
|
// Helper function to create all map layers
|
||||||
export function createAllMapLayers(map, selectedLayerName) {
|
export function createAllMapLayers(map, selectedLayerName, selfHosted) {
|
||||||
const layers = {};
|
const layers = {};
|
||||||
|
const mapsConfig = selfHosted === "true" ? rasterMapsConfig : vectorMapsConfig;
|
||||||
Object.keys(mapsConfig).forEach(layerKey => {
|
Object.keys(mapsConfig).forEach(layerKey => {
|
||||||
layers[layerKey] = createMapLayer(map, selectedLayerName, layerKey);
|
layers[layerKey] = createMapLayer(map, selectedLayerName, layerKey, selfHosted);
|
||||||
});
|
});
|
||||||
|
|
||||||
return layers;
|
return layers;
|
||||||
|
|
|
||||||
32
app/javascript/maps/vector_maps_config.js
Normal file
32
app/javascript/maps/vector_maps_config.js
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export const mapsConfig = {
|
||||||
|
"Light": {
|
||||||
|
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
|
||||||
|
flavor: "light",
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||||
|
},
|
||||||
|
"Dark": {
|
||||||
|
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
|
||||||
|
flavor: "dark",
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||||
|
},
|
||||||
|
"White": {
|
||||||
|
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
|
||||||
|
flavor: "white",
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||||
|
},
|
||||||
|
"Grayscale": {
|
||||||
|
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
|
||||||
|
flavor: "grayscale",
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||||
|
},
|
||||||
|
"Black": {
|
||||||
|
url: "https://tyles.dwri.xyz/20250420/{z}/{x}/{y}.mvt",
|
||||||
|
flavor: "black",
|
||||||
|
maxZoom: 16,
|
||||||
|
attribution: "<a href='https://github.com/protomaps/basemaps'>Protomaps</a>, © <a href='https://openstreetmap.org'>OpenStreetMap</a>"
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
@ -16,7 +16,6 @@ class User < ApplicationRecord
|
||||||
has_many :trips, dependent: :destroy
|
has_many :trips, dependent: :destroy
|
||||||
|
|
||||||
after_create :create_api_key
|
after_create :create_api_key
|
||||||
after_create :import_sample_points
|
|
||||||
after_commit :activate, on: :create, if: -> { DawarichSettings.self_hosted? }
|
after_commit :activate, on: :create, if: -> { DawarichSettings.self_hosted? }
|
||||||
before_save :sanitize_input
|
before_save :sanitize_input
|
||||||
|
|
||||||
|
|
@ -134,23 +133,4 @@ class User < ApplicationRecord
|
||||||
settings['photoprism_url']&.gsub!(%r{/+\z}, '')
|
settings['photoprism_url']&.gsub!(%r{/+\z}, '')
|
||||||
settings.try(:[], 'maps')&.try(:[], 'url')&.strip!
|
settings.try(:[], 'maps')&.try(:[], 'url')&.strip!
|
||||||
end
|
end
|
||||||
|
|
||||||
# rubocop:disable Metrics/MethodLength
|
|
||||||
def import_sample_points
|
|
||||||
return unless Rails.env.development? ||
|
|
||||||
Rails.env.production? ||
|
|
||||||
(Rails.env.test? && ENV['IMPORT_SAMPLE_POINTS'])
|
|
||||||
|
|
||||||
import = imports.create(
|
|
||||||
name: 'DELETE_ME_this_is_a_demo_import_DELETE_ME',
|
|
||||||
source: 'gpx'
|
|
||||||
)
|
|
||||||
|
|
||||||
import.file.attach(
|
|
||||||
Rack::Test::UploadedFile.new(
|
|
||||||
Rails.root.join('lib/assets/sample_points.gpx'), 'application/xml'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
# rubocop:enable Metrics/MethodLength
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,15 @@
|
||||||
<p class='py-2'>
|
<p class='py-2'>
|
||||||
<p>Docs: <%= link_to "API documentation", '/api-docs', class: 'underline hover:no-underline' %></p>
|
<p>Docs: <%= link_to "API documentation", '/api-docs', class: 'underline hover:no-underline' %></p>
|
||||||
|
|
||||||
Usage example:
|
<h3 class='text-xl font-bold mt-4'>Usage examples</h3>
|
||||||
|
|
||||||
<div role="tablist" class="tabs tabs-boxed">
|
<h3 class='text-lg font-bold mt-4'>OwnTracks</h3>
|
||||||
<input type="radio" name="my_tabs_2" role="tab" class="tab" aria-label="OwnTracks" checked />
|
<p><code><%= api_v1_owntracks_points_url(api_key: current_user.api_key) %></code></p>
|
||||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6">
|
|
||||||
<p><code><%= api_v1_owntracks_points_url(api_key: current_user.api_key) %></code></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="radio" name="my_tabs_2" role="tab" class="tab" aria-label="Overland" />
|
<h3 class='text-lg font-bold mt-4'>Overland</h3>
|
||||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-6">
|
<p><code><%= api_v1_overland_batches_url(api_key: current_user.api_key) %></code></p>
|
||||||
<p><code><%= api_v1_overland_batches_url(api_key: current_user.api_key) %></code></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</p>
|
</p>
|
||||||
<p class='py-2'>
|
<p class='py-2'>
|
||||||
<%= link_to "Generate new API key", generate_api_key_path, data: { confirm: "Are you sure?", turbo_confirm: "Are you sure?", turbo_method: :post }, class: 'btn btn-primary' %>
|
<%= link_to "Generate new API key", generate_api_key_path, data: { confirm: "Are you sure? This will invalidate the current API key.", turbo_confirm: "Are you sure?", turbo_method: :post }, class: 'btn btn-primary' %>
|
||||||
</p>
|
</p>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,8 @@
|
||||||
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag "tailwind", "inter-font", "data-turbo-track": "reload" %>
|
||||||
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
|
||||||
<%= javascript_importmap_tags %>
|
<%= javascript_importmap_tags %>
|
||||||
|
<%= javascript_include_tag "https://unpkg.com/protomaps-leaflet@5.0.0/dist/protomaps-leaflet.js" unless @self_hosted %>
|
||||||
|
|
||||||
<%= render 'application/favicon' %>
|
<%= render 'application/favicon' %>
|
||||||
<%= Sentry.get_trace_propagation_meta.html_safe if Sentry.initialized? %>
|
<%= Sentry.get_trace_propagation_meta.html_safe if Sentry.initialized? %>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
data-points-target="map"
|
data-points-target="map"
|
||||||
data-distance_unit="<%= DISTANCE_UNIT %>"
|
data-distance_unit="<%= DISTANCE_UNIT %>"
|
||||||
data-api_key="<%= current_user.api_key %>"
|
data-api_key="<%= current_user.api_key %>"
|
||||||
|
data-self_hosted="<%= @self_hosted %>"
|
||||||
data-user_settings='<%= current_user.settings.to_json.html_safe %>'
|
data-user_settings='<%= current_user.settings.to_json.html_safe %>'
|
||||||
data-coordinates="<%= @coordinates %>"
|
data-coordinates="<%= @coordinates %>"
|
||||||
data-distance="<%= @distance %>"
|
data-distance="<%= @distance %>"
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,4 @@
|
||||||
<%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %>
|
<%= link_to 'Background Jobs', settings_background_jobs_path, role: 'tab', class: "tab #{active_tab?(settings_background_jobs_path)}" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= link_to 'Map', settings_maps_path, role: 'tab', class: "tab #{active_tab?(settings_maps_path)}" %>
|
<%= link_to 'Map', settings_maps_path, role: 'tab', class: "tab #{active_tab?(settings_maps_path)}" %>
|
||||||
<% if !DawarichSettings.self_hosted? %>
|
|
||||||
<%= link_to 'Subscriptions', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}", role: 'tab', class: "tab" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,10 @@
|
||||||
<ul class="p-2 bg-base-100 rounded-t-none z-10">
|
<ul class="p-2 bg-base-100 rounded-t-none z-10">
|
||||||
<li><%= link_to 'Account', edit_user_registration_path %></li>
|
<li><%= link_to 'Account', edit_user_registration_path %></li>
|
||||||
<li><%= link_to 'Settings', settings_path %></li>
|
<li><%= link_to 'Settings', settings_path %></li>
|
||||||
|
<% if !DawarichSettings.self_hosted? %>
|
||||||
|
<li><%= link_to 'Subscription', "#{MANAGER_URL}/auth/dawarich?token=#{current_user.generate_subscription_token}" %></li>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
|
<li><%= link_to 'Logout', destroy_user_session_path, method: :delete, data: { turbo_method: :delete } %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
|
|
||||||
|
|
@ -56,26 +56,6 @@ RSpec.describe User, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#import_sample_points' do
|
|
||||||
before do
|
|
||||||
ENV['IMPORT_SAMPLE_POINTS'] = 'true'
|
|
||||||
end
|
|
||||||
|
|
||||||
after do
|
|
||||||
ENV['IMPORT_SAMPLE_POINTS'] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a sample import and enqueues an import job' do
|
|
||||||
user = create(:user)
|
|
||||||
|
|
||||||
expect(user.imports.count).to eq(1)
|
|
||||||
expect(user.imports.first.name).to eq('DELETE_ME_this_is_a_demo_import_DELETE_ME')
|
|
||||||
expect(user.imports.first.source).to eq('gpx')
|
|
||||||
|
|
||||||
expect(Import::ProcessJob).to have_been_enqueued.with(user.imports.first.id)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'methods' do
|
describe 'methods' do
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue