Skip to content

Commit

Permalink
#744 Login service
Browse files Browse the repository at this point in the history
  • Loading branch information
thekingofcity committed Aug 1, 2024
1 parent 04ef3f0 commit 14c3d95
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 10 deletions.
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 27 additions & 6 deletions src/components/page-header/open-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Badge, IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
import rmgRuntime from '@railmapgen/rmg-runtime';
import rmgRuntime, { logger } from '@railmapgen/rmg-runtime';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { MdInsertDriveFile, MdNoteAdd, MdOpenInNew, MdSchool, MdUpload } from 'react-icons/md';
import { Events } from '../../constants/constants';
import { Events, LocalStorageKey } from '../../constants/constants';
import { RMGParam } from '../../constants/rmg';
import { useRootDispatch, useRootSelector } from '../../redux';
import { saveGraph, setSvgViewBoxMin, setSvgViewBoxZoom } from '../../redux/param/param-slice';
import { clearSelected, setGlobalAlert, setRefreshEdges, setRefreshNodes } from '../../redux/runtime/runtime-slice';
import { getCanvasSize } from '../../util/helpers';
import { useWindowSize } from '../../util/hooks';
import { parseRmgParam } from '../../util/rmg-param-parser';
import { RMPSave, getInitialParam, upgrade } from '../../util/save';
import { saveManagerChannel, SaveManagerEvent, SaveManagerEventType } from '../../util/rmt-save';
import { getInitialParam, RMPSave, upgrade } from '../../util/save';
import RmgParamAppClip from './rmg-param-app-clip';
import RmpGalleryAppClip from './rmp-gallery-app-clip';

Expand Down Expand Up @@ -100,9 +101,6 @@ export default function OpenActions() {
event.target.value = '';
};

const size = useWindowSize();
const { height } = getCanvasSize(size);

const handleLoadTutorial = async () => {
await loadParam(await getInitialParam());
dispatch(setSvgViewBoxMin({ x: -10, y: -13 }));
Expand All @@ -112,6 +110,29 @@ export default function OpenActions() {
rmgRuntime.event(Events.LOAD_TUTORIAL, {});
};

React.useEffect(() => {
// Note that this function will capture all the states if they're used on first mount,
// which will prevent code from getting the lasted state changes.
// Move event listener of broadcast channel to init and use store.getState() and
// store.dispatch() for correctly handling this case.
const rmtSaveHandler = async (ev: MessageEvent<SaveManagerEvent>) => {
const { type, key, from } = ev.data;
if (type === SaveManagerEventType.SAVE_CHANGED && key === LocalStorageKey.PARAM && from === 'rmt') {
logger.debug(`Received save changed event on key: ${key}`);
const param = localStorage.getItem(LocalStorageKey.PARAM);
if (!param) return;
await loadParam(param);
}
};
saveManagerChannel.addEventListener('message', rmtSaveHandler);

// this should never get unmount, but added for safety
return () => saveManagerChannel.removeEventListener('message', rmtSaveHandler);
}, []);

const size = useWindowSize();
const { height } = getCanvasSize(size);

return (
<Menu>
<MenuButton as={IconButton} size="sm" variant="ghost" icon={<MdUpload />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const isTheme = (arr: any[]): boolean => {
arr.length >= 4 && // InterchangeInfo will append strings after Theme
arr.every(elem => typeof elem === 'string') && // type ok
!!arr[2].match(/^#[0-9a-fA-F]{6}$/) && // hex ok
Object.values(MonoColour).includes(arr[3]) // bg ok
Object.values(MonoColour).includes(arr[3] as any) // bg ok
);
};

Expand Down
1 change: 0 additions & 1 deletion src/components/svgs/nodes/facilities.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const Facilities = (props: NodeComponentProps<FacilitiesAttributes>) => {
const [bBox, setBBox] = React.useState({ width: 25, height: 25 } as DOMRect);
React.useEffect(() => {
setBBox(imgEl.current!.getBBox());
console.log(type, imgEl.current!.getBBox());
}, [type, setBBox, imgEl]);

const onPointerDown = React.useCallback(
Expand Down
8 changes: 6 additions & 2 deletions src/redux/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { enableMapSet } from 'immer';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { LocalStorageKey } from '../constants/constants';
import { onRMTSaveUpdate } from '../util/rmt-save';
import { stringifyParam } from '../util/save';
import appReducer from './app/app-slice';
import paramReducer from './param/param-slice';
import runtimeReducer from './runtime/runtime-slice';
import { enableMapSet } from 'immer';

enableMapSet();

Expand All @@ -26,7 +27,10 @@ export const createStore = (preloadedState: Partial<RootState> = {}) =>
const store = createStore();
export type RootStore = typeof store;

store.subscribe(() => {
store.subscribe(async () => {
// notify rmt to update the save
await onRMTSaveUpdate(store.getState().param.present);

localStorage.setItem(LocalStorageKey.PARAM, stringifyParam(store.getState().param));
localStorage.setItem(LocalStorageKey.APP, JSON.stringify(store.getState().app));
});
Expand Down
8 changes: 8 additions & 0 deletions src/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,11 @@ export const shuffle = <T>(arr: T[]): T[] => {
}
return arr;
};

export const createHash = async (data: string, algorithm = 'SHA-256') => {
const encoder = new TextEncoder();
const encodedData = encoder.encode(data);
const hashBuffer = await crypto.subtle.digest(algorithm, encodedData);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('');
};
50 changes: 50 additions & 0 deletions src/util/rmt-save.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { SerializedGraph } from 'graphology-types';
import { EdgeAttributes, GraphAttributes, LocalStorageKey, NodeAttributes } from '../constants/constants';
import { createHash } from './helpers';

export const SAVE_MANAGER_CHANNEL_NAME = 'rmt-save-manager';
export enum SaveManagerEventType {
SAVE_CHANGED = 'SAVE_CHANGED',
}
export interface SaveManagerEvent {
type: SaveManagerEventType;
key: LocalStorageKey.PARAM;
from: 'rmp' | 'rmt';
}

export const saveManagerChannel = new BroadcastChannel(SAVE_MANAGER_CHANNEL_NAME);

export const notifyRMTSaveChange = () => {
saveManagerChannel.postMessage({
type: SaveManagerEventType.SAVE_CHANGED,
key: LocalStorageKey.PARAM,
from: 'rmp',
} as SaveManagerEvent);
};

/**
* Remember the previous hash of the graph state.
* Notify the rmt only if the graph changes.
*/
let previousGraphHash: string | undefined;

/**
* Slightly reduce the server load by notifying a sequence of updates once.
*/
let notifyRMTSaveTimeout: number | undefined;

const SAVE_UPDATE_TIMEOUT_MS = 60 * 1000; // 60s

export const onRMTSaveUpdate = async (graph: SerializedGraph<NodeAttributes, EdgeAttributes, GraphAttributes>) => {
if (notifyRMTSaveTimeout) {
return;
}
const graphHash = await createHash(JSON.stringify(graph));
if (previousGraphHash && previousGraphHash !== graphHash) {
notifyRMTSaveTimeout = window.setTimeout(() => {
notifyRMTSaveChange();
notifyRMTSaveTimeout = undefined;
}, SAVE_UPDATE_TIMEOUT_MS);
}
previousGraphHash = graphHash;
};

0 comments on commit 14c3d95

Please sign in to comment.