mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-11 18:26:04 +00:00
fix(Map): Add ability to copy and past systems (UI part)
This commit is contained in:
@@ -9,6 +9,7 @@ import { useMapperHandlers } from './useMapperHandlers';
|
|||||||
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
import { MapRootContent } from '@/hooks/Mapper/components/mapRootContent/MapRootContent.tsx';
|
||||||
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
import { MapRootProvider } from '@/hooks/Mapper/mapRootProvider';
|
||||||
import './common-styles/main.scss';
|
import './common-styles/main.scss';
|
||||||
|
import { ToastProvider } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||||
|
|
||||||
const ErrorFallback = () => {
|
const ErrorFallback = () => {
|
||||||
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
return <div className="!z-100 absolute w-screen h-screen bg-transparent"></div>;
|
||||||
@@ -39,13 +40,15 @@ export default function MapRoot({ hooks }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PrimeReactProvider>
|
<PrimeReactProvider>
|
||||||
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
<ToastProvider>
|
||||||
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
<MapRootProvider fwdRef={providerRef} outCommand={handleCommand}>
|
||||||
<ReactFlowProvider>
|
<ErrorBoundary FallbackComponent={ErrorFallback} onError={logError}>
|
||||||
<MapRootContent />
|
<ReactFlowProvider>
|
||||||
</ReactFlowProvider>
|
<MapRootContent />
|
||||||
</ErrorBoundary>
|
</ReactFlowProvider>
|
||||||
</MapRootProvider>
|
</ErrorBoundary>
|
||||||
|
</MapRootProvider>
|
||||||
|
</ToastProvider>
|
||||||
</PrimeReactProvider>
|
</PrimeReactProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
31
assets/js/hooks/Mapper/ToastProvider.tsx
Normal file
31
assets/js/hooks/Mapper/ToastProvider.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import React, { createContext, useContext, useRef } from 'react';
|
||||||
|
import { Toast } from 'primereact/toast';
|
||||||
|
import type { ToastMessage } from 'primereact/toast';
|
||||||
|
|
||||||
|
interface ToastContextValue {
|
||||||
|
toastRef: React.RefObject<Toast>;
|
||||||
|
show: (message: ToastMessage | ToastMessage[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToastContext = createContext<ToastContextValue | null>(null);
|
||||||
|
|
||||||
|
export const ToastProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const toastRef = useRef<Toast>(null);
|
||||||
|
|
||||||
|
const show = (message: ToastMessage | ToastMessage[]) => {
|
||||||
|
toastRef.current?.show(message);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastContext.Provider value={{ toastRef, show }}>
|
||||||
|
<Toast ref={toastRef} position="top-right" />
|
||||||
|
{children}
|
||||||
|
</ToastContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useToast = (): ToastContextValue => {
|
||||||
|
const context = useContext(ToastContext);
|
||||||
|
if (!context) throw new Error('useToast must be used within a ToastProvider');
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -6,21 +6,28 @@ import { MenuItem } from 'primereact/menuitem';
|
|||||||
export interface ContextMenuSystemMultipleProps {
|
export interface ContextMenuSystemMultipleProps {
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
onDeleteSystems(): void;
|
onDeleteSystems(): void;
|
||||||
|
onCopySystems(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps> = ({
|
export const ContextMenuSystemMultiple: React.FC<ContextMenuSystemMultipleProps> = ({
|
||||||
contextMenuRef,
|
contextMenuRef,
|
||||||
onDeleteSystems,
|
onDeleteSystems,
|
||||||
|
onCopySystems,
|
||||||
}) => {
|
}) => {
|
||||||
const items: MenuItem[] = useMemo(() => {
|
const items: MenuItem[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
|
{
|
||||||
|
label: 'Copy',
|
||||||
|
icon: PrimeIcons.COPY,
|
||||||
|
command: onCopySystems,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
icon: PrimeIcons.TRASH,
|
icon: PrimeIcons.TRASH,
|
||||||
command: onDeleteSystems,
|
command: onDeleteSystems,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, [onDeleteSystems]);
|
}, [onCopySystems, onDeleteSystems]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -6,27 +6,34 @@ import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
|||||||
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
import { NodeSelectionMouseHandler } from '@/hooks/Mapper/components/contexts/types.ts';
|
||||||
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
import { useDeleteSystems } from '@/hooks/Mapper/components/contexts/hooks';
|
||||||
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
|
||||||
|
import { encodeJsonToUriBase64 } from '@/hooks/Mapper/utils';
|
||||||
|
import { useToast } from '@/hooks/Mapper/ToastProvider.tsx';
|
||||||
|
|
||||||
export const useContextMenuSystemMultipleHandlers = () => {
|
export const useContextMenuSystemMultipleHandlers = () => {
|
||||||
const {
|
const {
|
||||||
data: { pings },
|
data: { pings, connections },
|
||||||
} = useMapRootState();
|
} = useMapRootState();
|
||||||
|
|
||||||
|
const { show } = useToast();
|
||||||
|
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
|
const [systems, setSystems] = useState<Node<SolarSystemRawType>[]>();
|
||||||
|
|
||||||
const { deleteSystems } = useDeleteSystems();
|
const { deleteSystems } = useDeleteSystems();
|
||||||
|
|
||||||
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
const ping = useMemo(() => (pings.length === 1 ? pings[0] : undefined), [pings]);
|
||||||
|
const refVars = useRef({ systems, ping, connections, deleteSystems });
|
||||||
|
refVars.current = { systems, ping, connections, deleteSystems };
|
||||||
|
|
||||||
const handleSystemMultipleContext: NodeSelectionMouseHandler = (ev, systems_) => {
|
const handleSystemMultipleContext = useCallback<NodeSelectionMouseHandler>((ev, systems_) => {
|
||||||
setSystems(systems_);
|
setSystems(systems_);
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
ctxManager.next('ctxSysMult', contextMenuRef.current);
|
ctxManager.next('ctxSysMult', contextMenuRef.current);
|
||||||
contextMenuRef.current?.show(ev);
|
contextMenuRef.current?.show(ev);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const onDeleteSystems = useCallback(() => {
|
const onDeleteSystems = useCallback(() => {
|
||||||
|
const { systems, ping, deleteSystems } = refVars.current;
|
||||||
|
|
||||||
if (!systems) {
|
if (!systems) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -41,11 +48,34 @@ export const useContextMenuSystemMultipleHandlers = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteSystems(sysToDel);
|
deleteSystems(sysToDel);
|
||||||
}, [deleteSystems, systems, ping]);
|
}, []);
|
||||||
|
|
||||||
|
const onCopySystems = useCallback(async () => {
|
||||||
|
const { systems, connections } = refVars.current;
|
||||||
|
if (!systems) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectionToCopy = connections.filter(
|
||||||
|
c => systems.filter(s => [c.target, c.source].includes(s.id)).length == 2,
|
||||||
|
);
|
||||||
|
|
||||||
|
await navigator.clipboard.writeText(
|
||||||
|
encodeJsonToUriBase64({ systems: systems.map(x => x.data), connections: connectionToCopy }),
|
||||||
|
);
|
||||||
|
|
||||||
|
show({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Copied to clipboard',
|
||||||
|
detail: `Successfully copied to clipboard - [${systems.length}] systems and [${connectionToCopy.length}] connections`,
|
||||||
|
life: 3000,
|
||||||
|
});
|
||||||
|
}, [show]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleSystemMultipleContext,
|
handleSystemMultipleContext,
|
||||||
contextMenuRef,
|
contextMenuRef,
|
||||||
onDeleteSystems,
|
onDeleteSystems,
|
||||||
|
onCopySystems,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,4 @@ export type WaypointSetContextHandlerProps = {
|
|||||||
destination: string;
|
destination: string;
|
||||||
};
|
};
|
||||||
export type WaypointSetContextHandler = (props: WaypointSetContextHandlerProps) => void;
|
export type WaypointSetContextHandler = (props: WaypointSetContextHandlerProps) => void;
|
||||||
export type NodeSelectionMouseHandler =
|
export type NodeSelectionMouseHandler = (event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void;
|
||||||
| ((event: React.MouseEvent<Element, MouseEvent>, nodes: Node[]) => void)
|
|
||||||
| undefined;
|
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ const MapComp = ({
|
|||||||
useMapHandlers(refn, onSelectionChange);
|
useMapHandlers(refn, onSelectionChange);
|
||||||
useUpdateNodes(nodes);
|
useUpdateNodes(nodes);
|
||||||
|
|
||||||
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem });
|
const { handleRootContext, ...rootCtxProps } = useContextMenuRootHandlers({ onAddSystem, onCommand });
|
||||||
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
const { handleConnectionContext, ...connectionCtxProps } = useContextMenuConnectionHandlers();
|
||||||
const { update } = useMapState();
|
const { update } = useMapState();
|
||||||
const { variant, gap, size, color } = useBackgroundVars(theme);
|
const { variant, gap, size, color } = useBackgroundVars(theme);
|
||||||
|
|||||||
@@ -2,13 +2,21 @@ import React, { RefObject, useMemo } from 'react';
|
|||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { PrimeIcons } from 'primereact/api';
|
import { PrimeIcons } from 'primereact/api';
|
||||||
import { MenuItem } from 'primereact/menuitem';
|
import { MenuItem } from 'primereact/menuitem';
|
||||||
|
import { PasteSystemsAndConnections } from '@/hooks/Mapper/components/map/components';
|
||||||
|
|
||||||
export interface ContextMenuRootProps {
|
export interface ContextMenuRootProps {
|
||||||
contextMenuRef: RefObject<ContextMenu>;
|
contextMenuRef: RefObject<ContextMenu>;
|
||||||
|
pasteSystemsAndConnections: PasteSystemsAndConnections | undefined;
|
||||||
onAddSystem(): void;
|
onAddSystem(): void;
|
||||||
|
onPasteSystemsAnsConnections(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef, onAddSystem }) => {
|
export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({
|
||||||
|
contextMenuRef,
|
||||||
|
onAddSystem,
|
||||||
|
onPasteSystemsAnsConnections,
|
||||||
|
pasteSystemsAndConnections,
|
||||||
|
}) => {
|
||||||
const items: MenuItem[] = useMemo(() => {
|
const items: MenuItem[] = useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@@ -16,8 +24,17 @@ export const ContextMenuRoot: React.FC<ContextMenuRootProps> = ({ contextMenuRef
|
|||||||
icon: PrimeIcons.PLUS,
|
icon: PrimeIcons.PLUS,
|
||||||
command: onAddSystem,
|
command: onAddSystem,
|
||||||
},
|
},
|
||||||
|
...(pasteSystemsAndConnections != null
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'Paste',
|
||||||
|
icon: 'pi pi-clipboard',
|
||||||
|
command: onPasteSystemsAnsConnections,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
];
|
];
|
||||||
}, [onAddSystem]);
|
}, [onAddSystem, onPasteSystemsAnsConnections, pasteSystemsAndConnections]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -3,34 +3,74 @@ import React, { useCallback, useRef, useState } from 'react';
|
|||||||
import { ContextMenu } from 'primereact/contextmenu';
|
import { ContextMenu } from 'primereact/contextmenu';
|
||||||
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
import { ctxManager } from '@/hooks/Mapper/utils/contextManager.ts';
|
||||||
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
import { OnMapAddSystemCallback } from '@/hooks/Mapper/components/map/map.types.ts';
|
||||||
|
import { decodeUriBase64ToJson } from '@/hooks/Mapper/utils';
|
||||||
|
import { OutCommand, OutCommandHandler, SolarSystemConnection, SolarSystemRawType } from '@/hooks/Mapper/types';
|
||||||
|
import { recenterSystemsByBounds } from '@/hooks/Mapper/helpers/recenterSystems.ts';
|
||||||
|
|
||||||
|
export type PasteSystemsAndConnections = {
|
||||||
|
systems: SolarSystemRawType[];
|
||||||
|
connections: SolarSystemConnection[];
|
||||||
|
};
|
||||||
|
|
||||||
type UseContextMenuRootHandlers = {
|
type UseContextMenuRootHandlers = {
|
||||||
onAddSystem?: OnMapAddSystemCallback;
|
onAddSystem?: OnMapAddSystemCallback;
|
||||||
|
onCommand?: OutCommandHandler;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useContextMenuRootHandlers = ({ onAddSystem }: UseContextMenuRootHandlers = {}) => {
|
export const useContextMenuRootHandlers = ({ onAddSystem, onCommand }: UseContextMenuRootHandlers = {}) => {
|
||||||
const rf = useReactFlow();
|
const rf = useReactFlow();
|
||||||
const contextMenuRef = useRef<ContextMenu | null>(null);
|
const contextMenuRef = useRef<ContextMenu | null>(null);
|
||||||
const [position, setPosition] = useState<XYPosition | null>(null);
|
const [position, setPosition] = useState<XYPosition | null>(null);
|
||||||
|
const [pasteSystemsAndConnections, setPasteSystemsAndConnections] = useState<PasteSystemsAndConnections>();
|
||||||
|
|
||||||
const handleRootContext = (e: React.MouseEvent<HTMLDivElement>) => {
|
const handleRootContext = async (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
setPosition(rf.project({ x: e.clientX, y: e.clientY }));
|
setPosition(rf.project({ x: e.clientX, y: e.clientY }));
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
ctxManager.next('ctxRoot', contextMenuRef.current);
|
ctxManager.next('ctxRoot', contextMenuRef.current);
|
||||||
contextMenuRef.current?.show(e);
|
contextMenuRef.current?.show(e);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
const result = decodeUriBase64ToJson(text);
|
||||||
|
setPasteSystemsAndConnections(result as PasteSystemsAndConnections);
|
||||||
|
} catch (err) {
|
||||||
|
setPasteSystemsAndConnections(undefined);
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ref = useRef({ onAddSystem, position });
|
const ref = useRef({ onAddSystem, position, pasteSystemsAndConnections, onCommand });
|
||||||
ref.current = { onAddSystem, position };
|
ref.current = { onAddSystem, position, pasteSystemsAndConnections, onCommand };
|
||||||
|
|
||||||
const onAddSystemCallback = useCallback(() => {
|
const onAddSystemCallback = useCallback(() => {
|
||||||
ref.current.onAddSystem?.({ coordinates: position });
|
ref.current.onAddSystem?.({ coordinates: position });
|
||||||
}, [position]);
|
}, [position]);
|
||||||
|
|
||||||
|
const onPasteSystemsAnsConnections = useCallback(async () => {
|
||||||
|
const { pasteSystemsAndConnections, onCommand, position } = ref.current;
|
||||||
|
if (!position || !onCommand || !pasteSystemsAndConnections) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { systems } = recenterSystemsByBounds(pasteSystemsAndConnections.systems);
|
||||||
|
|
||||||
|
await onCommand({
|
||||||
|
type: OutCommand.manualPasteSystemsAndConnections,
|
||||||
|
data: {
|
||||||
|
systems: systems.map(({ position: srcPos, ...rest }) => ({
|
||||||
|
position: { x: srcPos.x + position.x, y: srcPos.y + position.y },
|
||||||
|
...rest,
|
||||||
|
})),
|
||||||
|
connections: pasteSystemsAndConnections.connections,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
handleRootContext,
|
handleRootContext,
|
||||||
|
pasteSystemsAndConnections,
|
||||||
contextMenuRef,
|
contextMenuRef,
|
||||||
onAddSystem: onAddSystemCallback,
|
onAddSystem: onAddSystemCallback,
|
||||||
|
onPasteSystemsAnsConnections,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from './parseSignatures';
|
|||||||
export * from './getSystemById';
|
export * from './getSystemById';
|
||||||
export * from './getEveImageUrl';
|
export * from './getEveImageUrl';
|
||||||
export * from './toastHelpers';
|
export * from './toastHelpers';
|
||||||
|
export * from './recenterSystems';
|
||||||
|
|||||||
39
assets/js/hooks/Mapper/helpers/recenterSystems.ts
Normal file
39
assets/js/hooks/Mapper/helpers/recenterSystems.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { XYPosition } from 'reactflow';
|
||||||
|
|
||||||
|
export type WithPosition<T = unknown> = T & { position: XYPosition };
|
||||||
|
|
||||||
|
export const computeBoundsCenter = (items: Array<WithPosition>): XYPosition => {
|
||||||
|
if (items.length === 0) return { x: 0, y: 0 };
|
||||||
|
|
||||||
|
let minX = Infinity;
|
||||||
|
let maxX = -Infinity;
|
||||||
|
let minY = Infinity;
|
||||||
|
let maxY = -Infinity;
|
||||||
|
|
||||||
|
for (const { position } of items) {
|
||||||
|
if (position.x < minX) minX = position.x;
|
||||||
|
if (position.x > maxX) maxX = position.x;
|
||||||
|
if (position.y < minY) minY = position.y;
|
||||||
|
if (position.y > maxY) maxY = position.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: minX + (maxX - minX) / 2,
|
||||||
|
y: minY + (maxY - minY) / 2,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Смещает все точки так, чтобы центр области стал (0,0) */
|
||||||
|
export const recenterSystemsByBounds = <T extends WithPosition>(items: T[]): { center: XYPosition; systems: T[] } => {
|
||||||
|
const center = computeBoundsCenter(items);
|
||||||
|
|
||||||
|
const systems = items.map(it => ({
|
||||||
|
...it,
|
||||||
|
position: {
|
||||||
|
x: it.position.x - center.x,
|
||||||
|
y: it.position.y - center.y,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { center, systems };
|
||||||
|
};
|
||||||
@@ -247,6 +247,7 @@ export enum OutCommand {
|
|||||||
deleteSystems = 'delete_systems',
|
deleteSystems = 'delete_systems',
|
||||||
manualAddSystem = 'manual_add_system',
|
manualAddSystem = 'manual_add_system',
|
||||||
manualAddConnection = 'manual_add_connection',
|
manualAddConnection = 'manual_add_connection',
|
||||||
|
manualPasteSystemsAndConnections = 'manual_paste_systems_and_connections',
|
||||||
manualDeleteConnection = 'manual_delete_connection',
|
manualDeleteConnection = 'manual_delete_connection',
|
||||||
setAutopilotWaypoint = 'set_autopilot_waypoint',
|
setAutopilotWaypoint = 'set_autopilot_waypoint',
|
||||||
addSystem = 'add_system',
|
addSystem = 'add_system',
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ export * from './getQueryVariable';
|
|||||||
export * from './loadTextFile';
|
export * from './loadTextFile';
|
||||||
export * from './saveToFile';
|
export * from './saveToFile';
|
||||||
export * from './omit';
|
export * from './omit';
|
||||||
|
export * from './jsonToUriBase64';
|
||||||
|
|||||||
26
assets/js/hooks/Mapper/utils/jsonToUriBase64.ts
Normal file
26
assets/js/hooks/Mapper/utils/jsonToUriBase64.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
export const encodeJsonToUriBase64 = (value: unknown): string => {
|
||||||
|
const json = JSON.stringify(value);
|
||||||
|
const uriEncoded = encodeURIComponent(json);
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && typeof window.btoa === 'function') {
|
||||||
|
return window.btoa(uriEncoded);
|
||||||
|
}
|
||||||
|
// Node.js
|
||||||
|
// @ts-ignore
|
||||||
|
return Buffer.from(uriEncoded, 'utf8').toString('base64');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const decodeUriBase64ToJson = <T = unknown>(base64: string): T => {
|
||||||
|
let uriEncoded: string;
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && typeof window.atob === 'function') {
|
||||||
|
uriEncoded = window.atob(base64);
|
||||||
|
} else {
|
||||||
|
// Node.js
|
||||||
|
// @ts-ignore
|
||||||
|
uriEncoded = Buffer.from(base64, 'base64').toString('utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
const json = decodeURIComponent(uriEncoded);
|
||||||
|
return JSON.parse(json) as T;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user