mirror of https://github.com/usememos/memos.git
Merge cfdc67e7d6 into 8d8cc83fd8
This commit is contained in:
commit
a5ed2ce3c6
|
|
@ -405,6 +405,8 @@ message UserSetting {
|
|||
// This references a CSS file in the web/public/themes/ directory.
|
||||
// If not set, the default theme will be used.
|
||||
string theme = 4 [(google.api.field_behavior) = OPTIONAL];
|
||||
// The user's map tile layer provider.
|
||||
string map_tile_layer_provider = 5 [(google.api.field_behavior) = OPTIONAL];
|
||||
}
|
||||
|
||||
// User authentication sessions configuration.
|
||||
|
|
|
|||
|
|
@ -2244,9 +2244,11 @@ type UserSetting_GeneralSetting struct {
|
|||
// The preferred theme of the user.
|
||||
// This references a CSS file in the web/public/themes/ directory.
|
||||
// If not set, the default theme will be used.
|
||||
Theme string `protobuf:"bytes,4,opt,name=theme,proto3" json:"theme,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
Theme string `protobuf:"bytes,4,opt,name=theme,proto3" json:"theme,omitempty"`
|
||||
// The user's map tile layer provider.
|
||||
MapTileLayerProvider string `protobuf:"bytes,5,opt,name=map_tile_layer_provider,json=mapTileLayerProvider,proto3" json:"map_tile_layer_provider,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *UserSetting_GeneralSetting) Reset() {
|
||||
|
|
@ -2300,6 +2302,13 @@ func (x *UserSetting_GeneralSetting) GetTheme() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *UserSetting_GeneralSetting) GetMapTileLayerProvider() string {
|
||||
if x != nil {
|
||||
return x.MapTileLayerProvider
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// User authentication sessions configuration.
|
||||
type UserSetting_SessionsSetting struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
|
|
@ -2604,17 +2613,18 @@ const file_api_v1_user_service_proto_rawDesc = "" +
|
|||
"\x11memos.api.v1/UserR\x04name\"\x19\n" +
|
||||
"\x17ListAllUserStatsRequest\"I\n" +
|
||||
"\x18ListAllUserStatsResponse\x12-\n" +
|
||||
"\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xb3\a\n" +
|
||||
"\x05stats\x18\x01 \x03(\v2\x17.memos.api.v1.UserStatsR\x05stats\"\xf0\a\n" +
|
||||
"\vUserSetting\x12\x17\n" +
|
||||
"\x04name\x18\x01 \x01(\tB\x03\xe0A\bR\x04name\x12S\n" +
|
||||
"\x0fgeneral_setting\x18\x02 \x01(\v2(.memos.api.v1.UserSetting.GeneralSettingH\x00R\x0egeneralSetting\x12V\n" +
|
||||
"\x10sessions_setting\x18\x03 \x01(\v2).memos.api.v1.UserSetting.SessionsSettingH\x00R\x0fsessionsSetting\x12c\n" +
|
||||
"\x15access_tokens_setting\x18\x04 \x01(\v2-.memos.api.v1.UserSetting.AccessTokensSettingH\x00R\x13accessTokensSetting\x12V\n" +
|
||||
"\x10webhooks_setting\x18\x05 \x01(\v2).memos.api.v1.UserSetting.WebhooksSettingH\x00R\x0fwebhooksSetting\x1av\n" +
|
||||
"\x10webhooks_setting\x18\x05 \x01(\v2).memos.api.v1.UserSetting.WebhooksSettingH\x00R\x0fwebhooksSetting\x1a\xb2\x01\n" +
|
||||
"\x0eGeneralSetting\x12\x1b\n" +
|
||||
"\x06locale\x18\x01 \x01(\tB\x03\xe0A\x01R\x06locale\x12,\n" +
|
||||
"\x0fmemo_visibility\x18\x03 \x01(\tB\x03\xe0A\x01R\x0ememoVisibility\x12\x19\n" +
|
||||
"\x05theme\x18\x04 \x01(\tB\x03\xe0A\x01R\x05theme\x1aH\n" +
|
||||
"\x05theme\x18\x04 \x01(\tB\x03\xe0A\x01R\x05theme\x12:\n" +
|
||||
"\x17map_tile_layer_provider\x18\x05 \x01(\tB\x03\xe0A\x01R\x14mapTileLayerProvider\x1aH\n" +
|
||||
"\x0fSessionsSetting\x125\n" +
|
||||
"\bsessions\x18\x01 \x03(\v2\x19.memos.api.v1.UserSessionR\bsessions\x1aY\n" +
|
||||
"\x13AccessTokensSetting\x12B\n" +
|
||||
|
|
|
|||
|
|
@ -3459,6 +3459,9 @@ components:
|
|||
theme:
|
||||
type: string
|
||||
description: "The preferred theme of the user.\r\n This references a CSS file in the web/public/themes/ directory.\r\n If not set, the default theme will be used."
|
||||
mapTileLayerProvider:
|
||||
type: string
|
||||
description: The user's map tile layer provider.
|
||||
description: General user settings configuration.
|
||||
UserSetting_SessionsSetting:
|
||||
type: object
|
||||
|
|
|
|||
|
|
@ -239,9 +239,11 @@ type GeneralUserSetting struct {
|
|||
MemoVisibility string `protobuf:"bytes,2,opt,name=memo_visibility,json=memoVisibility,proto3" json:"memo_visibility,omitempty"`
|
||||
// The user's theme preference.
|
||||
// This references a CSS file in the web/public/themes/ directory.
|
||||
Theme string `protobuf:"bytes,3,opt,name=theme,proto3" json:"theme,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
Theme string `protobuf:"bytes,3,opt,name=theme,proto3" json:"theme,omitempty"`
|
||||
// The user's map tile layer provider.
|
||||
MapTileLayerProvider string `protobuf:"bytes,4,opt,name=map_tile_layer_provider,json=mapTileLayerProvider,proto3" json:"map_tile_layer_provider,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GeneralUserSetting) Reset() {
|
||||
|
|
@ -295,6 +297,13 @@ func (x *GeneralUserSetting) GetTheme() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (x *GeneralUserSetting) GetMapTileLayerProvider() string {
|
||||
if x != nil {
|
||||
return x.MapTileLayerProvider
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
type SessionsUserSetting struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Sessions []*SessionsUserSetting_Session `protobuf:"bytes,1,rep,name=sessions,proto3" json:"sessions,omitempty"`
|
||||
|
|
@ -823,11 +832,12 @@ const file_store_user_setting_proto_rawDesc = "" +
|
|||
"\rACCESS_TOKENS\x10\x03\x12\r\n" +
|
||||
"\tSHORTCUTS\x10\x04\x12\f\n" +
|
||||
"\bWEBHOOKS\x10\x05B\a\n" +
|
||||
"\x05value\"k\n" +
|
||||
"\x05value\"\xa2\x01\n" +
|
||||
"\x12GeneralUserSetting\x12\x16\n" +
|
||||
"\x06locale\x18\x01 \x01(\tR\x06locale\x12'\n" +
|
||||
"\x0fmemo_visibility\x18\x02 \x01(\tR\x0ememoVisibility\x12\x14\n" +
|
||||
"\x05theme\x18\x03 \x01(\tR\x05theme\"\xf3\x03\n" +
|
||||
"\x05theme\x18\x03 \x01(\tR\x05theme\x125\n" +
|
||||
"\x17map_tile_layer_provider\x18\x04 \x01(\tR\x14mapTileLayerProvider\"\xf3\x03\n" +
|
||||
"\x13SessionsUserSetting\x12D\n" +
|
||||
"\bsessions\x18\x01 \x03(\v2(.memos.store.SessionsUserSetting.SessionR\bsessions\x1a\xfd\x01\n" +
|
||||
"\aSession\x12\x1d\n" +
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ message GeneralUserSetting {
|
|||
// The user's theme preference.
|
||||
// This references a CSS file in the web/public/themes/ directory.
|
||||
string theme = 3;
|
||||
// The user's map tile layer provider.
|
||||
string map_tile_layer_provider = 4;
|
||||
}
|
||||
|
||||
message SessionsUserSetting {
|
||||
|
|
|
|||
|
|
@ -305,9 +305,10 @@ func (s *APIV1Service) DeleteUser(ctx context.Context, request *v1pb.DeleteUserR
|
|||
|
||||
func getDefaultUserGeneralSetting() *v1pb.UserSetting_GeneralSetting {
|
||||
return &v1pb.UserSetting_GeneralSetting{
|
||||
Locale: "en",
|
||||
MemoVisibility: "PRIVATE",
|
||||
Theme: "",
|
||||
Locale: "en",
|
||||
MemoVisibility: "PRIVATE",
|
||||
Theme: "",
|
||||
MapTileLayerProvider: "",
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -390,9 +391,10 @@ func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.Upda
|
|||
}
|
||||
|
||||
updatedGeneral := &v1pb.UserSetting_GeneralSetting{
|
||||
MemoVisibility: generalSetting.GetMemoVisibility(),
|
||||
Locale: generalSetting.GetLocale(),
|
||||
Theme: generalSetting.GetTheme(),
|
||||
MemoVisibility: generalSetting.GetMemoVisibility(),
|
||||
Locale: generalSetting.GetLocale(),
|
||||
Theme: generalSetting.GetTheme(),
|
||||
MapTileLayerProvider: generalSetting.GetMapTileLayerProvider(),
|
||||
}
|
||||
|
||||
// Apply updates for fields specified in the update mask
|
||||
|
|
@ -405,6 +407,8 @@ func (s *APIV1Service) UpdateUserSetting(ctx context.Context, request *v1pb.Upda
|
|||
updatedGeneral.Theme = incomingGeneral.Theme
|
||||
case "locale":
|
||||
updatedGeneral.Locale = incomingGeneral.Locale
|
||||
case "mapTileLayerProvider":
|
||||
updatedGeneral.MapTileLayerProvider = incomingGeneral.MapTileLayerProvider
|
||||
default:
|
||||
// Ignore unsupported fields
|
||||
}
|
||||
|
|
@ -1166,9 +1170,10 @@ func convertUserSettingFromStore(storeSetting *storepb.UserSetting, userID int32
|
|||
if general := storeSetting.GetGeneral(); general != nil {
|
||||
setting.Value = &v1pb.UserSetting_GeneralSetting_{
|
||||
GeneralSetting: &v1pb.UserSetting_GeneralSetting{
|
||||
Locale: general.Locale,
|
||||
MemoVisibility: general.MemoVisibility,
|
||||
Theme: general.Theme,
|
||||
Locale: general.Locale,
|
||||
MemoVisibility: general.MemoVisibility,
|
||||
Theme: general.Theme,
|
||||
MapTileLayerProvider: general.MapTileLayerProvider,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1254,9 +1259,10 @@ func convertUserSettingToStore(apiSetting *v1pb.UserSetting, userID int32, key s
|
|||
if general := apiSetting.GetGeneralSetting(); general != nil {
|
||||
storeSetting.Value = &storepb.UserSetting_General{
|
||||
General: &storepb.GeneralUserSetting{
|
||||
Locale: general.Locale,
|
||||
MemoVisibility: general.MemoVisibility,
|
||||
Theme: general.Theme,
|
||||
Locale: general.Locale,
|
||||
MemoVisibility: general.MemoVisibility,
|
||||
Theme: general.Theme,
|
||||
MapTileLayerProvider: general.MapTileLayerProvider,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { MapPinIcon } from "lucide-react";
|
|||
import { useEffect, useState } from "react";
|
||||
import ReactDOMServer from "react-dom/server";
|
||||
import { MapContainer, Marker, TileLayer, useMapEvents } from "react-leaflet";
|
||||
import { userStore } from "@/store";
|
||||
|
||||
const markerIcon = new DivIcon({
|
||||
className: "relative border-none",
|
||||
|
|
@ -48,11 +49,22 @@ interface MapProps {
|
|||
|
||||
const DEFAULT_CENTER_LAT_LNG = new LatLng(48.8584, 2.2945);
|
||||
|
||||
// Create a mapping for common map tile providers. If the user-supplied mapTileLayerProvider is not in the map, use it directly as the tile layer URL.
|
||||
|
||||
const generalSetting = userStore.state.userGeneralSetting;
|
||||
|
||||
const LeafletMap = (props: MapProps) => {
|
||||
const position = props.latlng || DEFAULT_CENTER_LAT_LNG;
|
||||
// Default to OpenStreetMap if not set, otherwise use the set value as the URL
|
||||
let tileLayerUrl = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png";
|
||||
const tileLayerProvider = generalSetting?.mapTileLayerProvider;
|
||||
if (tileLayerProvider && tileLayerProvider.trim() !== "") {
|
||||
tileLayerUrl = tileLayerProvider;
|
||||
}
|
||||
|
||||
return (
|
||||
<MapContainer className="w-full h-72" center={position} zoom={13} scrollWheelZoom={false}>
|
||||
<TileLayer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" />
|
||||
<TileLayer url={tileLayerUrl} />
|
||||
<LocationMarker position={position} readonly={props.readonly} onChange={props.onChange ? props.onChange : () => {}} />
|
||||
</MapContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
import { ExternalLinkIcon, Settings2Icon } from "lucide-react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { useTranslate } from "@/utils/i18n";
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// 内置地图模板配置
|
||||
const BUILTIN_TEMPLATES = {
|
||||
openstreetmap: {
|
||||
name: "OpenStreetMap",
|
||||
url: "",
|
||||
wiki: "https://wiki.openstreetmap.org/wiki/Tiles",
|
||||
requiresToken: false,
|
||||
},
|
||||
cartodb: {
|
||||
name: "CartoDB",
|
||||
url: "https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png",
|
||||
wiki: "https://carto.com/help/building-maps/basemap-list/",
|
||||
requiresToken: false,
|
||||
},
|
||||
google: {
|
||||
name: "Google Maps",
|
||||
url: "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}&key={your_token}",
|
||||
wiki: "https://developers.google.com/maps/documentation/tile/overview",
|
||||
requiresToken: true,
|
||||
},
|
||||
apple: {
|
||||
name: "Apple Maps",
|
||||
url: "https://c.tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
wiki: "https://developer.apple.com/maps/",
|
||||
requiresToken: true,
|
||||
},
|
||||
bing: {
|
||||
name: "Bing Maps",
|
||||
url: "https://ecn.t3.tiles.virtualearth.net/tiles/a{q}.jpeg?g=1&key={your_token}",
|
||||
wiki: "https://docs.microsoft.com/en-us/bingmaps/rest-services/imagery/get-imagery-metadata",
|
||||
requiresToken: true,
|
||||
},
|
||||
mapbox: {
|
||||
name: "Mapbox",
|
||||
url: "https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token={your_token}",
|
||||
wiki: "https://docs.mapbox.com/api/maps/",
|
||||
requiresToken: true,
|
||||
},
|
||||
};
|
||||
|
||||
const MapTileLayerProviderSelect = (props: Props) => {
|
||||
const { value, onValueChange, className } = props;
|
||||
const [isCustomDialogOpen, setIsCustomDialogOpen] = useState(false);
|
||||
const [customUrl, setCustomUrl] = useState("");
|
||||
const [selectedTemplate, setSelectedTemplate] = useState<string>("");
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const t = useTranslate();
|
||||
|
||||
const handleEditClick = () => {
|
||||
setIsCustomDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCustomUrlSubmit = () => {
|
||||
onValueChange(customUrl.trim());
|
||||
setIsCustomDialogOpen(false);
|
||||
setCustomUrl("");
|
||||
setSelectedTemplate("");
|
||||
};
|
||||
|
||||
const handleTemplateSelect = (templateKey: string) => {
|
||||
const template = BUILTIN_TEMPLATES[templateKey as keyof typeof BUILTIN_TEMPLATES];
|
||||
if (template) {
|
||||
setCustomUrl(template.url);
|
||||
setSelectedTemplate(templateKey);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUrlChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
const newValue = e.target.value;
|
||||
setCustomUrl(newValue);
|
||||
|
||||
// If user manually modifies the URL, clear the template selection
|
||||
if (selectedTemplate) {
|
||||
const template = BUILTIN_TEMPLATES[selectedTemplate as keyof typeof BUILTIN_TEMPLATES];
|
||||
if (template && newValue !== template.url) {
|
||||
setSelectedTemplate("");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Auto-resize input based on content
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
const input = inputRef.current;
|
||||
input.style.height = "auto";
|
||||
input.style.height = `${input.scrollHeight}px`;
|
||||
}
|
||||
}, [customUrl]);
|
||||
|
||||
const getDisplayName = () => {
|
||||
if (value === "") return "OpenStreetMap";
|
||||
if (Object.keys(BUILTIN_TEMPLATES).includes(value)) {
|
||||
const template = BUILTIN_TEMPLATES[value as keyof typeof BUILTIN_TEMPLATES];
|
||||
return template ? template.name : "Custom";
|
||||
}
|
||||
return "Custom";
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button variant="outline" onClick={handleEditClick} className={className || "min-w-fit justify-between"}>
|
||||
<span>{getDisplayName()}</span>
|
||||
<Settings2Icon className="h-4 w-4 ml-2" />
|
||||
</Button>
|
||||
|
||||
<Dialog open={isCustomDialogOpen} onOpenChange={setIsCustomDialogOpen}>
|
||||
<DialogContent className="max-w-3xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("setting.preference-section.map-config.title")}</DialogTitle>
|
||||
<DialogDescription>{t("setting.preference-section.map-config.description")}</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-3">
|
||||
<Label>{t("setting.preference-section.map-config.quick-templates")}</Label>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{Object.entries(BUILTIN_TEMPLATES).map(([key, template]) => (
|
||||
<Button
|
||||
key={key}
|
||||
variant={selectedTemplate === key ? "default" : "outline"}
|
||||
size="sm"
|
||||
onClick={() => handleTemplateSelect(key)}
|
||||
className="justify-start text-left h-auto py-3 px-3"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="font-medium">{template.name}</div>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
window.open(template.wiki, "_blank");
|
||||
}}
|
||||
>
|
||||
<ExternalLinkIcon className="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
{template.requiresToken && (
|
||||
<div className="text-xs text-orange-600 mt-1">{t("setting.preference-section.map-config.requires-api-key")}</div>
|
||||
)}
|
||||
</div>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<Label htmlFor="custom-url">{t("setting.preference-section.map-config.tile-server-url")}</Label>
|
||||
<Textarea
|
||||
id="custom-url"
|
||||
placeholder="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
value={customUrl}
|
||||
onChange={handleUrlChange}
|
||||
className="mt-2 min-h-[40px] resize-none"
|
||||
ref={inputRef}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="bg-muted p-3 rounded-md">
|
||||
<h4 className="font-medium mb-2">{t("setting.preference-section.map-config.parameters")}</h4>
|
||||
<ul className="text-sm space-y-1 text-muted-foreground">
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{z}"}</code> {t("setting.preference-section.map-config.zoom-level")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{x}"}</code> {t("setting.preference-section.map-config.tile-x-coordinate")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{y}"}</code> {t("setting.preference-section.map-config.tile-y-coordinate")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{s}"}</code> {t("setting.preference-section.map-config.subdomain")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{r}"}</code> {t("setting.preference-section.map-config.retina-resolution")}
|
||||
</li>
|
||||
<li>
|
||||
<code className="bg-background px-1 rounded">{"{your_token}"}</code>{" "}
|
||||
{t("setting.preference-section.map-config.api-token-placeholder")}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<Button variant="outline" onClick={() => setIsCustomDialogOpen(false)}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleCustomUrlSubmit}>{t("setting.preference-section.map-config.apply")}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MapTileLayerProviderSelect;
|
||||
|
|
@ -7,6 +7,7 @@ import { UserSetting_GeneralSetting } from "@/types/proto/api/v1/user_service";
|
|||
import { useTranslate } from "@/utils/i18n";
|
||||
import { convertVisibilityFromString, convertVisibilityToString } from "@/utils/memo";
|
||||
import LocaleSelect from "../LocaleSelect";
|
||||
import MapTileLayerProviderSelect from "../MapTileLayerProviderSelect";
|
||||
import ThemeSelect from "../ThemeSelect";
|
||||
import VisibilityIcon from "../VisibilityIcon";
|
||||
import WebhookSection from "./WebhookSection";
|
||||
|
|
@ -27,11 +28,16 @@ const PreferencesSection = observer(() => {
|
|||
await userStore.updateUserGeneralSetting({ theme }, ["theme"]);
|
||||
};
|
||||
|
||||
const handleMapApiProviderChange = async (mapApiProvider: string) => {
|
||||
await userStore.updateUserGeneralSetting({ mapTileLayerProvider: mapApiProvider }, ["mapTileLayerProvider"]);
|
||||
};
|
||||
|
||||
// Provide default values if setting is not loaded yet
|
||||
const setting: UserSetting_GeneralSetting = generalSetting || {
|
||||
locale: "en",
|
||||
memoVisibility: "PRIVATE",
|
||||
theme: "default",
|
||||
mapTileLayerProvider: "",
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -71,6 +77,11 @@ const PreferencesSection = observer(() => {
|
|||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-row justify-between items-center mt-2">
|
||||
<span className="truncate">{t("setting.preference-section.map-tile-layer-provider")}</span>
|
||||
<MapTileLayerProviderSelect value={setting.mapTileLayerProvider} onValueChange={handleMapApiProviderChange} />
|
||||
</div>
|
||||
|
||||
<Separator className="my-3" />
|
||||
|
||||
<WebhookSection />
|
||||
|
|
|
|||
|
|
@ -307,7 +307,24 @@
|
|||
"preference-section": {
|
||||
"default-memo-sort-option": "Memo display time",
|
||||
"default-memo-visibility": "Default memo visibility",
|
||||
"theme": "Theme"
|
||||
"theme": "Theme",
|
||||
"map-tile-layer-provider": "Map Tile Layer Provider",
|
||||
"map-tile-layer-provider-custom-url": "Custom Map Tile Layer Provider URL",
|
||||
"map-config": {
|
||||
"title": "Map Tile Server Configuration",
|
||||
"description": "Choose a built-in template or enter a custom URL template. Use Leaflet placeholders like {z}, {x}, {y} for zoom level and tile coordinates. Replace <your_token> with your actual API key when needed.",
|
||||
"quick-templates": "Quick Templates",
|
||||
"tile-server-url": "Tile Server URL Template",
|
||||
"parameters": "Leaflet URL Template Parameters:",
|
||||
"zoom-level": "Zoom level (0-18)",
|
||||
"tile-x-coordinate": "Tile X coordinate",
|
||||
"tile-y-coordinate": "Tile Y coordinate",
|
||||
"subdomain": "Subdomain (a, b, c) for load balancing",
|
||||
"retina-resolution": "Retina resolution (@2x)",
|
||||
"api-token-placeholder": "API token/key placeholder (edit directly in URL)",
|
||||
"requires-api-key": "Requires API Key",
|
||||
"apply": "Apply"
|
||||
}
|
||||
},
|
||||
"sso": "SSO",
|
||||
"sso-section": {
|
||||
|
|
|
|||
|
|
@ -306,7 +306,24 @@
|
|||
"preference-section": {
|
||||
"default-memo-sort-option": "备忘录显示时间",
|
||||
"default-memo-visibility": "默认备忘录可见性",
|
||||
"theme": "主题"
|
||||
"theme": "主题",
|
||||
"map-tile-layer-provider": "地图图层提供商",
|
||||
"map-tile-layer-provider-custom-url": "自定义地图图层提供商URL",
|
||||
"map-config": {
|
||||
"title": "地图图层服务器配置",
|
||||
"description": "选择内置模板或输入自定义URL模板。使用Leaflet占位符如 {z}、{x}、{y} 表示缩放级别和图块坐标。需要时请将 <your_token> 替换为您的实际API密钥。",
|
||||
"quick-templates": "快速模板",
|
||||
"tile-server-url": "图层服务器URL模板",
|
||||
"parameters": "Leaflet URL模板参数:",
|
||||
"zoom-level": "缩放级别 (0-18)",
|
||||
"tile-x-coordinate": "图块X坐标",
|
||||
"tile-y-coordinate": "图块Y坐标",
|
||||
"subdomain": "子域名 (a, b, c) 用于负载均衡",
|
||||
"retina-resolution": "视网膜分辨率 (@2x)",
|
||||
"api-token-placeholder": "API令牌/密钥占位符(直接在URL中编辑)",
|
||||
"requires-api-key": "需要API密钥",
|
||||
"apply": "应用"
|
||||
}
|
||||
},
|
||||
"sso": "单点登录",
|
||||
"sso-section": {
|
||||
|
|
|
|||
|
|
@ -327,6 +327,8 @@ export interface UserSetting_GeneralSetting {
|
|||
* If not set, the default theme will be used.
|
||||
*/
|
||||
theme: string;
|
||||
/** The user's map tile layer provider. */
|
||||
mapTileLayerProvider: string;
|
||||
}
|
||||
|
||||
/** User authentication sessions configuration. */
|
||||
|
|
@ -1718,7 +1720,7 @@ export const UserSetting: MessageFns<UserSetting> = {
|
|||
};
|
||||
|
||||
function createBaseUserSetting_GeneralSetting(): UserSetting_GeneralSetting {
|
||||
return { locale: "", memoVisibility: "", theme: "" };
|
||||
return { locale: "", memoVisibility: "", theme: "", mapTileLayerProvider: "" };
|
||||
}
|
||||
|
||||
export const UserSetting_GeneralSetting: MessageFns<UserSetting_GeneralSetting> = {
|
||||
|
|
@ -1732,6 +1734,9 @@ export const UserSetting_GeneralSetting: MessageFns<UserSetting_GeneralSetting>
|
|||
if (message.theme !== "") {
|
||||
writer.uint32(34).string(message.theme);
|
||||
}
|
||||
if (message.mapTileLayerProvider !== "") {
|
||||
writer.uint32(42).string(message.mapTileLayerProvider);
|
||||
}
|
||||
return writer;
|
||||
},
|
||||
|
||||
|
|
@ -1766,6 +1771,14 @@ export const UserSetting_GeneralSetting: MessageFns<UserSetting_GeneralSetting>
|
|||
message.theme = reader.string();
|
||||
continue;
|
||||
}
|
||||
case 5: {
|
||||
if (tag !== 42) {
|
||||
break;
|
||||
}
|
||||
|
||||
message.mapTileLayerProvider = reader.string();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if ((tag & 7) === 4 || tag === 0) {
|
||||
break;
|
||||
|
|
@ -1783,6 +1796,7 @@ export const UserSetting_GeneralSetting: MessageFns<UserSetting_GeneralSetting>
|
|||
message.locale = object.locale ?? "";
|
||||
message.memoVisibility = object.memoVisibility ?? "";
|
||||
message.theme = object.theme ?? "";
|
||||
message.mapTileLayerProvider = object.mapTileLayerProvider ?? "";
|
||||
return message;
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue