Skip to content

Commit

Permalink
#26 Marketplace
Browse files Browse the repository at this point in the history
  • Loading branch information
langonginc committed Aug 3, 2024
1 parent 56804b8 commit 407e4a7
Show file tree
Hide file tree
Showing 32 changed files with 1,796 additions and 149 deletions.
714 changes: 683 additions & 31 deletions package-lock.json

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"url": "https://github.com/railmapgen/rmp-designer/issues"
},
"license": "GPL-3.0-only",
"type": "module",
"dependencies": {
"@actions/core": "^1.10.1",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.3",
"@emotion/styled": "^11.11.0",
Expand All @@ -15,14 +17,18 @@
"@railmapgen/rmg-runtime": "^9.0.2",
"@railmapgen/rmg-translate": "^3.1.2",
"@reduxjs/toolkit": "^2.1.0",
"@types/jsdom": "^21.1.7",
"common": "^0.2.5",
"framer-motion": "^11.0.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^14.0.1",
"react-icons": "^5.0.1",
"react-redux": "^9.1.0",
"react-router-dom": "^6.21.3",
"react-use-event-hook": "^0.9.6"
"react-use-event-hook": "^0.9.6",
"selenium-webdriver": "^4.23.0",
"zipson": "^0.2.12"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.3.0",
Expand All @@ -32,6 +38,7 @@
"@types/react": "^18.2.48",
"@types/react-dom": "^18.2.18",
"@types/react-router-dom": "^5.3.3",
"@types/selenium-webdriver": "^4.1.24",
"@typescript-eslint/eslint-plugin": "^6.19.1",
"@typescript-eslint/parser": "^6.19.1",
"@vitejs/plugin-legacy": "^5.3.0",
Expand All @@ -42,7 +49,8 @@
"jsdom": "^24.0.0",
"prettier": "^3.2.4",
"terser": "^5.27.0",
"typescript": "^5.3.3",
"ts-node": "^10.9.2",
"typescript": "^5.5.4",
"vite": "^5.0.12",
"vitest": "^1.2.2"
},
Expand Down
Binary file added public/resources/images/test01.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/resources/images/test02.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/resources/json/test01.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id":"GjDADK","label":"test stn","transform":{"translateX":0,"translateY":0,"scale":2,"rotate":0},"version":2,"type":"Station","svgs":[{"id":"id_WrTuFEYbsz","type":"circle","label":"WdvcC","attrs":{"x":"1\"0\"","y":"1\"0\"","r":"1\"5\"","opacity":"1\"1\"","fill":"1\"black\"","stroke":"1\"none\"","strokeWidth":"1\"0\""}},{"id":"id_DQfvyjORQT","type":"rect","label":"bzAPd","attrs":{"x":"1\"-13\"","y":"1\"-13\"","width":"1\"10\"","height":"1\"10\"","rx":"1\"0\"","ry":"1\"0\"","opacity":"1\"1\"","fill":"1\"black\""}}],"components":[],"core":"id_WrTuFEYbsz"}
1 change: 1 addition & 0 deletions public/resources/json/test02.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id":"GjDADK","label":"SVG ZaW8a","transform":{"translateX":-24,"translateY":-24,"scale":0.2,"rotate":0},"version":2,"type":"Station","svgs":[{"id":"id_zmmuoEBtjT","type":"g","label":"PvHda","attrs":{"transform":"1\"translate(0.000000,240.000000) scale(0.100000,-0.100000)\"","fill":"1\"#000000\"","stroke":"1\"none\""},"children":[{"id":"id_lBfIheMlZs","type":"path","label":"jzCJZ","attrs":{"d":"1\"M970 2301 c-305 -68 -555 -237 -727 -493 -301 -451 -241 -1056 143 -1442 115 -116 290 -228 422 -271 49 -16 55 -16 77 -1 24 16 25 20 25 135 l0 118 -88 -5 c-103 -5 -183 13 -231 54 -17 14 -50 62 -73 106 -38 74 -66 108 -144 177 -26 23 -27 24 -9 37 43 32 130 1 185 -65 96 -117 133 -148 188 -160 49 -10 94 -6 162 14 9 3 21 24 27 48 6 23 22 58 35 77 l24 35 -81 16 c-170 35 -275 96 -344 200 -64 96 -85 179 -86 334 0 146 16 206 79 288 28 36 31 47 23 68 -15 36 -11 188 5 234 13 34 20 40 47 43 45 5 129 -24 214 -72 l73 -42 64 15 c91 21 364 20 446 0 l62 -16 58 35 c77 46 175 82 224 82 39 0 39 -1 55 -52 17 -59 20 -166 5 -217 -8 -30 -6 -39 16 -68 109 -144 121 -383 29 -579 -62 -129 -193 -219 -369 -252 l-84 -16 31 -55 32 -56 3 -223 4 -223 25 -16 c23 -15 28 -15 76 2 80 27 217 101 292 158 446 334 590 933 343 1431 -145 293 -419 518 -733 602 -137 36 -395 44 -525 15z\""}}]}],"components":[],"core":"id_WrTuFEYbsz"}
24 changes: 24 additions & 0 deletions public/resources/styles.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"test01": {
"contributor": "59787082",
"name": {
"en": "Test 01",
"zh": "测试 01 - 中文多语言"
},
"desc": {
"en": "English.",
"zh": "这里是中文。"
},
"lastUpdateOn": 1693822303326
},
"test02": {
"contributor": "59787082",
"name": {
"en": "GitHub Logo"
},
"desc": {
"en": "something"
},
"lastUpdateOn": 1693822303329
}
}
121 changes: 45 additions & 76 deletions src/components/app-root.tsx
Original file line number Diff line number Diff line change
@@ -1,93 +1,62 @@
import React from 'react';
import { MdErrorOutline } from 'react-icons/md';
import { Button, Flex, HStack, Spacer } from '@chakra-ui/react';
import { HashRouter, Route, Routes } from 'react-router-dom';
import WindowHeader from './header/window-header';
import { RmgPage, RmgErrorBoundary, RmgThemeProvider, RmgWindow } from '@railmapgen/rmg-components';
import { useTranslation } from 'react-i18next';
import { useRootDispatch, useRootSelector } from '../redux';
import { closePaletteAppClip, onPaletteAppClipEmit } from '../redux/runtime/runtime-slice';
import { useWindowSize } from '../util/hook';
import { getErrorList } from '../util/helper';
import { ToolsPanel } from './panel/tools';
import SvgWrapper from './svg-wrapper';
import { RmpDetails } from './panel/details-rmp';
import { DetailsSvgs } from './panel/details-svgs';
import { Settings } from './panel/settings';
import { DetailsComponents } from './panel/details-components';
import { Preview } from './panel/preview';
import { ErrorDisplay } from './panel/error-display';
import RmgPaletteAppClip from './panel/rmg-palette-app-clip';
import MarketplaceView from './marketplace/marketplace';
import DesignerRoot from './designer-root';
import Ticket from './marketplace/ticket';

export default function AppRoot() {
const { t } = useTranslation();
const dispatch = useRootDispatch();
const param = useRootSelector(store => store.param);
const {
paletteAppClip: { input },
globalAlerts,
} = useRootSelector(state => state.runtime);
const [isDetailsOpen, setDetailsOpen] = React.useState(false);
const [openExport, setOpenExport] = React.useState(false);
const [openErrorDisplay, setOpenErrorDisplay] = React.useState(false);
const size = useWindowSize();
const svgHeight = (((size.height ?? 720) - 40) * 3) / 5;

const [errorList, setErrorList] = React.useState<Array<string[]>>([]);
React.useEffect(() => {
setErrorList(getErrorList(globalAlerts, param));
}, [globalAlerts, param]);

return (
<RmgThemeProvider>
<RmgWindow>
<WindowHeader />
<RmgPage>
<RmgErrorBoundary allowReset>
<Flex direction="row" height={svgHeight} overflow="hidden" sx={{ position: 'relative' }}>
<ToolsPanel />
<SvgWrapper />
<RmpDetails isOpen={isDetailsOpen} onClose={() => setDetailsOpen(false)} />
</Flex>
<Flex height={(size.height ?? 720) - 40 - svgHeight} direction="column" overflow="hidden">
<Flex p={2} direction="row" overflow="hidden" sx={{ position: 'relative' }}>
<HStack width="100%">
<Settings />
<Button onClick={() => setOpenExport(true)} isDisabled={errorList.length > 0}>
{t('header.export.export')}
</Button>
{errorList.length > 0 && (
<Button onClick={() => setOpenErrorDisplay(true)}>
<MdErrorOutline />
{errorList.length}
</Button>
)}
<Spacer />
<Button hidden={isDetailsOpen} onClick={() => setDetailsOpen(true)}>
{t('panel.details.header')}
</Button>
</HStack>
</Flex>
<Flex direction="row" height="100%" overflow="auto" sx={{ position: 'relative' }}>
<DetailsSvgs />
<DetailsComponents />
</Flex>
</Flex>
<Preview isOpen={openExport} onClose={() => setOpenExport(false)} />
<ErrorDisplay
isOpen={openErrorDisplay}
onClose={() => setOpenErrorDisplay(false)}
errorList={errorList}
/>
</RmgErrorBoundary>
<HashRouter>
<RmgThemeProvider>
<RmgWindow>
<WindowHeader />
<RmgPage>
<Routes>
<Route
path="/"
element={
<RmgErrorBoundary allowReset>
<DesignerRoot />
</RmgErrorBoundary>
}
/>
<Route
path="/marketplace"
element={
<RmgErrorBoundary>
<MarketplaceView />
</RmgErrorBoundary>
}
/>
<Route
path="/new"
element={
<RmgErrorBoundary>
<Ticket />
</RmgErrorBoundary>
}
/>
</Routes>

<RmgPaletteAppClip
isOpen={!!input}
onClose={() => dispatch(closePaletteAppClip())}
defaultTheme={input}
onSelect={nextTheme => dispatch(onPaletteAppClipEmit(nextTheme))}
/>
</RmgPage>
</RmgWindow>
</RmgThemeProvider>
<RmgPaletteAppClip
isOpen={!!input}
onClose={() => dispatch(closePaletteAppClip())}
defaultTheme={input}
onSelect={nextTheme => dispatch(onPaletteAppClipEmit(nextTheme))}
/>
</RmgPage>
</RmgWindow>
</RmgThemeProvider>
</HashRouter>
);
}
78 changes: 78 additions & 0 deletions src/components/designer-root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { Badge, Button, Flex, HStack, Spacer } from '@chakra-ui/react';
import React from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { MdErrorOutline } from 'react-icons/md';
import { useRootSelector } from '../redux';
import { ToolsPanel } from './panel/tools';
import { useWindowSize } from '../util/hook';
import { getErrorList } from '../util/helper';
import SvgWrapper from './svg-wrapper';
import { RmpDetails } from './panel/details-rmp';
import { Settings } from './panel/settings';
import { DetailsSvgs } from './panel/details-svgs';
import { DetailsComponents } from './panel/details-components';
import { Preview } from './panel/preview';
import { ErrorDisplay } from './panel/error-display';

const DesignerRoot = () => {
const navigate = useNavigate();
const { t } = useTranslation();
const param = useRootSelector(store => store.param);
const {
paletteAppClip: { input },
globalAlerts,
} = useRootSelector(state => state.runtime);
const [isDetailsOpen, setDetailsOpen] = React.useState(false);
const [openExport, setOpenExport] = React.useState(false);
const [openErrorDisplay, setOpenErrorDisplay] = React.useState(false);
const size = useWindowSize();
const svgHeight = (((size.height ?? 720) - 40) * 3) / 5;

const [errorList, setErrorList] = React.useState<Array<string[]>>([]);
React.useEffect(() => {
setErrorList(getErrorList(globalAlerts, param));
}, [globalAlerts, param]);

return (
<>
<Flex direction="row" height={svgHeight} overflow="hidden" sx={{ position: 'relative' }}>
<ToolsPanel />
<SvgWrapper />
<RmpDetails isOpen={isDetailsOpen} onClose={() => setDetailsOpen(false)} />
</Flex>
<Flex height={(size.height ?? 720) - 40 - svgHeight} direction="column" overflow="hidden">
<Flex p={2} direction="row" overflow="hidden" sx={{ position: 'relative' }}>
<HStack width="100%">
<Settings />
<Button onClick={() => setOpenExport(true)} isDisabled={errorList.length > 0}>
{t('header.export.export')}
<Badge ml="1" colorScheme="green">
RMP
</Badge>
</Button>
<Button onClick={() => navigate('/marketplace')}>{t('marketplace.title')}</Button>
{errorList.length > 0 && (
<Button onClick={() => setOpenErrorDisplay(true)}>
<MdErrorOutline />
{errorList.length}
</Button>
)}
<Spacer />
<Button hidden={isDetailsOpen} onClick={() => setDetailsOpen(true)}>
{t('panel.details.header')}
</Button>
</HStack>
</Flex>
<Flex direction="row" height="100%" overflow="auto" sx={{ position: 'relative' }}>
<DetailsSvgs />
<DetailsComponents />
</Flex>
</Flex>
<Preview isOpen={openExport} onClose={() => setOpenExport(false)} />
<ErrorDisplay isOpen={openErrorDisplay} onClose={() => setOpenErrorDisplay(false)} errorList={errorList} />
</>
);
};

export default DesignerRoot;
21 changes: 2 additions & 19 deletions src/components/header/export-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,9 @@ import React, { useState } from 'react';
import { MdDownload, MdOutput, MdSave } from 'react-icons/md';
import { useTranslation } from 'react-i18next';
import { useRootSelector } from '../../redux';
import { downloadAs } from '../../util/helper';
import { Preview } from '../panel/preview';

const downloadAs = (filename: string, type: string, data: any) => {
const blob = new Blob([data], { type });
downloadBlobAs(filename, blob);
};

const downloadBlobAs = (filename: string, blob: Blob) => {
const url = window.URL.createObjectURL(blob);

const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();

document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};

export default function ExportActions() {
const { t } = useTranslation();
const param = useRootSelector(state => state.param);
Expand All @@ -31,7 +14,7 @@ export default function ExportActions() {

return (
<>
<Menu id="upload">
<Menu id="download">
<MenuButton as={IconButton} size="sm" variant="ghost" icon={<MdDownload />} />
<MenuList>
<MenuItem
Expand Down
6 changes: 4 additions & 2 deletions src/components/header/import-svg-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
import { RmgFields, RmgFieldsField } from '@railmapgen/rmg-components';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SvgsElem } from '../../constants/constants';
import { defaultTransform, SvgsElem } from '../../constants/constants';
import { nanoid } from '../../util/helper';
import { useRootDispatch } from '../../redux';
import { setSvgs } from '../../redux/param/param-slice';
import { setLabel, setSvgs, setTransform } from '../../redux/param/param-slice';

export const loadSvgs = (svgString: string) => {
const parser = new DOMParser();
Expand Down Expand Up @@ -94,6 +94,8 @@ export const ImportFromSvg = (props: { isOpen: boolean; onClose: () => void }) =

const handleImport = () => {
dispatch(setSvgs(loadSvgs(svgString)));
dispatch(setLabel(`SVG ${nanoid(5)}`));
dispatch(setTransform(defaultTransform));
onClose();
};

Expand Down
Loading

0 comments on commit 407e4a7

Please sign in to comment.