feat: Update admin panel
This commit is contained in:
@@ -5,6 +5,7 @@ export interface NavigationItem {
|
||||
label: string;
|
||||
icon: LucideIcon;
|
||||
path?: string;
|
||||
for_admin?: boolean;
|
||||
onClick?: () => void;
|
||||
nestedItems?: NavigationItem[];
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import type { NavigationItem } from "../model";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { Plus } from "lucide-react";
|
||||
import { authStore } from "@shared";
|
||||
|
||||
interface NavigationItemProps {
|
||||
item: NavigationItem;
|
||||
@@ -30,9 +31,21 @@ export const NavigationItemComponent: React.FC<NavigationItemProps> = ({
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const [isExpanded, setIsExpanded] = React.useState(false);
|
||||
const { payload } = authStore;
|
||||
|
||||
// @ts-ignore
|
||||
const isAdmin = payload?.is_admin || false;
|
||||
|
||||
const isActive = item.path ? location.pathname.startsWith(item.path) : false;
|
||||
|
||||
const filteredNestedItems = item.nestedItems?.filter((nestedItem) => {
|
||||
if (nestedItem.for_admin) {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const handleClick = () => {
|
||||
if (item.id === "all" && !open) {
|
||||
onDrawerOpen?.();
|
||||
@@ -108,15 +121,16 @@ export const NavigationItemComponent: React.FC<NavigationItemProps> = ({
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{item.nestedItems &&
|
||||
{filteredNestedItems &&
|
||||
filteredNestedItems.length > 0 &&
|
||||
open &&
|
||||
(isExpanded ? <ExpandLessIcon /> : <ExpandMoreIcon />)}
|
||||
</ListItemButton>
|
||||
</ListItem>
|
||||
{item.nestedItems && (
|
||||
{filteredNestedItems && filteredNestedItems.length > 0 && (
|
||||
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
|
||||
<List component="div" disablePadding>
|
||||
{item.nestedItems.map((nestedItem) => (
|
||||
{filteredNestedItems.map((nestedItem) => (
|
||||
<NavigationItemComponent
|
||||
key={nestedItem.id}
|
||||
item={nestedItem}
|
||||
|
||||
@@ -1,41 +1,62 @@
|
||||
import List from "@mui/material/List";
|
||||
import Divider from "@mui/material/Divider";
|
||||
import { NAVIGATION_ITEMS } from "@shared";
|
||||
import { authStore, NAVIGATION_ITEMS } from "@shared";
|
||||
import { NavigationItem, NavigationItemComponent } from "@entities";
|
||||
import { observer } from "mobx-react-lite";
|
||||
|
||||
interface NavigationListProps {
|
||||
open: boolean;
|
||||
onDrawerOpen?: () => void;
|
||||
}
|
||||
|
||||
export const NavigationList = ({ open, onDrawerOpen }: NavigationListProps) => {
|
||||
const primaryItems = NAVIGATION_ITEMS.primary;
|
||||
const secondaryItems = NAVIGATION_ITEMS.secondary;
|
||||
export const NavigationList = observer(
|
||||
({ open, onDrawerOpen }: NavigationListProps) => {
|
||||
const { payload } = authStore;
|
||||
// @ts-ignore
|
||||
const isAdmin = Boolean(payload?.is_admin) || false;
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
{primaryItems.map((item) => (
|
||||
<NavigationItemComponent
|
||||
key={item.id}
|
||||
item={item as NavigationItem}
|
||||
open={open}
|
||||
onDrawerOpen={onDrawerOpen}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
{secondaryItems.map((item) => (
|
||||
<NavigationItemComponent
|
||||
key={item.id}
|
||||
item={item as NavigationItem}
|
||||
open={open}
|
||||
onClick={item.onClick ? item.onClick : undefined}
|
||||
onDrawerOpen={onDrawerOpen}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
};
|
||||
const primaryItems = NAVIGATION_ITEMS.primary.filter((item) => {
|
||||
if (item.for_admin) {
|
||||
return isAdmin;
|
||||
}
|
||||
|
||||
if (item.nestedItems && item.nestedItems.length > 0) {
|
||||
return item.nestedItems.some((nestedItem) => {
|
||||
if (nestedItem.for_admin) {
|
||||
return isAdmin;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<List>
|
||||
{primaryItems.map((item) => (
|
||||
<NavigationItemComponent
|
||||
key={item.id}
|
||||
item={item as NavigationItem}
|
||||
open={open}
|
||||
onDrawerOpen={onDrawerOpen}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
<Divider />
|
||||
<List>
|
||||
{NAVIGATION_ITEMS.secondary.map((item) => (
|
||||
<NavigationItemComponent
|
||||
key={item.id}
|
||||
item={item as NavigationItem}
|
||||
open={open}
|
||||
onClick={item.onClick ? item.onClick : undefined}
|
||||
onDrawerOpen={onDrawerOpen}
|
||||
/>
|
||||
))}
|
||||
</List>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -52,7 +52,12 @@ export const LoginPage = () => {
|
||||
}
|
||||
|
||||
navigate("/map");
|
||||
await getUsers();
|
||||
try {
|
||||
await getUsers();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
toast.success("Вход в систему выполнен успешно");
|
||||
} catch (err) {
|
||||
setError(
|
||||
|
||||
@@ -16,7 +16,12 @@ import {
|
||||
Draw,
|
||||
Modify,
|
||||
Select,
|
||||
defaults as defaultInteractions,
|
||||
DragPan,
|
||||
MouseWheelZoom,
|
||||
KeyboardPan,
|
||||
KeyboardZoom,
|
||||
PinchZoom,
|
||||
PinchRotate,
|
||||
} from "ol/interaction";
|
||||
import { DrawEvent } from "ol/interaction/Draw";
|
||||
import { SelectEvent } from "ol/interaction/Select";
|
||||
@@ -102,6 +107,7 @@ import {
|
||||
sightsStore,
|
||||
menuStore,
|
||||
selectedCityStore,
|
||||
carrierStore,
|
||||
} from "@shared";
|
||||
|
||||
// Функция для сброса кешей карты
|
||||
@@ -123,6 +129,7 @@ interface ApiRoute {
|
||||
path: [number, number][];
|
||||
center_latitude: number;
|
||||
center_longitude: number;
|
||||
carrier_id: number;
|
||||
}
|
||||
|
||||
interface ApiStation {
|
||||
@@ -270,6 +277,23 @@ class MapStore {
|
||||
);
|
||||
}
|
||||
|
||||
get filteredRoutes(): ApiRoute[] {
|
||||
const selectedCityId = selectedCityStore.selectedCityId;
|
||||
if (!selectedCityId) {
|
||||
return this.routes;
|
||||
}
|
||||
|
||||
// Получаем carriers для текущего языка
|
||||
const carriers = carrierStore.carriers.ru.data;
|
||||
|
||||
// Фильтруем маршруты по городу через carriers
|
||||
return this.routes.filter((route: ApiRoute) => {
|
||||
// Находим carrier для маршрута
|
||||
const carrier = carriers.find((c: any) => c.id === route.carrier_id);
|
||||
return carrier && carrier.city_id === selectedCityId;
|
||||
});
|
||||
}
|
||||
|
||||
get filteredSights(): ApiSight[] {
|
||||
const selectedCityId = selectedCityStore.selectedCityId;
|
||||
if (!selectedCityId) {
|
||||
@@ -287,7 +311,14 @@ class MapStore {
|
||||
languageInstance("ru").get(`/route/${id}`)
|
||||
);
|
||||
const routeResponses = await Promise.all(routePromises);
|
||||
this.routes = routeResponses.map((res) => res.data);
|
||||
this.routes = routeResponses.map((res) => ({
|
||||
id: res.data.id,
|
||||
route_number: res.data.route_number,
|
||||
path: res.data.path,
|
||||
center_latitude: res.data.center_latitude,
|
||||
center_longitude: res.data.center_longitude,
|
||||
carrier_id: res.data.carrier_id,
|
||||
}));
|
||||
|
||||
this.routes = this.routes.sort((a, b) =>
|
||||
a.route_number.localeCompare(b.route_number)
|
||||
@@ -372,13 +403,28 @@ class MapStore {
|
||||
"EPSG:3857"
|
||||
);
|
||||
|
||||
// Автоматически назначаем перевозчика из выбранного города
|
||||
let carrier_id = 0;
|
||||
let carrier = "";
|
||||
|
||||
if (selectedCityStore.selectedCityId) {
|
||||
const carriersInCity = carrierStore.carriers.ru.data.filter(
|
||||
(c: any) => c.city_id === selectedCityStore.selectedCityId
|
||||
);
|
||||
|
||||
if (carriersInCity.length > 0) {
|
||||
carrier_id = carriersInCity[0].id;
|
||||
carrier = carriersInCity[0].full_name;
|
||||
}
|
||||
}
|
||||
|
||||
const routeData = {
|
||||
route_number,
|
||||
path,
|
||||
center_latitude,
|
||||
center_longitude,
|
||||
carrier: "",
|
||||
carrier_id: 0,
|
||||
carrier,
|
||||
carrier_id,
|
||||
governor_appeal: 0,
|
||||
rotate: 0,
|
||||
route_direction: false,
|
||||
@@ -388,6 +434,12 @@ class MapStore {
|
||||
};
|
||||
|
||||
await routeStore.createRoute(routeData);
|
||||
|
||||
if (!carrier_id) {
|
||||
toast.error(
|
||||
"В выбранном городе нет доступных перевозчиков, маршрут отображается в общем списке"
|
||||
);
|
||||
}
|
||||
createdItem = routeStore.routes.data[routeStore.routes.data.length - 1];
|
||||
} else if (featureType === "sight") {
|
||||
const name = properties.name || "Достопримечательность 1";
|
||||
@@ -935,7 +987,33 @@ class MapService {
|
||||
center: transform(initialCenter, "EPSG:4326", "EPSG:3857"),
|
||||
zoom: initialZoom,
|
||||
}),
|
||||
interactions: defaultInteractions({ doubleClickZoom: false }),
|
||||
interactions: [
|
||||
new MouseWheelZoom(),
|
||||
new KeyboardPan(),
|
||||
new KeyboardZoom(),
|
||||
new PinchZoom(),
|
||||
new PinchRotate(),
|
||||
// Отключаем DoubleClickZoom как было изначально
|
||||
// new DoubleClickZoom(),
|
||||
new DragPan({
|
||||
condition: (event) => {
|
||||
// Разрешаем перетаскивание только при нажатии средней кнопки мыши (колёсико)
|
||||
const originalEvent = event.originalEvent;
|
||||
if (!originalEvent) return false;
|
||||
|
||||
// Проверяем, что это событие мыши и нажата средняя кнопка
|
||||
if (
|
||||
originalEvent.type === "pointerdown" ||
|
||||
originalEvent.type === "pointermove"
|
||||
) {
|
||||
const pointerEvent = originalEvent as PointerEvent;
|
||||
return pointerEvent.buttons === 4; // 4 = средняя кнопка мыши
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
}),
|
||||
],
|
||||
controls: [],
|
||||
});
|
||||
|
||||
@@ -1249,8 +1327,52 @@ class MapService {
|
||||
this.map.on("pointermove", this.boundHandlePointerMove as any);
|
||||
const targetEl = this.map.getTargetElement();
|
||||
if (targetEl instanceof HTMLElement) {
|
||||
// Устанавливаем курсор pointer по умолчанию для всей карты
|
||||
targetEl.style.cursor = "pointer";
|
||||
targetEl.addEventListener("contextmenu", this.boundHandleContextMenu);
|
||||
targetEl.addEventListener("pointerleave", this.boundHandlePointerLeave);
|
||||
|
||||
// Добавляем обработчики для изменения курсора при нажатии средней кнопки мыши
|
||||
targetEl.addEventListener("pointerdown", (e) => {
|
||||
if (e.buttons === 4) {
|
||||
// Средняя кнопка мыши
|
||||
e.preventDefault(); // Предотвращаем скролл страницы
|
||||
targetEl.style.cursor = "grabbing";
|
||||
}
|
||||
});
|
||||
|
||||
targetEl.addEventListener("pointerup", (e) => {
|
||||
if (e.button === 1) {
|
||||
// Средняя кнопка мыши отпущена
|
||||
e.preventDefault(); // Предотвращаем скролл страницы
|
||||
targetEl.style.cursor = "pointer";
|
||||
}
|
||||
});
|
||||
|
||||
// Также добавляем обработчик для mousedown/mouseup для совместимости
|
||||
targetEl.addEventListener("mousedown", (e) => {
|
||||
if (e.button === 1) {
|
||||
// Средняя кнопка мыши
|
||||
e.preventDefault(); // Предотвращаем скролл страницы
|
||||
targetEl.style.cursor = "grabbing";
|
||||
}
|
||||
});
|
||||
|
||||
targetEl.addEventListener("mouseup", (e) => {
|
||||
if (e.button === 1) {
|
||||
// Средняя кнопка мыши отпущена
|
||||
e.preventDefault(); // Предотвращаем скролл страницы
|
||||
targetEl.style.cursor = "pointer";
|
||||
}
|
||||
});
|
||||
|
||||
// Дополнительная защита от нежелательного поведения средней кнопки мыши
|
||||
targetEl.addEventListener("auxclick", (e) => {
|
||||
if (e.button === 1) {
|
||||
// Средняя кнопка мыши
|
||||
e.preventDefault(); // Предотвращаем открытие ссылки в новой вкладке
|
||||
}
|
||||
});
|
||||
}
|
||||
document.addEventListener("keydown", this.boundHandleKeyDown);
|
||||
this.activateEditMode();
|
||||
@@ -1270,7 +1392,7 @@ class MapService {
|
||||
|
||||
public loadFeaturesFromApi(
|
||||
_apiStations: typeof mapStore.stations,
|
||||
apiRoutes: typeof mapStore.routes,
|
||||
_apiRoutes: typeof mapStore.routes,
|
||||
_apiSights: typeof mapStore.sights
|
||||
): void {
|
||||
if (!this.map) return;
|
||||
@@ -1282,6 +1404,7 @@ class MapService {
|
||||
// Используем фильтрованные данные из mapStore
|
||||
const filteredStations = mapStore.filteredStations;
|
||||
const filteredSights = mapStore.filteredSights;
|
||||
const filteredRoutes = mapStore.filteredRoutes;
|
||||
|
||||
filteredStations.forEach((station) => {
|
||||
if (station.longitude == null || station.latitude == null) return;
|
||||
@@ -1313,17 +1436,16 @@ class MapService {
|
||||
pointFeatures.push(feature);
|
||||
});
|
||||
|
||||
apiRoutes.forEach((route) => {
|
||||
filteredRoutes.forEach((route) => {
|
||||
if (!route.path || route.path.length === 0) return;
|
||||
const coordinates = route.path
|
||||
.filter((c) => c && c[0] != null && c[1] != null)
|
||||
.map((c: [number, number]) =>
|
||||
transform([c[1], c[0]], "EPSG:4326", projection)
|
||||
);
|
||||
|
||||
if (coordinates.length === 0) return;
|
||||
|
||||
const routeId = `route-${route.id}`;
|
||||
|
||||
const line = new LineString(coordinates);
|
||||
const lineFeature = new Feature({
|
||||
geometry: line,
|
||||
@@ -1332,8 +1454,6 @@ class MapService {
|
||||
lineFeature.setId(routeId);
|
||||
lineFeature.set("featureType", "route");
|
||||
lineFeatures.push(lineFeature);
|
||||
|
||||
// Не создаем прокси-точки для маршрутов - они должны оставаться только линиями
|
||||
});
|
||||
|
||||
this.pointSource.addFeatures(pointFeatures);
|
||||
@@ -1359,6 +1479,14 @@ class MapService {
|
||||
this.routeLayer.changed();
|
||||
}
|
||||
if (this.tooltipOverlay) this.tooltipOverlay.setPosition(undefined);
|
||||
|
||||
// Сбрасываем курсор при покидании области карты
|
||||
if (this.map) {
|
||||
const targetEl = this.map.getTargetElement();
|
||||
if (targetEl instanceof HTMLElement) {
|
||||
targetEl.style.cursor = "pointer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent): void {
|
||||
@@ -1565,7 +1693,8 @@ class MapService {
|
||||
layerFilter,
|
||||
hitTolerance: 5,
|
||||
});
|
||||
this.map.getTargetElement().style.cursor = hit ? "pointer" : "";
|
||||
// Устанавливаем курсор pointer для всей карты, чтобы показать возможность перетаскивания колёсиком
|
||||
this.map.getTargetElement().style.cursor = hit ? "pointer" : "pointer";
|
||||
|
||||
const featureAtPixel: Feature<Geometry> | undefined =
|
||||
this.map.forEachFeatureAtPixel(
|
||||
@@ -2137,14 +2266,21 @@ const MapSightbar: React.FC<MapSightbarProps> = observer(
|
||||
return feature;
|
||||
});
|
||||
|
||||
const lines = actualFeatures.filter(
|
||||
(f) => f.get("featureType") === "route"
|
||||
);
|
||||
const lines = mapStore.filteredRoutes.map((route) => {
|
||||
const feature = new Feature({
|
||||
geometry: new LineString(route.path),
|
||||
name: route.route_number,
|
||||
});
|
||||
feature.setId(`route-${route.id}`);
|
||||
feature.set("featureType", "route");
|
||||
return feature;
|
||||
});
|
||||
|
||||
return [...stations, ...sights, ...lines];
|
||||
}, [
|
||||
mapStore.filteredStations,
|
||||
mapStore.filteredSights,
|
||||
mapStore.filteredRoutes,
|
||||
actualFeatures,
|
||||
selectedCityId,
|
||||
mapStore,
|
||||
@@ -2613,6 +2749,7 @@ export const MapPage: React.FC = observer(() => {
|
||||
mapStore.getRoutes(),
|
||||
mapStore.getStations(),
|
||||
mapStore.getSights(),
|
||||
carrierStore.getCarriers("ru"),
|
||||
]);
|
||||
mapService.loadFeaturesFromApi(
|
||||
mapStore.stations,
|
||||
|
||||
@@ -16,13 +16,18 @@ import {
|
||||
import { MediaViewer } from "@widgets";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { ArrowLeft, Loader2, Save, Plus } from "lucide-react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useState, useMemo } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { toast } from "react-toastify";
|
||||
import { carrierStore } from "../../../shared/store/CarrierStore";
|
||||
import { articlesStore } from "../../../shared/store/ArticlesStore";
|
||||
import { Route, routeStore } from "../../../shared/store/RouteStore";
|
||||
import { languageStore, SelectArticleModal, SelectMediaDialog } from "@shared";
|
||||
import {
|
||||
languageStore,
|
||||
SelectArticleModal,
|
||||
SelectMediaDialog,
|
||||
selectedCityStore,
|
||||
} from "@shared";
|
||||
|
||||
export const RouteCreatePage = observer(() => {
|
||||
const navigate = useNavigate();
|
||||
@@ -50,6 +55,21 @@ export const RouteCreatePage = observer(() => {
|
||||
articlesStore.getArticleList();
|
||||
}, [language]);
|
||||
|
||||
// Фильтруем перевозчиков только из выбранного города
|
||||
const filteredCarriers = useMemo(() => {
|
||||
const carriers =
|
||||
carrierStore.carriers[language as keyof typeof carrierStore.carriers]
|
||||
.data || [];
|
||||
|
||||
if (!selectedCityStore.selectedCityId) {
|
||||
return carriers;
|
||||
}
|
||||
|
||||
return carriers.filter(
|
||||
(carrier: any) => carrier.city_id === selectedCityStore.selectedCityId
|
||||
);
|
||||
}, [carrierStore.carriers, language, selectedCityStore.selectedCityId]);
|
||||
|
||||
const validateCoordinates = (value: string) => {
|
||||
try {
|
||||
const lines = value.trim().split("\n");
|
||||
@@ -194,16 +214,10 @@ export const RouteCreatePage = observer(() => {
|
||||
value={carrier}
|
||||
label="Выберите перевозчика"
|
||||
onChange={(e) => setCarrier(e.target.value as string)}
|
||||
disabled={
|
||||
carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.length === 0
|
||||
}
|
||||
disabled={filteredCarriers.length === 0}
|
||||
>
|
||||
<MenuItem value="">Не выбрано</MenuItem>
|
||||
{carrierStore.carriers[
|
||||
language as keyof typeof carrierStore.carriers
|
||||
].data?.map((carrier) => (
|
||||
{filteredCarriers.map((carrier: any) => (
|
||||
<MenuItem key={carrier.id} value={carrier.id}>
|
||||
{carrier.full_name}
|
||||
</MenuItem>
|
||||
|
||||
@@ -25,6 +25,7 @@ interface NavigationItem {
|
||||
label: string;
|
||||
icon?: LucideIcon | React.ReactNode;
|
||||
path?: string;
|
||||
for_admin?: boolean;
|
||||
onClick?: () => void;
|
||||
nestedItems?: NavigationItem[];
|
||||
isActive?: boolean;
|
||||
@@ -40,6 +41,7 @@ export const NAVIGATION_ITEMS: {
|
||||
label: "Снапшоты",
|
||||
icon: GitBranch,
|
||||
path: "/snapshot",
|
||||
for_admin: true,
|
||||
},
|
||||
{
|
||||
id: "map",
|
||||
@@ -52,6 +54,7 @@ export const NAVIGATION_ITEMS: {
|
||||
label: "Устройства",
|
||||
icon: Cpu,
|
||||
path: "/devices",
|
||||
for_admin: true,
|
||||
},
|
||||
// {
|
||||
// id: "vehicles",
|
||||
@@ -64,6 +67,7 @@ export const NAVIGATION_ITEMS: {
|
||||
label: "Пользователи",
|
||||
icon: Users,
|
||||
path: "/user",
|
||||
for_admin: true,
|
||||
},
|
||||
{
|
||||
id: "all",
|
||||
@@ -106,12 +110,14 @@ export const NAVIGATION_ITEMS: {
|
||||
label: "Страны",
|
||||
icon: Earth,
|
||||
path: "/country",
|
||||
for_admin: true,
|
||||
},
|
||||
{
|
||||
id: "cities",
|
||||
label: "Города",
|
||||
icon: Building2,
|
||||
path: "/city",
|
||||
for_admin: true,
|
||||
},
|
||||
{
|
||||
id: "carriers",
|
||||
@@ -119,6 +125,7 @@ export const NAVIGATION_ITEMS: {
|
||||
// @ts-ignore
|
||||
icon: CarrierSvg,
|
||||
path: "/carrier",
|
||||
for_admin: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -316,31 +316,35 @@ export const LeftWidgetTab = observer(
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
<img
|
||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||
sight.common.watermark_lu
|
||||
}/download?token=${token}`}
|
||||
alt="preview"
|
||||
className="absolute top-4 left-4 z-10"
|
||||
style={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
{sight.common.watermark_lu && (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||
sight.common.watermark_lu
|
||||
}/download?token=${token}`}
|
||||
alt="preview"
|
||||
className="absolute top-4 left-4 z-10"
|
||||
style={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<img
|
||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||
sight.common.watermark_rd
|
||||
}/download?token=${token}`}
|
||||
alt="preview"
|
||||
className="absolute bottom-4 right-4 z-10"
|
||||
style={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
{sight.common.watermark_rd && (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_KRBL_MEDIA}${
|
||||
sight.common.watermark_rd
|
||||
}/download?token=${token}`}
|
||||
alt="preview"
|
||||
className="absolute bottom-4 right-4 z-10"
|
||||
style={{
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
objectFit: "contain",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<ImagePlus size={48} color="white" />
|
||||
|
||||
Reference in New Issue
Block a user