Compare commits

..

27 Commits

Author SHA1 Message Date
CI
005068de9c chore: release version v1.12.5 2024-10-22 08:18:22 +00:00
Dmitry Popov
d8c7b1e070 Auto delete connections (#38)
* feat(Map): Auto delete linked connections

* feat(Map): Auto delete linked connections
2024-10-22 12:17:53 +04:00
CI
4835dfcc42 chore: release version v1.12.4 2024-10-21 11:11:42 +00:00
Dmitry Popov
15bceb09a2 chore: release version v1.12.3 2024-10-21 13:11:13 +02:00
Dmitry Popov
13e818abfd fix(Map): Fix systems cleanup 2024-10-21 13:02:42 +02:00
CI
9c5f6049b5 chore: release version v1.12.3 2024-10-18 07:10:35 +00:00
Dmitry Popov
2095b619a4 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-18 11:10:06 +04:00
Dmitry Popov
df155cbc1b fix(Map): Fix regression issues 2024-10-18 11:10:02 +04:00
CI
3781729fd1 chore: release version v1.12.2 2024-10-16 21:12:27 +00:00
Dmitry Popov
d03c634ec0 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-17 01:11:41 +04:00
Dmitry Popov
93c979c218 chore: release version v1.12.0 2024-10-17 01:11:37 +04:00
CI
90fef94583 chore: release version v1.12.1 2024-10-16 20:11:16 +00:00
Dmitry Popov
0b8eec2263 fix(Map): Fix system add error after map page refresh 2024-10-17 00:10:36 +04:00
CI
9511af4e6d chore: release version v1.12.0 2024-10-16 14:12:30 +00:00
Aleksei Chichenkov
7deaf1fd9f Merge pull request #36 from wanderer-industries/refactor-settings
feat(Map): Prettify user settings
2024-10-16 17:12:04 +03:00
achichenkov
43cc5bd520 feat(Map): Prettify user settings
Fixes #35
2024-10-16 17:04:50 +03:00
CI
68b58aa520 chore: release version v1.11.5 2024-10-16 12:46:16 +00:00
Dmitry Popov
dbadd09af3 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 16:45:48 +04:00
Dmitry Popov
fcbe2c754f chore: release version v1.11.1 2024-10-16 16:45:44 +04:00
CI
ad4580677b chore: release version v1.11.4 2024-10-16 11:48:48 +00:00
Dmitry Popov
01a6cc7d92 Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 15:48:20 +04:00
Dmitry Popov
95ce95a187 chore: release version v1.11.1 2024-10-16 15:48:16 +04:00
CI
ce8e6fbfb0 chore: release version v1.11.3 2024-10-16 05:51:27 +00:00
Dmitry Popov
a20eaed76b Merge branch 'main' of github.com:wanderer-industries/wanderer 2024-10-16 09:50:49 +04:00
Dmitry Popov
419af72028 chore: release version v1.11.1 2024-10-16 09:50:45 +04:00
CI
8e499522f6 chore: release version v1.11.2 2024-10-15 22:13:53 +00:00
Dmitry Popov
84321b847e chore: release version v1.11.1 2024-10-16 02:13:18 +04:00
30 changed files with 1361 additions and 1038 deletions

View File

@@ -2,6 +2,72 @@
<!-- changelog -->
## [v1.12.5](https://github.com/wanderer-industries/wanderer/compare/v1.12.4...v1.12.5) (2024-10-22)
## [v1.12.4](https://github.com/wanderer-industries/wanderer/compare/v1.12.3...v1.12.4) (2024-10-21)
### Bug Fixes:
* Map: Fix systems cleanup
## [v1.12.3](https://github.com/wanderer-industries/wanderer/compare/v1.12.2...v1.12.3) (2024-10-18)
### Bug Fixes:
* Map: Fix regression issues
## [v1.12.2](https://github.com/wanderer-industries/wanderer/compare/v1.12.1...v1.12.2) (2024-10-16)
## [v1.12.1](https://github.com/wanderer-industries/wanderer/compare/v1.12.0...v1.12.1) (2024-10-16)
### Bug Fixes:
* Map: Fix system add error after map page refresh
## [v1.12.0](https://github.com/wanderer-industries/wanderer/compare/v1.11.5...v1.12.0) (2024-10-16)
### Features:
* Map: Prettify user settings
## [v1.11.5](https://github.com/wanderer-industries/wanderer/compare/v1.11.4...v1.11.5) (2024-10-16)
## [v1.11.4](https://github.com/wanderer-industries/wanderer/compare/v1.11.3...v1.11.4) (2024-10-16)
## [v1.11.3](https://github.com/wanderer-industries/wanderer/compare/v1.11.2...v1.11.3) (2024-10-16)
## [v1.11.2](https://github.com/wanderer-industries/wanderer/compare/v1.11.1...v1.11.2) (2024-10-15)
## [v1.11.1](https://github.com/wanderer-industries/wanderer/compare/v1.11.0...v1.11.1) (2024-10-14)

View File

@@ -1,5 +1,5 @@
/* Основной класс диалога */
.p-dialog {
body .p-dialog {
display: flex;
flex-direction: column;
//position: absolute;
@@ -7,11 +7,26 @@
left: 0;
//visibility: hidden;
overflow: hidden;
border-radius: 6px;
border-radius: 2px;
box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);
transition: box-shadow 0.3s;
background: #fff;
z-index: 1000;
border: 1px solid #212121;
background: var(--surface-h);
color: var(--text-color);
.p-dialog-header {
background: #171717 !important;
color: var(--text-color);
.p-dialog-header-icon:focus-visible {
box-shadow: none !important;
}
}
.p-dialog-footer {
border-top: 1px solid var(--surface-d);
}
}
/* Стиль видимого диалога */
@@ -45,12 +60,12 @@
justify-content: space-between;
padding: 1rem;
background: #f4f4f4;
border-bottom: 1px solid #ddd;
//border-bottom: 1px solid #ddd;
}
/* Содержимое диалога */
.p-dialog-content {
padding: 1rem;
padding: 0.5rem;
overflow-y: auto;
flex: 1;
}
@@ -78,23 +93,3 @@
.p-dialog-header-close .pi {
font-size: 1.25rem;
}
/* Тема Saga Blue (пример) */
body .p-dialog {
background: var(--surface-a);
color: var(--text-color);
}
body .p-dialog .p-dialog-header,
body .p-dialog .p-dialog-footer {
background: var(--surface-b);
color: var(--text-color);
}
body .p-dialog .p-dialog-header {
border-bottom: 1px solid var(--surface-d);
}
body .p-dialog .p-dialog-footer {
border-top: 1px solid var(--surface-d);
}

View File

@@ -9,6 +9,7 @@
--surface-d: #3f4b5b;
--surface-e: #2a323d;
--surface-f: #2a323d;
--surface-h: #171717;
--text-color: rgba(255, 255, 255, 0.87);
--text-color-secondary: rgba(255, 255, 255, 0.6);
--primary-color: #8dd0ff;

View File

@@ -320,7 +320,8 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
></Column>
<Column
field="linked_system"
header="Linked System"
header="Leads To"
headerClassName="whitespace-nowrap"
bodyClassName="text-ellipsis overflow-hidden whitespace-nowrap"
body={renderLinkedSystem}
style={{ maxWidth: nameColumnWidth }}
@@ -364,7 +365,7 @@ export const SystemSignaturesContent = ({ systemId, settings, selectable, onSele
</button>
<button className="p-button p-component p-button-outlined p-button-sm btn-wide">
<span className="p-button-label p-c" onClick={handleReplaceAll}>
Update & Delete
Update & Delete missing
</span>
</button>
</div>

View File

@@ -7,6 +7,7 @@ import { useCallback, useState } from 'react';
import { OnTheMap, RightBar } from '@/hooks/Mapper/components/mapRootContent/components';
import { MapContextMenu } from '@/hooks/Mapper/components/mapRootContent/components/MapContextMenu/MapContextMenu.tsx';
import { useSkipContextMenu } from '@/hooks/Mapper/hooks/useSkipContextMenu';
import { MapSettings } from "@/hooks/Mapper/components/mapRootContent/components/MapSettings";
export interface MapRootContentProps {}
@@ -16,9 +17,11 @@ export const MapRootContent = ({}: MapRootContentProps) => {
const { isShowMenu } = interfaceSettings;
const [showOnTheMap, setShowOnTheMap] = useState(false);
const [showMapSettings, setShowMapSettings] = useState(false);
const mapInterface = <MapInterface />;
const handleShowOnTheMap = useCallback(() => setShowOnTheMap(true), []);
const handleShowMapSettings = useCallback(() => setShowMapSettings(true), []);
useSkipContextMenu();
@@ -31,18 +34,19 @@ export const MapRootContent = ({}: MapRootContentProps) => {
{mapInterface}
</div>
<div className="absolute top-0 right-0 w-14 h-[calc(100%+3.5rem)] pointer-events-auto">
<RightBar onShowOnTheMap={handleShowOnTheMap} />
<RightBar onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</div>
</div>
) : (
<div className="absolute top-0 left-14 w-[calc(100%-3.5rem)] h-[calc(100%-3.5rem)] pointer-events-none">
<Topbar>
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} />
<MapContextMenu onShowOnTheMap={handleShowOnTheMap} onShowMapSettings={handleShowMapSettings} />
</Topbar>
{mapInterface}
</div>
)}
<OnTheMap show={showOnTheMap} onHide={() => setShowOnTheMap(false)} />
<MapSettings show={showMapSettings} onHide={() => setShowMapSettings(false)} />
</Layout>
);
};

View File

@@ -8,10 +8,11 @@ import { MenuItem } from 'primereact/menuitem';
export interface MapContextMenuProps {
onShowOnTheMap?: () => void;
onShowMapSettings?: () => void;
}
export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
export const MapContextMenu = ({ onShowOnTheMap, onShowMapSettings }: MapContextMenuProps) => {
const { outCommand, setInterfaceSettings } = useMapRootState();
const menuRight = useRef<Menu>(null);
@@ -22,13 +23,6 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
});
}, [outCommand]);
const toggleMinimap = useCallback(() => {
setInterfaceSettings(x => ({
...x,
isShowMinimap: !x.isShowMinimap,
}));
}, [setInterfaceSettings]);
const items = useMemo(() => {
return [
{
@@ -43,9 +37,9 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
},
{ separator: true },
{
label: interfaceSettings.isShowMinimap ? 'Hide minimap' : 'Show minimap',
icon: `pi ${interfaceSettings.isShowMinimap ? 'pi-eye-slash' : 'pi-eye'}`,
command: toggleMinimap,
label: 'Settings',
icon: `pi pi-cog`,
command: onShowMapSettings,
},
{
label: 'Dock menu',
@@ -57,7 +51,7 @@ export const MapContextMenu = ({ onShowOnTheMap }: MapContextMenuProps) => {
})),
},
] as MenuItem[];
}, [handleAddCharacter, interfaceSettings.isShowMinimap, onShowOnTheMap, setInterfaceSettings, toggleMinimap]);
}, [handleAddCharacter, onShowMapSettings, onShowOnTheMap, setInterfaceSettings]);
return (
<div className="ml-1">

View File

@@ -0,0 +1,127 @@
.verticalTabsContainer {
display: flex;
width: 100%;
min-height: 300px;
:global {
.p-tabview {
width: 100%;
display: flex;
align-items: flex-start;
}
.p-tabview-panels {
padding: 6px 1rem !important;
flex-grow: 1;
}
.p-tabview-nav-container {
border-right: none;
height: 100%;
}
.p-tabview-nav {
flex-direction: column;
width: 150px;
min-height: 100%;
border: none;
li {
width: 100%;
border-right: 4px solid var(--surface-hover);
background-color: var(--surface-card);
transition: background-color 200ms, border-right-color 200ms;
&:hover {
background-color: var(--surface-hover);
border-right: 4px solid var(--surface-100);
}
.p-tabview-nav-link {
transition: color 200ms;
justify-content: flex-end;
padding: 10px;
//background-color: var(--surface-card);
background-color: initial;
border: none;
color: var(--gray-400);
border-radius: initial;
font-weight: 400;
margin: 0;
}
&.p-tabview-selected {
background-color: var(--surface-50);
border-right: 4px solid var(--primary-color);
.p-tabview-nav-link {
font-weight: 600;
color: var(--primary-color);
}
&:hover {
//background-color: var(--surface-hover);
border-right: 4px solid var(--primary-color);
}
}
}
}
.p-tabview-panel {
flex-grow: 1;
}
}
}
.CheckboxContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
& > span:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
}
& > :nth-child(2){
border-bottom: 2px dotted #3f3f3f;
height: 2px;
margin: 0 12px;
}
}
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
:global {
.p-inputswitch {
height: 1rem;
width: 2rem;
&.p-inputswitch-checked {
.p-inputswitch-slider::before {
transform: translateX(1rem);
}
}
&.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
}
}
}

View File

@@ -0,0 +1,175 @@
import styles from './MapSettings.module.scss';
import { Dialog } from 'primereact/dialog';
import { useCallback, useMemo, useState } from 'react';
import { TabPanel, TabView } from 'primereact/tabview';
import { PrettySwitchbox } from './components';
import { InterfaceStoredSettings, InterfaceStoredSettingsProps, useMapRootState } from '@/hooks/Mapper/mapRootProvider';
import { OutCommand } from '@/hooks/Mapper/types';
export enum UserSettingsRemoteProps {
link_signature_on_splash = 'link_signature_on_splash',
select_on_spash = 'select_on_spash',
delete_connection_with_sigs = 'delete_connection_with_sigs',
}
export const DEFAULT_REMOTE_SETTINGS = {
[UserSettingsRemoteProps.link_signature_on_splash]: false,
[UserSettingsRemoteProps.select_on_spash]: false,
[UserSettingsRemoteProps.delete_connection_with_sigs]: false,
};
export const UserSettingsRemoteList = [
UserSettingsRemoteProps.link_signature_on_splash,
UserSettingsRemoteProps.select_on_spash,
UserSettingsRemoteProps.delete_connection_with_sigs,
];
export type UserSettingsRemote = {
link_signature_on_splash: boolean;
select_on_spash: boolean;
delete_connection_with_sigs: boolean;
};
export type UserSettings = UserSettingsRemote & InterfaceStoredSettings;
export interface MapSettingsProps {
show: boolean;
onHide: () => void;
}
type CheckboxesList = {
prop: keyof UserSettings;
label: string;
}[];
const COMMON_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMinimap, label: 'Show Minimap' },
];
const SYSTEMS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowKSpace, label: 'Highlight Low/High-security systems' },
{ prop: UserSettingsRemoteProps.select_on_spash, label: 'Auto-select splashed' },
];
const SIGNATURES_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.link_signature_on_splash, label: 'Link signature on splash' },
];
const CONNECTIONS_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: UserSettingsRemoteProps.delete_connection_with_sigs, label: 'Delete connections to linked signatures' },
];
const UI_CHECKBOXES_PROPS: CheckboxesList = [
{ prop: InterfaceStoredSettingsProps.isShowMenu, label: 'Enable compact map menu bar' },
];
export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
const [activeIndex, setActiveIndex] = useState(0);
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const [userRemoteSettings, setUserRemoteSettings] = useState<UserSettingsRemote>({ ...DEFAULT_REMOTE_SETTINGS });
const mergedSettings = useMemo(() => {
return {
...interfaceSettings,
...userRemoteSettings,
};
}, [userRemoteSettings, interfaceSettings]);
const handleShow = async () => {
const { user_settings } = await outCommand({
type: OutCommand.getUserSettings,
data: null,
});
setUserRemoteSettings({
...user_settings,
});
};
const handleChangeChecked = useCallback(
(prop: keyof UserSettings) => async (checked: boolean) => {
// @ts-ignore
if (UserSettingsRemoteList.includes(prop)) {
const newRemoteSettings = {
...userRemoteSettings,
[prop]: checked,
};
await outCommand({
type: OutCommand.updateUserSettings,
data: newRemoteSettings,
});
setUserRemoteSettings(newRemoteSettings);
return;
}
setInterfaceSettings({
...interfaceSettings,
[prop]: checked,
});
},
[interfaceSettings, outCommand, setInterfaceSettings, userRemoteSettings],
);
const renderCheckboxesList = (list: CheckboxesList) => {
return list.map(x => {
return (
<PrettySwitchbox
key={x.prop}
label={x.label}
checked={mergedSettings[x.prop]}
setChecked={handleChangeChecked(x.prop)}
/>
);
});
};
return (
<Dialog
header="Map settings"
visible={show}
draggable={false}
style={{ width: '550px' }}
onShow={handleShow}
onHide={() => {
if (!show) {
return;
}
setActiveIndex(0);
onHide();
}}
>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-2">
<div className={styles.verticalTabsContainer}>
<TabView
activeIndex={activeIndex}
onTabChange={e => setActiveIndex(e.index)}
className={styles.verticalTabView}
>
<TabPanel header="Common" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">{renderCheckboxesList(COMMON_CHECKBOXES_PROPS)}</div>
</TabPanel>
<TabPanel header="Systems" headerClassName={styles.verticalTabHeader}>
<div className="w-full h-full flex flex-col gap-1">
{renderCheckboxesList(SYSTEMS_CHECKBOXES_PROPS)}
</div>
</TabPanel>
<TabPanel header="Connections" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(CONNECTIONS_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="Signatures" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(SIGNATURES_CHECKBOXES_PROPS)}
</TabPanel>
<TabPanel header="User Interface" headerClassName={styles.verticalTabHeader}>
{renderCheckboxesList(UI_CHECKBOXES_PROPS)}
</TabPanel>
</TabView>
</div>
</div>
</div>
</Dialog>
);
};

View File

@@ -0,0 +1,48 @@
.CheckboxContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
& > span:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
user-select: none;
}
& > :nth-child(2){
border-bottom: 2px dotted #3f3f3f;
height: 1px;
margin: 0 12px;
}
}
/* Уменьшение размеров InputSwitch с использованием глобальных стилей */
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
:global {
.p-inputswitch {
height: 1rem;
width: 2rem;
&.p-inputswitch-checked {
.p-inputswitch-slider::before {
transform: translateX(1rem);
}
}
&.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
}
}
}

View File

@@ -0,0 +1,20 @@
import styles from './MapSettings.module.scss';
import { WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
interface PrettySwitchboxProps {
checked: boolean;
setChecked: (checked: boolean) => void;
label: string;
}
export const PrettySwitchbox = ({ checked, setChecked, label }: PrettySwitchboxProps) => {
return (
<label className={styles.CheckboxContainer}>
<span>{label}</span>
<div />
<div className={styles.smallInputSwitch}>
<WdCheckbox size="m" label={''} value={checked} onChange={e => setChecked(e.checked ?? false)} />
</div>
</label>
);
};

View File

@@ -0,0 +1 @@
export * from './PrettySwitchbox';

View File

@@ -0,0 +1 @@
export * from './PrettySwitchbox';

View File

@@ -0,0 +1 @@
export * from './MapSettings';

View File

@@ -8,9 +8,10 @@ import { TooltipPosition } from '@/hooks/Mapper/components/ui-kit';
interface RightBarProps {
onShowOnTheMap?: () => void;
onShowMapSettings?: () => void;
}
export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) => {
const { outCommand, interfaceSettings, setInterfaceSettings } = useMapRootState();
const isShowMinimap = interfaceSettings.isShowMinimap === undefined ? true : interfaceSettings.isShowMinimap;
@@ -22,13 +23,6 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
});
}, [outCommand]);
const handleOpenUserSettings = useCallback(() => {
outCommand({
type: OutCommand.openUserSettings,
data: null,
});
}, [outCommand]);
const toggleMinimap = useCallback(() => {
setInterfaceSettings(x => ({
...x,
@@ -70,16 +64,6 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
</button>
</WdTooltipWrapper>
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button"
onClick={handleOpenUserSettings}
>
<i className="pi pi-cog"></i>
</button>
</WdTooltipWrapper>
<WdTooltipWrapper content="Show on the map" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
@@ -92,6 +76,16 @@ export const RightBar = ({ onShowOnTheMap }: RightBarProps) => {
</div>
<div className="flex flex-col items-center mb-2 gap-1">
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}>
<button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button"
onClick={onShowMapSettings}
>
<i className="pi pi-cog"></i>
</button>
</WdTooltipWrapper>
<WdTooltipWrapper
content={
interfaceSettings.isShowKSpace ? 'Hide highlighting Imperial Space' : 'Show highlighting Imperial Space'

View File

@@ -26,7 +26,13 @@ const INITIAL_DATA: MapRootData = {
selectedConnections: [],
};
type InterfaceStoredSettings = {
export enum InterfaceStoredSettingsProps {
isShowMenu = 'isShowMenu',
isShowMinimap = 'isShowMinimap',
isShowKSpace = 'isShowKSpace',
}
export type InterfaceStoredSettings = {
isShowMenu: boolean;
isShowMinimap: boolean;
isShowKSpace: boolean;

View File

@@ -140,6 +140,9 @@ export enum OutCommand {
// Only UI commands
openSettings = 'open_settings',
getUserSettings = 'get_user_settings',
updateUserSettings = 'update_user_settings',
}
export type OutCommandHandler = <T = any>(event: { type: OutCommand; data: any }) => Promise<T>;

View File

@@ -30,4 +30,19 @@ defmodule WandererApp do
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
def log_exception(kind, reason, stacktrace) do
reason = Exception.normalize(kind, reason, stacktrace)
crash_reason =
case kind do
:throw -> {{:nocatch, reason}, stacktrace}
_ -> {reason, stacktrace}
end
Logger.error(
Exception.format(kind, reason, stacktrace),
crash_reason: crash_reason
)
end
end

View File

@@ -67,8 +67,7 @@ defmodule WandererApp.Api.MapSystemSignature do
:name,
:description,
:kind,
:group,
:linked_system_id
:group
]
primary? true

View File

@@ -149,7 +149,7 @@ defmodule WandererApp.Map.Server.Impl do
WandererApp.Cache.insert("map_#{map_id}:started", true)
broadcast!(map_id, :map_started)
broadcast!(map_id, :map_server_started)
:telemetry.execute([:wanderer_app, :map, :started], %{count: 1})

View File

@@ -1,7 +1,7 @@
defmodule WandererApp.MapRepo do
use WandererApp, :repository
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => false}
@default_map_options %{"layout" => "left_to_right", "store_custom_labels" => "false"}
def get(map_id, relationships \\ []) do
map_id
@@ -15,10 +15,23 @@ defmodule WandererApp.MapRepo do
end
end
def get_by_slug_with_permissions(map_slug, current_user),
do:
map_slug
|> WandererApp.Api.Map.get_map_by_slug()
|> load_user_permissions(current_user)
def load_relationships(map, []), do: {:ok, map}
def load_relationships(map, relationships), do: map |> Ash.load(relationships)
defp load_user_permissions({:ok, map}, current_user),
do:
map
|> Ash.load([:acls, :user_permissions], actor: current_user)
defp load_user_permissions(error, _current_user), do: error
def update_hubs(map_id, hubs) do
map_id
|> WandererApp.Api.Map.by_id()
@@ -36,7 +49,9 @@ defmodule WandererApp.MapRepo do
map
|> WandererApp.Api.Map.update_options(%{options: Jason.encode!(options)})
def options_to_form_data(%{options: options} = _map_options) when not is_nil(options), do: {:ok, Jason.decode!(options)}
def options_to_form_data(%{options: options} = _map_options) when not is_nil(options),
do: {:ok, Jason.decode!(options)}
def options_to_form_data(_), do: {:ok, @default_map_options}
def options_to_form_data!(options) do

View File

@@ -38,7 +38,9 @@ defmodule WandererApp.MapSystemRepo do
end
def cleanup_labels(%{labels: labels} = system, opts) do
store_custom_labels? = Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
store_custom_labels? =
Keyword.get(opts, :store_custom_labels, "false") |> String.to_existing_atom()
labels = get_filtered_labels(labels, store_custom_labels?)
system
@@ -54,10 +56,12 @@ defmodule WandererApp.MapSystemRepo do
%{"customLabel" => customLabel} = labels when is_binary(customLabel) ->
%{"customLabel" => customLabel, "labels" => []}
|> Jason.encode!()
_ ->
nil
end
end
def get_filtered_labels(_, _store_custom_labels), do: nil
def update_name(system, update),

View File

@@ -1,7 +1,7 @@
defmodule WandererApp.MapUserSettingsRepo do
use WandererApp, :repository
@default_form_data %{"select_on_spash" => "false", "link_signature_on_splash" => "false"}
@default_form_data %{"select_on_spash" => false, "link_signature_on_splash" => false, "delete_connection_with_sigs" => false}
def get(map_id, user_id) do
map_id
@@ -46,4 +46,13 @@ defmodule WandererApp.MapUserSettingsRepo do
{:ok, data} = to_form_data(user_settings)
data
end
def get_boolean_setting(settings, key, default \\ false) do
settings
|> Map.get(key, default)
|> to_boolean()
end
def to_boolean(value) when is_binary(value), do: value |> String.to_existing_atom()
def to_boolean(value) when is_boolean(value), do: value
end

View File

@@ -62,6 +62,7 @@ defmodule WandererAppWeb do
use Phoenix.LiveView, @opts
unquote(html_helpers())
defguard is_connected?(socket) when socket.transport_pid != nil
end
end

View File

@@ -0,0 +1,38 @@
defmodule WandererAppWeb.MapLoader do
use WandererAppWeb, :live_component
def render(assigns) do
~H"""
<div
id="map-loader"
data-loading={show_loader("map-loader")}
data-loaded={hide_loader("map-loader")}
class="!z-100 w-screen h-screen hidden relative"
>
<div class="hs-overlay-backdrop transition duration absolute inset-0 blur" />
<div class="flex !z-[150] w-full h-full items-center justify-center">
<div class="Loader" data-text="Wanderer">
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
</div>
</div>
</div>
"""
end
defp show_loader(js \\ %JS{}, id),
do:
JS.show(js,
to: "##{id}",
transition: {"transition-opacity ease-out duration-500", "opacity-0", "opacity-100"}
)
defp hide_loader(js \\ %JS{}, id),
do:
JS.hide(js,
to: "##{id}",
transition: {"transition-opacity ease-in duration-500", "opacity-100", "opacity-0"}
)
end

View File

@@ -0,0 +1,75 @@
defmodule WandererAppWeb.MapPicker do
use WandererAppWeb, :live_component
use LiveViewEvents
@impl true
def mount(socket) do
socket =
socket
|> assign(form: to_form(%{"map_slug" => nil}))
{:ok, socket}
end
def update(
%{
current_user: current_user,
map_slug: map_slug
} = assigns,
socket
) do
socket = handle_info_or_assign(socket, assigns)
{:ok,
socket
|> assign(form: to_form(%{"map_slug" => map_slug}))
|> assign_async(:maps, fn ->
get_available_maps(current_user)
end)}
end
def render(assigns) do
~H"""
<div id={@id}>
<.form
:let={f}
:if={not is_nil(assigns |> Map.get(:maps))}
for={@form}
phx-change="select"
phx-target={@myself}
>
<.async_result :let={maps} assign={@maps}>
<:loading><span class="loading loading-dots loading-xs" /></:loading>
<:failed :let={reason}><%= reason %></:failed>
<.input
:if={maps}
type="select"
field={f[:map_slug]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
placeholder="Select a map..."
options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)}
/>
</.async_result>
</.form>
</div>
"""
end
def handle_event("select", %{"map_slug" => map_slug} = _params, socket) do
notify_to(socket.assigns.notify_to, socket.assigns.event_name, map_slug)
{:noreply, socket}
end
defp get_available_maps(current_user) do
{:ok, maps} =
current_user
|> WandererApp.Maps.get_available_maps()
{:ok, %{maps: maps |> Enum.sort_by(& &1.name, :asc) |> Enum.map(&map_map/1)}}
end
defp map_map(%{name: name, slug: slug} = _map),
do: %{label: name, value: slug}
end

View File

@@ -1,6 +0,0 @@
defmodule WandererApp.MapCharacterSettingsRepo do
use WandererApp, :repository
def create(settings),
do: WandererApp.Api.MapCharacterSettings.create(settings)
end

File diff suppressed because it is too large Load Diff

View File

@@ -1,34 +1,17 @@
<div
id="map-loader"
data-loading={show_loader("map-loader")}
data-loaded={hide_loader("map-loader")}
class="!z-100 w-screen h-screen hidden relative"
>
<div class="hs-overlay-backdrop transition duration absolute inset-0 blur" />
<div class="flex !z-[150] w-full h-full items-center justify-center">
<div class="Loader" data-text="Wanderer">
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
<span class="Loader__Circle"></span>
</div>
</div>
</div>
<.live_component module={WandererAppWeb.MapLoader} id="map-loader" />
<div class="w-full h-full" id="mapper" phx-hook="Mapper" phx-update="ignore"></div>
<div class="absolute top-0 mt-2 left-16 flex gap-1">
<.form :let={f} for={@form} phx-change="change_map">
<span :if={@maps_loading} class="loading loading-dots loading-xs"></span>
<.input
:if={not @maps_loading}
type="select"
field={f[:map_slug]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
placeholder="Select a map..."
options={Enum.map(@maps, fn map -> {map.label, map.value} end)}
/>
</.form>
<.live_component
:if={not is_nil(assigns |> Map.get(:map_slug))}
module={WandererAppWeb.MapPicker}
id="map-picker"
notify_to={self()}
current_user={@current_user}
map_slug={@map_slug}
event_name="change_map"
/>
<.button
:if={(@user_permissions || %{}) |> Map.get(:view_character, false)}
@@ -49,7 +32,7 @@
</div>
<.modal
:if={@live_action in [:add_system]}
:if={@live_action in [:add_system] && not is_nil(assigns |> Map.get(:map_slug)) && @map_loaded?}
id="add-system-modal"
class="!w-[400px]"
title="Add System"
@@ -149,25 +132,3 @@
</.table>
</.async_result>
</.modal>
<.modal
:if={assigns |> Map.get(:show_user_settings?, false)}
id="map-user-settings-modal"
title="Map user settings"
show
on_cancel={JS.push("hide_user_settings")}
>
<.form
:let={f}
:if={assigns |> Map.get(:user_settings_form, false)}
for={@user_settings_form}
phx-change="update_user_settings"
>
<.input type="checkbox" field={f[:select_on_spash]} label="Auto select splashed systems" />
<.input
type="checkbox"
field={f[:link_signature_on_splash]}
label="Link splashed systems to signatures"
/>
</.form>
</.modal>

View File

@@ -2,7 +2,7 @@ defmodule WandererApp.MixProject do
use Mix.Project
@source_url "https://github.com/wanderer-industries/wanderer"
@version "1.11.1"
@version "1.12.5"
def project do
[
@@ -112,7 +112,8 @@ defmodule WandererApp.MixProject do
{:git_ops, "~> 2.6.1"},
{:version_tasks, "~> 0.12.0"},
{:error_tracker, "~> 0.2"},
{:ddrt, "~> 0.2.1"}
{:ddrt, "~> 0.2.1"},
{:live_view_events, "~> 0.1.0"}
]
end

View File

@@ -58,6 +58,7 @@
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"libgraph": {:hex, :libgraph, "0.16.0", "3936f3eca6ef826e08880230f806bfea13193e49bf153f93edcf0239d4fd1d07", [:mix], [], "hexpm", "41ca92240e8a4138c30a7e06466acc709b0cbb795c643e9e17174a178982d6bf"},
"live_select": {:hex, :live_select, "1.4.2", "193056948a52144177bb53266b116117c5ae129939a67f15d7927750d35dd1a9", [:mix], [{:ecto, "~> 3.8", [hex: :ecto, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.6.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_html_helpers, "~> 1.0", [hex: :phoenix_html_helpers, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "fc59e20d8fcb78f3971e898019ad82a4fe2bb516414ccfd63c8463231030ed1f"},
"live_view_events": {:hex, :live_view_events, "0.1.2", "cd8df6d330c1e5e376664e9bd924ea2272c6060d234019be3cb7579c1c562590", [:mix], [{:phoenix_live_view, "~> 0.19", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}], "hexpm", "d54cb2515698a548a7ec9cc8d36798fc4799a157e9344f10642c3f848a6a1174"},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
"makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},