feat(Map): Added an option to show 'Offline characters' to map admins & managers only

- updated UI layout for map settings modal
This commit is contained in:
Dmitry Popov
2024-12-04 18:01:50 +01:00
parent 12fa1a0be8
commit ab02fe988c
24 changed files with 3693 additions and 1463 deletions

View File

@@ -870,3 +870,63 @@ body {
} }
} }
/* Map refresh END */ /* Map refresh END */
.inputContainer {
display: grid;
grid-template-columns: auto 1fr auto;
align-items: center;
}
.inputContainer > span:nth-child(1),
.inputContainer > label:nth-child(1) {
color: var(--gray-200);
font-size: 13px;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
}
.inputContainer > :nth-child(2) {
border-bottom: 2px dotted #3f3f3f;
height: 1px;
margin: 0 12px;
}
.smallInputSwitch {
height: 100%;
display: flex;
align-items: center;
}
.smallInputSwitch .p-inputswitch {
height: 1rem;
width: 2rem;
}
.smallInputSwitch .p-inputswitch.p-inputswitch-checked .p-inputswitch-slider::before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch.p-highlight .p-inputswitch-slider:before {
transform: translateX(1rem);
}
.smallInputSwitch .p-inputswitch .p-inputswitch-slider::before {
width: 0.8rem;
height: 0.8rem;
margin-top: -0.4rem;
margin-left: -3px;
}
.checkboxRoot.sizeXS {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeXS .p-checkbox-box,
.checkboxRoot.sizeXS .p-checkbox-input {
width: 14px;
height: 14px;
}
.checkboxRoot.sizeM {
width: 16px;
height: 16px;
}
.checkboxRoot.sizeM .p-checkbox-box,
.checkboxRoot.sizeM .p-checkbox-input {
width: 16px;
height: 16px;
}

View File

@@ -8,6 +8,8 @@ import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; import { CharacterCard, LayoutEventBlocker, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts'; import { sortCharacters } from '@/hooks/Mapper/components/mapInterface/helpers/sortCharacters.ts';
import useLocalStorageState from 'use-local-storage-state'; import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
type CharItemProps = { type CharItemProps = {
compact: boolean; compact: boolean;
@@ -62,6 +64,14 @@ export const LocalCharacters = () => {
const [systemId] = selectedSystems; const [systemId] = selectedSystems;
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const itemTemplate = useItemTemplate(); const itemTemplate = useItemTemplate();
const sorted = useMemo(() => { const sorted = useMemo(() => {
@@ -70,13 +80,13 @@ export const LocalCharacters = () => {
.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact })) .map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id), compact: settings.compact }))
.sort(sortCharacters); .sort(sortCharacters);
if (!settings.showOffline) { if (!showOffline || !settings.showOffline) {
return sorted.filter(c => c.online); return sorted.filter(c => c.online);
} }
return sorted; return sorted;
// eslint-disable-next-line // eslint-disable-next-line
}, [characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]); }, [showOffline, characters, settings.showOffline, settings.compact, systemId, userCharacters, presentCharacters]);
const isNobodyHere = sorted.length === 0; const isNobodyHere = sorted.length === 0;
const isNotSelectedSystem = selectedSystems.length !== 1; const isNotSelectedSystem = selectedSystems.length !== 1;
@@ -88,14 +98,16 @@ export const LocalCharacters = () => {
<div className="flex justify-between items-center text-xs w-full"> <div className="flex justify-between items-center text-xs w-full">
<span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span> <span className="select-none">Local{showList ? ` [${sorted.length}]` : ''}</span>
<LayoutEventBlocker className="flex items-center gap-2"> <LayoutEventBlocker className="flex items-center gap-2">
<WdCheckbox {showOffline && (
size="xs" <WdCheckbox
labelSide="left" size="xs"
label={'Show offline'} labelSide="left"
value={settings.showOffline} label={'Show offline'}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300" value={settings.showOffline}
onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))} classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
/> onChange={() => setSettings(() => ({ ...settings, showOffline: !settings.showOffline }))}
/>
)}
<span <span
className={clsx('w-4 h-4 cursor-pointer', { className={clsx('w-4 h-4 cursor-pointer', {
@@ -115,7 +127,9 @@ export const LocalCharacters = () => {
)} )}
{isNobodyHere && !isNotSelectedSystem && ( {isNobodyHere && !isNotSelectedSystem && (
<div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">Nobody here</div> <div className="w-full h-full flex justify-center items-center select-none text-stone-400/80 text-sm">
Nobody here
</div>
)} )}
{showList && ( {showList && (

View File

@@ -128,7 +128,7 @@ export const MapSettings = ({ show, onHide }: MapSettingsProps) => {
return ( return (
<Dialog <Dialog
header="Map settings" header="Map user settings"
visible={show} visible={show}
draggable={false} draggable={false}
style={{ width: '550px' }} style={{ width: '550px' }}

View File

@@ -8,6 +8,8 @@ import clsx from 'clsx';
import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types'; import { CharacterTypeRaw, WithIsOwnCharacter } from '@/hooks/Mapper/types';
import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit'; import { CharacterCard, WdCheckbox } from '@/hooks/Mapper/components/ui-kit';
import useLocalStorageState from 'use-local-storage-state'; import useLocalStorageState from 'use-local-storage-state';
import { useMapCheckPermissions, useMapGetOption } from '@/hooks/Mapper/mapRootProvider/hooks/api';
import { UserPermission } from '@/hooks/Mapper/types/permissions.ts';
type WindowLocalSettingsType = { type WindowLocalSettingsType = {
compact: boolean; compact: boolean;
@@ -50,14 +52,22 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
defaultValue: STORED_DEFAULT_VALUES, defaultValue: STORED_DEFAULT_VALUES,
}); });
const restrictOfflineShowing = useMapGetOption('restrict_offline_showing');
const isAdminOrManager = useMapCheckPermissions([UserPermission.MANAGE_MAP]);
const showOffline = useMemo(
() => !restrictOfflineShowing || isAdminOrManager,
[isAdminOrManager, restrictOfflineShowing],
);
const sorted = useMemo(() => { const sorted = useMemo(() => {
const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters); const out = characters.map(x => ({ ...x, isOwn: userCharacters.includes(x.eve_id) })).sort(sortCharacters);
if (!settings.hideOffline) { if (showOffline && !settings.hideOffline) {
return out; return out;
} }
return out.filter(x => x.online); return out.filter(x => x.online);
}, [characters, settings.hideOffline, userCharacters]); }, [showOffline, characters, settings.hideOffline, userCharacters]);
return ( return (
<Sidebar <Sidebar
@@ -70,14 +80,16 @@ export const OnTheMap = ({ show, onHide }: OnTheMapProps) => {
> >
<div className={clsx(classes.SidebarContent, '')}> <div className={clsx(classes.SidebarContent, '')}>
<div className={'flex justify-end items-center gap-2 px-3'}> <div className={'flex justify-end items-center gap-2 px-3'}>
<WdCheckbox {showOffline && (
size="m" <WdCheckbox
labelSide="left" size="m"
label={'Hide offline'} labelSide="left"
value={settings.hideOffline} label={'Hide offline'}
classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300" value={settings.hideOffline}
onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))} classNameLabel="text-stone-400 hover:text-stone-200 transition duration-300"
/> onChange={() => setSettings(() => ({ ...settings, hideOffline: !settings.hideOffline }))}
/>
)}
</div> </div>
<VirtualScroller <VirtualScroller

View File

@@ -83,7 +83,7 @@ export const RightBar = ({ onShowOnTheMap, onShowMapSettings }: RightBarProps) =
</div> </div>
<div className="flex flex-col items-center mb-2 gap-1"> <div className="flex flex-col items-center mb-2 gap-1">
<WdTooltipWrapper content="User settings" position={TooltipPosition.left}> <WdTooltipWrapper content="Map user settings" position={TooltipPosition.left}>
<button <button
className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto" className="btn bg-transparent text-gray-400 hover:text-white border-transparent hover:bg-transparent py-2 h-auto min-h-auto"
type="button" type="button"

View File

@@ -41,8 +41,6 @@ export const WHClassView = ({
data: { wormholesData }, data: { wormholesData },
} = useMapRootState(); } = useMapRootState();
console.log(whClassName);
const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]); const whData = useMemo(() => wormholesData[whClassName], [whClassName, wormholesData]);
const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]); const whClass = useMemo(() => WORMHOLES_ADDITIONAL_INFO[whData.dest], [whData.dest]);
const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? ''; const whClassStyle = WORMHOLE_CLASS_STYLES[whClass?.wormholeClassID] ?? '';

View File

@@ -26,6 +26,7 @@ const INITIAL_DATA: MapRootData = {
selectedSystems: [], selectedSystems: [],
selectedConnections: [], selectedConnections: [],
userPermissions: {}, userPermissions: {},
options: {},
}; };
export enum InterfaceStoredSettingsProps { export enum InterfaceStoredSettingsProps {

View File

@@ -1,6 +1,7 @@
export * from './useMapInit'; export * from './useMapInit';
export * from './useMapUpdated'; export * from './useMapUpdated';
export * from './useMapCheckPermissions'; export * from './useMapCheckPermissions';
export * from './useMapGetOption';
export * from './useRoutes'; export * from './useRoutes';
export * from './useCommandsConnections'; export * from './useCommandsConnections';
export * from './useCommandsSystems'; export * from './useCommandsSystems';

View File

@@ -0,0 +1,10 @@
import { useMemo } from 'react';
import { useMapRootState } from '@/hooks/Mapper/mapRootProvider';
export const useMapGetOption = (option: string) => {
const {
data: { options },
} = useMapRootState();
return useMemo(() => options[option], [option, options]);
};

View File

@@ -20,6 +20,7 @@ export const useMapInit = () => {
present_characters, present_characters,
hubs, hubs,
user_permissions, user_permissions,
options,
}: CommandInit) => { }: CommandInit) => {
const updateData: Partial<MapRootData> = {}; const updateData: Partial<MapRootData> = {};
@@ -60,6 +61,10 @@ export const useMapInit = () => {
updateData.hubs = hubs; updateData.hubs = hubs;
} }
if (options) {
updateData.options = options;
}
if (system_static_infos) { if (system_static_infos) {
system_static_infos.forEach(static_info => { system_static_infos.forEach(static_info => {
addSystemStatic(static_info); addSystemStatic(static_info);

View File

@@ -62,6 +62,7 @@ export type CommandInit = {
user_permissions: UserPermissions; user_permissions: UserPermissions;
hubs: string[]; hubs: string[];
routes: RoutesList; routes: RoutesList;
options: Record<string, string | boolean>;
reset?: boolean; reset?: boolean;
}; };
export type CommandAddSystems = SolarSystemRawType[]; export type CommandAddSystems = SolarSystemRawType[];

View File

@@ -19,4 +19,5 @@ export type MapUnionTypes = {
kills: Record<number, number>; kills: Record<number, number>;
connections: SolarSystemConnection[]; connections: SolarSystemConnection[];
userPermissions: Partial<UserPermissions>; userPermissions: Partial<UserPermissions>;
options: Record<string, string | boolean>;
}; };

View File

@@ -14,6 +14,7 @@ defmodule WandererApp.Map do
hubs: [], hubs: [],
connections: Map.new(), connections: Map.new(),
acls: [], acls: [],
options: Map.new(),
characters_limit: nil, characters_limit: nil,
hubs_limit: nil hubs_limit: nil
@@ -69,6 +70,9 @@ defmodule WandererApp.Map do
def get_characters_limit(map_id), def get_characters_limit(map_id),
do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 100)} do: {:ok, map_id |> get_map!() |> Map.get(:characters_limit, 100)}
def get_options(map_id),
do: {:ok, map_id |> get_map!() |> Map.get(:options, Map.new())}
@doc """ @doc """
Returns a full list of characters in the map Returns a full list of characters in the map
""" """
@@ -251,6 +255,13 @@ defmodule WandererApp.Map do
map map
end end
def update_options!(%{map_id: map_id} = map, options) do
map_id
|> update_map(%{options: options})
map
end
def add_systems!(map, []), do: map def add_systems!(map, []), do: map
def add_systems!(%{map_id: map_id} = map, [system | rest]) do def add_systems!(%{map_id: map_id} = map, [system | rest]) do

View File

@@ -277,15 +277,11 @@ defmodule WandererApp.Map.Server.Impl do
} }
end end
def handle_event({:options_updated, options}, state), def handle_event({:options_updated, options}, %{map: map} = state) do
do: %{ map |> WandererApp.Map.update_options!(options)
state
| map_opts: [ %{state | map_opts: map_options(options)}
layout: options |> Map.get("layout", "left_to_right"), end
store_custom_labels:
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
}
def handle_event({ref, _result}, %{map_id: _map_id} = state) do def handle_event({ref, _result}, %{map_id: _map_id} = state) do
Process.demonitor(ref, [:flush]) Process.demonitor(ref, [:flush])
@@ -319,6 +315,16 @@ defmodule WandererApp.Map.Server.Impl do
map |> Map.put_new(attribute, get_in(update, [Access.key(attribute)])) map |> Map.put_new(attribute, get_in(update, [Access.key(attribute)]))
end)} end)}
defp map_options(options) do
[
layout: options |> Map.get("layout", "left_to_right"),
store_custom_labels:
options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom(),
restrict_offline_showing:
options |> Map.get("restrict_offline_showing", "false") |> String.to_existing_atom()
]
end
defp save_map_state(%{map_id: map_id} = _state) do defp save_map_state(%{map_id: map_id} = _state) do
systems_last_activity = systems_last_activity =
map_id map_id
@@ -389,22 +395,17 @@ defmodule WandererApp.Map.Server.Impl do
systems, systems,
connections connections
) do ) do
{:ok, options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map = map =
initial_map initial_map
|> WandererApp.Map.new() |> WandererApp.Map.new()
|> WandererApp.Map.update_options!(options)
|> WandererApp.Map.update_subscription_settings!(subscription_settings) |> WandererApp.Map.update_subscription_settings!(subscription_settings)
|> WandererApp.Map.add_systems!(systems) |> WandererApp.Map.add_systems!(systems)
|> WandererApp.Map.add_connections!(connections) |> WandererApp.Map.add_connections!(connections)
|> WandererApp.Map.add_characters!(characters) |> WandererApp.Map.add_characters!(characters)
{:ok, map_options} = WandererApp.MapRepo.options_to_form_data(initial_map)
map_opts = [
layout: map_options |> Map.get("layout", "left_to_right"),
store_custom_labels:
map_options |> Map.get("store_custom_labels", "false") |> String.to_existing_atom()
]
character_ids = character_ids =
map_id map_id
|> WandererApp.Map.get_map!() |> WandererApp.Map.get_map!()
@@ -412,7 +413,7 @@ defmodule WandererApp.Map.Server.Impl do
WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids) WandererApp.Cache.insert("map_#{map_id}:invalidate_character_ids", character_ids)
%{state | map: map, map_opts: map_opts} %{state | map: map, map_opts: map_options(options)}
end end
def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do def maybe_import_systems(state, %{"systems" => systems} = _settings, user_id, character_id) do

View File

@@ -1,7 +1,11 @@
defmodule WandererApp.MapRepo do defmodule WandererApp.MapRepo do
use WandererApp, :repository 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",
"restrict_offline_showing" => "false"
}
def get(map_id, relationships \\ []) do def get(map_id, relationships \\ []) do
map_id map_id

View File

@@ -143,15 +143,10 @@ defmodule WandererAppWeb.CoreComponents do
def server_status(assigns) do def server_status(assigns) do
~H""" ~H"""
<div <div
class="flex flex-col p-4 items-center absolute bottom-16 left-1 gap-2 tooltip tooltip-right" class="flex flex-col p-4 items-center absolute bottom-16 left-2 gap-2 tooltip tooltip-right"
data-tip="server: Tranquility" data-tip="server: Tranquility"
> >
<div <div class={"block w-2 h-2 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
:if={@online}
class="absolute block w-4 h-4 rounded-full shadow-inner bg-green-500 animate-ping"
>
</div>
<div class={"block w-4 h-4 rounded-full shadow-inner #{if @online, do: " bg-green-500", else: "bg-red-500"}"}>
</div> </div>
</div> </div>
""" """
@@ -336,6 +331,7 @@ defmodule WandererAppWeb.CoreComponents do
""" """
attr(:id, :any, default: nil) attr(:id, :any, default: nil)
attr(:class, :string, default: nil) attr(:class, :string, default: nil)
attr(:wrapper_class, :string, default: nil)
attr(:name, :any) attr(:name, :any)
attr(:label, :string, default: nil) attr(:label, :string, default: nil)
attr(:prefix, :string, default: nil) attr(:prefix, :string, default: nil)
@@ -381,21 +377,62 @@ defmodule WandererAppWeb.CoreComponents do
end) end)
~H""" ~H"""
<div phx-feedback-for={@name} class="form-control mt-8"> <div phx-feedback-for={@name} class="form-control mt-2">
<label class="label cursor-pointer gap-2"> <label class="inputContainer" for={@name}>
<span class="label-text"><%= @label %></span> <span><%= @label %></span>
<input type="hidden" name={@name} value="false" /> <div></div>
<input <div class="smallInputSwitch">
type="checkbox" <div class="flex items-center">
id={@id} <div
name={@name} class={[
value="true" "checkboxRoot sizeM p-checkbox p-component",
checked={@checked} classes("p-highlight": @checked)
class="checkbox" ]}
{@rest} data-p-highlight={@checked}
/> data-p-disabled="false"
data-pc-name="checkbox"
data-pc-section="root"
>
<input
id={@id}
name={@name}
type="checkbox"
class="p-checkbox-input"
aria-invalid="false"
data-pc-section="input"
value="true"
checked={@checked}
{@rest}
/>
<div
class="p-checkbox-box"
data-p-highlight={@checked}
data-p-disabled="false"
data-pc-section="box"
>
<svg
:if={@checked}
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
class="p-icon p-checkbox-icon"
aria-hidden="true"
data-pc-section="icon"
>
<path
d="M4.86199 11.5948C4.78717 11.5923 4.71366 11.5745 4.64596 11.5426C4.57826 11.5107 4.51779 11.4652 4.46827 11.4091L0.753985 7.69483C0.683167 7.64891 0.623706 7.58751 0.580092 7.51525C0.536478 7.44299 0.509851 7.36177 0.502221 7.27771C0.49459 7.19366 0.506156 7.10897 0.536046 7.03004C0.565935 6.95111 0.613367 6.88 0.674759 6.82208C0.736151 6.76416 0.8099 6.72095 0.890436 6.69571C0.970973 6.67046 1.05619 6.66385 1.13966 6.67635C1.22313 6.68886 1.30266 6.72017 1.37226 6.76792C1.44186 6.81567 1.4997 6.8786 1.54141 6.95197L4.86199 10.2503L12.6397 2.49483C12.7444 2.42694 12.8689 2.39617 12.9932 2.40745C13.1174 2.41873 13.2343 2.47141 13.3251 2.55705C13.4159 2.64268 13.4753 2.75632 13.4938 2.87973C13.5123 3.00315 13.4888 3.1292 13.4271 3.23768L5.2557 11.4091C5.20618 11.4652 5.14571 11.5107 5.07801 11.5426C5.01031 11.5745 4.9368 11.5923 4.86199 11.5948Z"
fill="currentColor"
>
</path>
</svg>
</div>
</div>
<label for={@name} class="select-none ml-1.5"></label>
</div>
</div>
</label> </label>
<.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
end end
@@ -403,24 +440,28 @@ defmodule WandererAppWeb.CoreComponents do
def input(%{type: "range"} = assigns) do def input(%{type: "range"} = assigns) do
~H""" ~H"""
<div phx-feedback-for={@name}> <div phx-feedback-for={@name}>
<label class="form-control w-full"> <div class="form-control w-full">
<.label for={@id}> <.label for={@id}>
<span class="label-text"><%= @label %></span> <span><%= @label %></span>
<span class="label-value"><%= @value %></span> <div></div>
<%= @value %>
</.label> </.label>
<input
type="range" <div>
id={@id} <input
name={@name} type="range"
value={@value} id={@id}
class={[ name={@name}
"p-component w-full", value={@value}
@class, class={[
@errors != [] && "border-rose-400 focus:border-rose-400" "p-component w-full",
]} @class,
{@rest} @errors != [] && "border-rose-400 focus:border-rose-400"
/> ]}
</label> {@rest}
/>
</div>
</div>
<.error :for={msg <- @errors}><%= msg %></.error> <.error :for={msg <- @errors}><%= msg %></.error>
</div> </div>
""" """
@@ -428,13 +469,20 @@ defmodule WandererAppWeb.CoreComponents do
def input(%{type: "select"} = assigns) do def input(%{type: "select"} = assigns) do
~H""" ~H"""
<div phx-feedback-for={@name}> <div
phx-feedback-for={@name}
class={[
"inputContainer",
@wrapper_class
]}
>
<.label :if={@label} for={@id}><%= @label %></.label> <.label :if={@label} for={@id}><%= @label %></.label>
<div :if={@label}></div>
<select <select
id={@id} id={@id}
name={@name} name={@name}
class={[ class={[
"w-full", "p-component",
@class @class
]} ]}
multiple={@multiple} multiple={@multiple}
@@ -503,9 +551,9 @@ defmodule WandererAppWeb.CoreComponents do
def label(assigns) do def label(assigns) do
~H""" ~H"""
<div for={@for} class="label"> <label for={@for} class="inputContainer">
<%= render_slot(@inner_block) %> <%= render_slot(@inner_block) %>
</div> </label>
""" """
end end

View File

@@ -48,7 +48,7 @@ defmodule WandererAppWeb.MapPicker do
:if={maps} :if={maps}
type="select" type="select"
field={f[:map_slug]} field={f[:map_slug]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900" class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
placeholder="Select a map..." placeholder="Select a map..."
options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)} options={Enum.map(@maps.result, fn map -> {map.label, map.value} end)}
/> />

View File

@@ -136,8 +136,10 @@
<.input <.input
type="select" type="select"
field={f[:owner_id]} field={f[:owner_id]}
class="p-dropdown p-component p-inputwrapper mt-8" class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
placeholder="Select a map owner" wrapper_class="mt-2"
label="Owner"
placeholder="Select an owner"
options={Enum.map(@characters, fn character -> {character.label, character.id} end)} options={Enum.map(@characters, fn character -> {character.label, character.id} end)}
/> />
<div class="modal-action"> <div class="modal-action">

View File

@@ -39,7 +39,8 @@ defmodule WandererAppWeb.AclMember do
<.input <.input
type="select" type="select"
field={f[:role]} field={f[:role]}
class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900 w-[70px]" class="select h-8 min-h-[0px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="w-[60px] mr-16"
placeholder="Select a role..." placeholder="Select a role..."
options={Enum.map(@roles, fn role -> {role.label, role.value} end)} options={Enum.map(@roles, fn role -> {role.label, role.value} end)}
/> />

View File

@@ -500,13 +500,15 @@ defmodule WandererAppWeb.MapCoreEventHandler do
{:ok, hubs} = map_id |> WandererApp.Map.list_hubs() {:ok, hubs} = map_id |> WandererApp.Map.list_hubs()
{:ok, connections} = map_id |> WandererApp.Map.list_connections() {:ok, connections} = map_id |> WandererApp.Map.list_connections()
{:ok, systems} = map_id |> WandererApp.Map.list_systems() {:ok, systems} = map_id |> WandererApp.Map.list_systems()
{:ok, options} = map_id |> WandererApp.Map.get_options()
%{ %{
systems: systems:
systems systems
|> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end), |> Enum.map(fn system -> MapEventHandler.map_ui_system(system, include_static_data?) end),
hubs: hubs, hubs: hubs,
connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1) connections: connections |> Enum.map(&MapEventHandler.map_ui_connection/1),
options: options
} }
end end

View File

@@ -280,14 +280,17 @@ defmodule WandererAppWeb.MapsLive do
do: do:
{:noreply, {:noreply,
socket socket
|> assign(:amounts, [ |> assign(
%{label: "150M", value: 150_000_000}, :amounts,
%{label: "300M", value: 300_000_000}, [
%{label: "600M", value: 600_000_000}, {"150M", 150_000_000},
%{label: "1.2B", value: 1_200_000_000}, {"300M", 300_000_000},
%{label: "2.4B", value: 2_400_000_000}, {"600M", 600_000_000},
%{label: "5B", value: 5_000_000_000} {"1.2B", 1_200_000_000},
]) {"2.4B", 2_400_000_000},
{"5B", 5_000_000_000}
]
)
|> assign(is_topping_up?: true)} |> assign(is_topping_up?: true)}
@impl true @impl true
@@ -654,7 +657,7 @@ defmodule WandererAppWeb.MapsLive do
) do ) do
options = options =
options_form options_form
|> Map.take(["layout", "store_custom_labels"]) |> Map.take(["layout", "store_custom_labels", "restrict_offline_showing"])
{:ok, updated_map} = WandererApp.MapRepo.update_options(map, options) {:ok, updated_map} = WandererApp.MapRepo.update_options(map, options)

View File

@@ -139,14 +139,18 @@
<.input <.input
type="select" type="select"
field={f[:owner_id]} field={f[:owner_id]}
class="p-dropdown p-component p-inputwrapper mt-8" class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="mt-2"
label="Map owner"
placeholder="Select a map owner" placeholder="Select a map owner"
options={Enum.map(@characters, fn character -> {character.label, character.id} end)} options={Enum.map(@characters, fn character -> {character.label, character.id} end)}
/> />
<.input <.input
type="select" type="select"
field={f[:scope]} field={f[:scope]}
class="p-dropdown p-component p-inputwrapper mt-8" class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
wrapper_class="mt-2"
label="Map scope"
placeholder="Select a map scope" placeholder="Select a map scope"
options={Enum.map(@scopes, fn scope -> {scope, scope} end)} options={Enum.map(@scopes, fn scope -> {scope, scope} end)}
/> />
@@ -182,203 +186,411 @@
<.modal <.modal
:if={@live_action in [:settings]} :if={@live_action in [:settings]}
title="Map Settings" title="Map Settings"
class="!w-[800px]" class="!min-w-[700px]"
id="map-settings-modal" id="map-settings-modal"
show show
on_cancel={JS.patch(~p"/maps")} on_cancel={JS.patch(~p"/maps")}
> >
<div role="tablist" class="tabs tabs-bordered"> <div class="flex flex-col gap-3">
<a <div class="flex flex-col gap-2">
role="tab" <div class="_verticalTabsContainer_1o01l_2">
phx-click="change_settings_tab" <div class="p-tabview p-component" data-pc-name="tabview" data-pc-section="root">
phx-value-tab="general" <div class="p-tabview-nav-container" data-pc-section="navcontainer">
class={[ <div class="p-tabview-nav-content" data-pc-section="navcontent">
"tab", <ul class="p-tabview-nav" role="tablist" data-pc-section="nav">
classes("tab-active": @active_settings_tab == "general") <li
]} class={[
> "p-unselectable-text",
<.icon name="hero-wrench-screwdriver-solid" class="w-4 h-4" />&nbsp;General classes("p-tabview-selected p-highlight": @active_settings_tab == "general")
</a> ]}
<a role="presentation"
role="tab" data-pc-name=""
phx-click="change_settings_tab" data-pc-section="header"
phx-value-tab="import" >
class={[ <a
"tab", role="tab"
classes("tab-active": @active_settings_tab == "import") class="p-tabview-nav-link flex p-[10px]"
]} tabindex="0"
> aria-controls="pr_id_330_content"
<.icon name="hero-document-arrow-down-solid" class="w-4 h-4" />&nbsp;Import/Export aria-selected="true"
</a> aria-disabled="false"
<a data-pc-section="headeraction"
:if={@map_subscriptions_enabled?} phx-click="change_settings_tab"
role="tab" phx-value-tab="general"
phx-click="change_settings_tab" >
phx-value-tab="balance" <span class="p-tabview-title" data-pc-section="headertitle">
class={[ <.icon name="hero-wrench-screwdriver-solid" class="w-4 h-4" />&nbsp;General
"tab", </span>
classes("tab-active": @active_settings_tab == "balance") </a>
]} </li>
>
<.icon name="hero-banknotes-solid" class="w-4 h-4" />&nbsp;Balance <li
</a> :if={@map_subscriptions_enabled?}
<a class={[
:if={@map_subscriptions_enabled?} "p-unselectable-text",
role="tab" classes("p-tabview-selected p-highlight": @active_settings_tab == "balance")
phx-click="change_settings_tab" ]}
phx-value-tab="subscription" role="presentation"
class={[ data-pc-name=""
"tab", data-pc-section="header"
classes("tab-active": @active_settings_tab == "subscription") >
]} <a
> role="tab"
<.icon name="hero-check-badge-solid" class="w-4 h-4" />&nbsp;Subscription class="p-tabview-nav-link flex p-[10px]"
</a> tabindex="-1"
</div> aria-controls="pr_id_332_content"
<.header :if={@active_settings_tab == "general"} class="bordered border-1 border-zinc-800"> aria-selected="false"
<:actions> aria-disabled="false"
<.form data-pc-section="headeraction"
:let={f} phx-click="change_settings_tab"
:if={assigns |> Map.get(:options_form, false)} phx-value-tab="balance"
for={@options_form} >
phx-change="update_options" <span class="p-tabview-title" data-pc-section="headertitle">
> <.icon name="hero-banknotes-solid" class="w-4 h-4" />&nbsp;Balance
<div> </span>
<div class="stat-title">Map systems layout</div> </a>
<div class="stat-value text-white"> </li>
<.input
type="select" <li
field={f[:layout]} :if={@map_subscriptions_enabled?}
class="p-dropdown p-component p-inputwrapper" class={[
placeholder="Map default layout" "p-unselectable-text",
options={@layout_options} classes(
/> "p-tabview-selected p-highlight": @active_settings_tab == "subscription"
)
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_334_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_settings_tab"
phx-value-tab="subscription"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-check-badge-solid" class="w-4 h-4" />&nbsp;Subscription
</span>
</a>
</li>
<li
class={[
"p-unselectable-text",
classes("p-tabview-selected p-highlight": @active_settings_tab == "import")
]}
role="presentation"
data-pc-name=""
data-pc-section="header"
>
<a
role="tab"
class="p-tabview-nav-link flex p-[10px]"
tabindex="-1"
aria-controls="pr_id_331_content"
aria-selected="false"
aria-disabled="false"
data-pc-section="headeraction"
phx-click="change_settings_tab"
phx-value-tab="import"
>
<span class="p-tabview-title" data-pc-section="headertitle">
<.icon name="hero-document-arrow-down-solid" class="w-4 h-4" />&nbsp;Import/Export
</span>
</a>
</li>
<li
aria-hidden="true"
role="presentation"
class="p-tabview-ink-bar"
data-pc-section="inkbar"
style="width: 146px; left: 0px;"
>
</li>
</ul>
</div>
</div>
<div class="p-tabview-panels" data-pc-section="panelcontainer">
<div
id="pr_id_330_content"
class="p-tabview-panel"
role="tabpanel"
aria-labelledby="pr_id_33_header_0"
data-pc-name=""
data-pc-section="content"
>
<div :if={@active_settings_tab == "general"}>
<.form
:let={f}
:if={assigns |> Map.get(:options_form, false)}
for={@options_form}
phx-change="update_options"
>
<.input
type="select"
field={f[:layout]}
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
label="Map systems layout"
placeholder="Map default layout"
options={@layout_options}
/>
<.input
type="checkbox"
field={f[:store_custom_labels]}
label="Store system custom labels"
/>
<.input
type="checkbox"
field={f[:restrict_offline_showing]}
label="Show offline characters to admins & managers only"
/>
</.form>
</div>
<div :if={@active_settings_tab == "import"}>
<.form
:if={assigns |> Map.get(:import_form, false)}
for={@import_form}
phx-change="import"
>
<div phx-drop-target="{@uploads.settings.ref}">
<.live_file_input upload={@uploads.settings} />
</div>
</.form>
<progress :if={@importing} class="progress w-56"></progress>
<.button
id="export-settings-btn"
class="mt-8"
type="button"
disabled={@importing}
phx-hook="DownloadJson"
data-name={@map_slug}
data-content={Jason.encode!(assigns[:export_settings] || %{})}
>
<.icon name="hero-document-arrow-down-solid" class="w-4 h-4" /> Export Settings
</.button>
</div>
<div :if={@active_settings_tab == "balance"}>
<div class="stats w-full bg-primary text-primary-content">
<div class="stat">
<div class="stat-figure text-primary">
<.button
:if={not @is_topping_up?}
class="mt-2"
type="button"
phx-click="show_topup"
>
<.icon name="hero-banknotes-solid" class="w-4 h-4" /> Top Up
</.button>
</div>
<div class="stat-title">Map balance</div>
<div class="stat-value text-white">
ISK <%= @map_balance
|> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
<div class="stat-actions text-end"></div>
</div>
</div>
<.form
:let={f}
:if={@is_topping_up?}
for={@topup_form}
class="mt-2"
phx-change="validate_topup"
phx-submit="topup"
>
<.input
type="select"
field={f[:amount]}
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
label="Topup amount"
placeholder="Select topup amount"
options={@amounts}
/>
<div class="modal-action">
<.button class="mt-2" type="button" phx-click="hide_topup">
Cancel
</.button>
<.button class="mt-2" type="submit">
Top Up
</.button>
</div>
</.form>
</div>
<.table
:if={@active_settings_tab == "subscription"}
class="!max-h-[300px] !overflow-y-auto"
empty_label="No active subscriptions, using alpha plan by default."
id="active-subscriptions-tbl"
rows={@map_subscriptions}
>
<:col :let={subscription} label="Subscription Plan">
<%= subscription.plan %>
</:col>
<:col :let={subscription} label="Status">
<%= subscription.status %>
</:col>
<:col :let={subscription} label="Characters Limit">
<%= subscription.characters_limit %>
</:col>
<:col :let={subscription} label="Hubs Limit">
<%= subscription.hubs_limit %>
</:col>
<:col :let={subscription} label="Active Till">
<.local_time
:if={subscription.active_till}
id={"subscription-active-till-#{subscription.id}"}
at={subscription.active_till}
>
<%= subscription.active_till %>
</.local_time>
</:col>
<:col :let={subscription} label="Auto Renew">
<%= if subscription.auto_renew?, do: "Yes", else: "No" %>
</:col>
<:action :let={subscription}>
<div class="tooltip tooltip-left" data-tip="Edit subscription">
<button
:if={subscription.status == :active && subscription.plan != :alpha}
phx-click="edit-subscription"
phx-value-id={subscription.id}
>
<.icon name="hero-pencil-square-solid" class="w-4 h-4 hover:text-white" />
</button>
</div>
</:action>
<:action :let={subscription}>
<div class="tooltip tooltip-left" data-tip="Cancel subscription">
<button
:if={subscription.status == :active && subscription.plan != :alpha}
phx-click="cancel-subscription"
phx-value-id={subscription.id}
data={[confirm: "Please confirm to cancel subscription!"]}
>
<.icon name="hero-trash-solid" class="w-4 h-4 hover:text-white" />
</button>
</div>
</:action>
</.table>
<.header
:if={@active_settings_tab == "subscription" && @is_adding_subscription?}
class="bordered border-1 flex flex-col gap-4"
>
<div :if={is_nil(@selected_subscription)}>
Add subscription
<div class="badge badge-secondary">Limited time offer: 50%</div>
</div>
<div :if={not is_nil(@selected_subscription)}>
Edit subscription
<div class="badge badge-secondary">Limited time offer: 50%</div>
</div>
<.form
:let={f}
for={@subscription_form}
phx-change="validate_subscription"
phx-submit={
if is_nil(@selected_subscription),
do: "subscribe",
else: "update_subscription"
}
>
<.input
:if={is_nil(@selected_subscription)}
type="select"
field={f[:period]}
class="select h-8 min-h-[10px] !pt-1 !pb-1 text-sm bg-neutral-900"
label="Subscription period"
options={@subscription_periods}
/>
<.input
field={f[:characters_limit]}
label="Characters limit"
show_value={true}
type="range"
min="300"
max="5000"
step="100"
class="range range-xs"
/>
<.input
field={f[:hubs_limit]}
label="Hubs limit"
show_value={true}
type="range"
min="20"
max="50"
step="10"
class="range range-xs"
/>
<.input field={f[:auto_renew?]} label="Auto Renew" type="checkbox" />
<div
:if={is_nil(@selected_subscription)}
class="stats w-full bg-primary text-primary-content mt-2"
>
<div class="stat">
<div class="stat-figure text-primary">
<.button type="submit">
Subscribe
</.button>
</div>
<div class="flex gap-8">
<div>
<div class="stat-title">Estimated price</div>
<div class="stat-value text-white">
ISK <%= (@estimated_price - @discount)
|> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
</div>
<div>
<div class="stat-title">Discount</div>
<div class="stat-value text-white relative">
ISK <%= @discount
|> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
<span class="absolute top-0 right-0 text-xs text-white discount" />
</div>
</div>
</div>
</div>
</div>
<div
:if={not is_nil(@selected_subscription)}
class="stats w-full bg-primary text-primary-content"
>
<div class="stat">
<div class="stat-figure text-primary">
<.button type="button" phx-click="cancel_edit_subscription">
Cancel
</.button>
<.button type="submit">
Update
</.button>
</div>
<div class="stat-title">Additional price (mounthly)</div>
<div class="stat-value text-white">
ISK <%= @additional_price
|> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
<div class="stat-actions text-end"></div>
</div>
</div>
</.form>
</.header>
</div>
</div> </div>
</div> </div>
<.input
type="checkbox"
field={f[:store_custom_labels]}
label="Store system custom labels"
/>
</.form>
</:actions>
</.header>
<.header :if={@active_settings_tab == "import"} class="bordered border-1 border-zinc-800">
Import/Export Map Settings
<:actions>
<.form :if={assigns |> Map.get(:import_form, false)} for={@import_form} phx-change="import">
<div phx-drop-target="{@uploads.settings.ref}">
<.live_file_input upload={@uploads.settings} />
</div>
</.form>
<progress :if={@importing} class="progress w-56"></progress>
<.button
id="export-settings-btn"
class="mt-8"
type="button"
disabled={@importing}
phx-hook="DownloadJson"
data-name={@map_slug}
data-content={Jason.encode!(assigns[:export_settings] || %{})}
>
<.icon name="hero-document-arrow-down-solid" class="w-4 h-4" /> Export Settings
</.button>
</:actions>
</.header>
<.header :if={@active_settings_tab == "balance"} class="bordered border-1 border-zinc-800">
<div class="stats w-full bg-primary text-primary-content">
<div class="stat">
<div class="stat-figure text-primary">
<.button :if={not @is_topping_up?} class="mt-2" type="button" phx-click="show_topup">
<.icon name="hero-banknotes-solid" class="w-4 h-4" /> Top Up
</.button>
</div>
<div class="stat-title">Map balance</div>
<div class="stat-value text-white">
ISK <%= @map_balance |> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
<div class="stat-actions text-end"></div>
</div> </div>
</div> </div>
</div>
<.form
:let={f}
:if={@is_topping_up?}
for={@topup_form}
phx-change="validate_topup"
phx-submit="topup"
>
<.live_select
field={f[:amount]}
update_min_len={0}
options={@amounts}
placeholder="Select topup amount"
/>
<div class="modal-action">
<.button class="mt-2" type="button" phx-click="hide_topup">
Cancel
</.button>
<.button class="mt-2" type="submit">
Top Up
</.button>
</div>
</.form>
</.header>
<.table
:if={@active_settings_tab == "subscription"}
class="!max-h-[20vh] !overflow-y-auto"
empty_label="No active subscriptions, using alpha plan by default."
id="active-subscriptions-tbl"
rows={@map_subscriptions}
>
<:col :let={subscription} label="Subscription Plan">
<%= subscription.plan %>
</:col>
<:col :let={subscription} label="Status">
<%= subscription.status %>
</:col>
<:col :let={subscription} label="Characters Limit">
<%= subscription.characters_limit %>
</:col>
<:col :let={subscription} label="Hubs Limit">
<%= subscription.hubs_limit %>
</:col>
<:col :let={subscription} label="Active Till">
<.local_time
:if={subscription.active_till}
id={"subscription-active-till-#{subscription.id}"}
at={subscription.active_till}
>
<%= subscription.active_till %>
</.local_time>
</:col>
<:col :let={subscription} label="Auto Renew">
<%= if subscription.auto_renew?, do: "Yes", else: "No" %>
</:col>
<:action :let={subscription}>
<div class="tooltip tooltip-left" data-tip="Edit subscription">
<button
:if={subscription.status == :active && subscription.plan != :alpha}
phx-click="edit-subscription"
phx-value-id={subscription.id}
>
<.icon name="hero-pencil-square-solid" class="w-4 h-4 hover:text-white" />
</button>
</div>
</:action>
<:action :let={subscription}>
<div class="tooltip tooltip-left" data-tip="Cancel subscription">
<button
:if={subscription.status == :active && subscription.plan != :alpha}
phx-click="cancel-subscription"
phx-value-id={subscription.id}
data={[confirm: "Please confirm to cancel subscription!"]}
>
<.icon name="hero-trash-solid" class="w-4 h-4 hover:text-white" />
</button>
</div>
</:action>
</.table>
<div class="modal-action"> <div class="modal-action">
<div <div
@@ -402,102 +614,4 @@
</.button> </.button>
</div> </div>
</div> </div>
<.header
:if={@active_settings_tab == "subscription" && @is_adding_subscription?}
class="bordered border-1 flex flex-col gap-4"
>
<div :if={is_nil(@selected_subscription)}>
Add subscription
<div class="badge badge-secondary">Limited time offer: 50%</div>
</div>
<div :if={not is_nil(@selected_subscription)}>
Edit subscription
<div class="badge badge-secondary">Limited time offer: 50%</div>
</div>
<.form
:let={f}
for={@subscription_form}
phx-change="validate_subscription"
phx-submit={if is_nil(@selected_subscription), do: "subscribe", else: "update_subscription"}
>
<.input
:if={is_nil(@selected_subscription)}
type="select"
field={f[:period]}
class="p-dropdown p-component p-inputwrapper"
placeholder="Subscription period"
options={@subscription_periods}
/>
<.input
field={f[:characters_limit]}
label="Characters limit"
show_value={true}
type="range"
min="300"
max="5000"
step="100"
class="range range-xs"
/>
<.input
field={f[:hubs_limit]}
label="Hubs limit"
show_value={true}
type="range"
min="20"
max="50"
step="10"
class="range range-xs"
/>
<.input field={f[:auto_renew?]} label="Auto Renew" type="checkbox" />
<div
:if={is_nil(@selected_subscription)}
class="stats w-full bg-primary text-primary-content"
>
<div class="stat">
<div class="stat-figure text-primary">
<.button type="submit">
Subscribe
</.button>
</div>
<div class="flex gap-8">
<div>
<div class="stat-title">Estimated price</div>
<div class="stat-value text-white">
ISK <%= (@estimated_price - @discount)
|> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
</div>
<div>
<div class="stat-title">Discount</div>
<div class="stat-value text-white relative">
ISK <%= @discount |> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
<span class="absolute top-0 right-0 text-xs text-white discount" />
</div>
</div>
</div>
</div>
</div>
<div
:if={not is_nil(@selected_subscription)}
class="stats w-full bg-primary text-primary-content"
>
<div class="stat">
<div class="stat-figure text-primary">
<.button type="button" phx-click="cancel_edit_subscription">
Cancel
</.button>
<.button type="submit">
Update
</.button>
</div>
<div class="stat-title">Additional price (mounthly)</div>
<div class="stat-value text-white">
ISK <%= @additional_price |> Number.to_human(units: ["", "K", "M", "B", "T", "P"]) %>
</div>
<div class="stat-actions text-end"></div>
</div>
</div>
</.form>
</.header>
</.modal> </.modal>

View File

@@ -31,10 +31,10 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
31,6,Shuttle,0,0,0,0,0,1 31,6,Shuttle,0,0,0,0,0,1
32,1,Alliance,None,0,0,0,0,0 32,1,Alliance,None,0,0,0,0,0
38,7,Shield Extender,82,0,0,0,0,1 38,7,Shield Extender,82,0,0,0,0,1
39,7,Shield Recharger,83,0,0,0,0,1 39,7,Shield Recharger,26451,0,0,0,0,1
40,7,Shield Booster,84,0,0,0,0,1 40,7,Shield Booster,84,0,0,0,0,1
41,7,Remote Shield Booster,86,0,0,0,0,1 41,7,Remote Shield Booster,86,0,0,0,0,1
43,7,Capacitor Recharger,90,0,0,0,0,1 43,7,Capacitor Recharger,26457,0,0,0,0,1
46,7,Propulsion Module,96,0,0,0,0,1 46,7,Propulsion Module,96,0,0,0,0,1
47,7,Cargo Scanner,106,0,0,0,0,1 47,7,Cargo Scanner,106,0,0,0,0,1
48,7,Ship Scanner,107,0,0,0,0,1 48,7,Ship Scanner,107,0,0,0,0,1
@@ -44,8 +44,8 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
54,7,Mining Laser,138,0,0,0,0,1 54,7,Mining Laser,138,0,0,0,0,1
55,7,Projectile Weapon,384,0,0,0,0,1 55,7,Projectile Weapon,384,0,0,0,0,1
56,7,Missile Launcher,168,0,0,0,0,0 56,7,Missile Launcher,168,0,0,0,0,0
57,7,Shield Power Relay,0,0,0,0,0,1 57,7,Shield Power Relay,26450,0,0,0,0,1
59,7,Gyrostabilizer,0,0,0,0,0,1 59,7,Gyrostabilizer,26452,0,0,0,0,1
60,7,Damage Control,0,0,0,0,0,1 60,7,Damage Control,0,0,0,0,0,1
61,7,Capacitor Battery,0,0,0,0,0,1 61,7,Capacitor Battery,0,0,0,0,0,1
62,7,Armor Repair Unit,0,1,0,0,0,1 62,7,Armor Repair Unit,0,1,0,0,0,1
@@ -86,10 +86,10 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
110,9,Titan Blueprint,None,1,0,0,0,1 110,9,Titan Blueprint,None,1,0,0,0,1
111,9,Shuttle Blueprint,0,1,0,0,0,1 111,9,Shuttle Blueprint,0,1,0,0,0,1
118,9,Shield Extender Blueprint,82,1,0,0,0,1 118,9,Shield Extender Blueprint,82,1,0,0,0,1
119,9,Shield Recharger Blueprint,83,1,0,0,0,1 119,9,Shield Recharger Blueprint,26451,1,0,0,0,1
120,9,Shield Booster Blueprint,84,1,0,0,0,1 120,9,Shield Booster Blueprint,84,1,0,0,0,1
121,9,Remote Shield Booster Blueprint,86,1,0,0,0,1 121,9,Remote Shield Booster Blueprint,86,1,0,0,0,1
123,9,Capacitor Recharger Blueprint,90,1,0,0,0,1 123,9,Capacitor Recharger Blueprint,26457,1,0,0,0,1
126,9,Propulsion Module Blueprint,96,1,0,0,0,1 126,9,Propulsion Module Blueprint,96,1,0,0,0,1
127,9,Cargo Scanner Blueprint,106,1,0,0,0,1 127,9,Cargo Scanner Blueprint,106,1,0,0,0,1
128,9,Ship Scanner Blueprint,107,1,0,0,0,1 128,9,Ship Scanner Blueprint,107,1,0,0,0,1
@@ -149,14 +149,14 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
201,7,ECM,0,0,0,0,0,1 201,7,ECM,0,0,0,0,0,1
202,7,ECCM,0,0,0,0,0,1 202,7,ECCM,0,0,0,0,0,1
203,7,Sensor Backup Array,0,0,0,0,0,1 203,7,Sensor Backup Array,0,0,0,0,0,1
205,7,Heat Sink,0,0,0,0,0,1 205,7,Heat Sink,26453,0,0,0,0,1
208,7,Sensor Dampener,105,0,0,0,0,1 208,7,Sensor Dampener,105,0,0,0,0,1
209,7,Remote Tracking Computer,3346,0,0,0,0,1 209,7,Remote Tracking Computer,3346,0,0,0,0,1
210,7,Signal Amplifier,0,0,0,0,0,1 210,7,Signal Amplifier,0,0,0,0,0,1
211,7,Tracking Enhancer,0,0,0,0,0,1 211,7,Tracking Enhancer,0,0,0,0,0,1
212,7,Sensor Booster,74,0,0,0,0,1 212,7,Sensor Booster,74,0,0,0,0,1
213,7,Tracking Computer,3346,0,0,0,0,1 213,7,Tracking Computer,3346,0,0,0,0,1
218,9,Heat Sink Blueprint,0,1,0,0,0,1 218,9,Heat Sink Blueprint,26453,1,0,0,0,1
223,9,Sensor Booster Blueprint,0,1,0,0,0,1 223,9,Sensor Booster Blueprint,0,1,0,0,0,1
224,9,Tracking Computer Blueprint,0,1,0,0,0,1 224,9,Tracking Computer Blueprint,0,1,0,0,0,1
225,7,Cheat Module Group,0,0,0,0,0,0 225,7,Cheat Module Group,0,0,0,0,0,0
@@ -197,7 +197,7 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
299,18,Repair Drone,0,0,0,0,0,0 299,18,Repair Drone,0,0,0,0,0,0
300,20,Cyberimplant,0,1,0,0,0,1 300,20,Cyberimplant,0,1,0,0,0,1
301,11,Concord Drone,0,0,0,0,0,0 301,11,Concord Drone,0,0,0,0,0,0
302,7,Magnetic Field Stabilizer,0,0,0,0,0,1 302,7,Magnetic Field Stabilizer,26454,0,0,0,0,1
303,20,Booster,0,0,0,0,0,1 303,20,Booster,0,0,0,0,0,1
304,20,DNA Mutator,0,0,0,0,0,0 304,20,DNA Mutator,0,0,0,0,0,0
305,2,Comet,0,0,0,0,0,0 305,2,Comet,0,0,0,0,0,0
@@ -595,10 +595,10 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
764,7,Overdrive Injector System,0,0,0,0,0,1 764,7,Overdrive Injector System,0,0,0,0,0,1
765,7,Expanded Cargohold,0,0,0,0,0,1 765,7,Expanded Cargohold,0,0,0,0,0,1
766,7,Power Diagnostic System,0,0,0,0,0,1 766,7,Power Diagnostic System,0,0,0,0,0,1
767,7,Capacitor Power Relay,0,0,0,0,0,1 767,7,Capacitor Power Relay,26455,0,0,0,0,1
768,7,Capacitor Flux Coil,0,0,0,0,0,1 768,7,Capacitor Flux Coil,26456,0,0,0,0,1
769,7,Reactor Control Unit,0,0,0,0,0,1 769,7,Reactor Control Unit,0,0,0,0,0,1
770,7,Shield Flux Coil,0,0,0,0,0,1 770,7,Shield Flux Coil,26449,0,0,0,0,1
771,7,Missile Launcher Heavy Assault,3241,0,0,0,0,1 771,7,Missile Launcher Heavy Assault,3241,0,0,0,0,1
772,8,Heavy Assault Missile,3237,0,0,0,1,1 772,8,Heavy Assault Missile,3237,0,0,0,1,1
773,7,Rig Armor,0,0,0,0,0,1 773,7,Rig Armor,0,0,0,0,0,1
@@ -1322,7 +1322,7 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
1962,66,Structure QA Modules,None,0,0,0,0,0 1962,66,Structure QA Modules,None,0,0,0,0,0
1964,17,Mutaplasmids,None,0,0,0,0,1 1964,17,Mutaplasmids,None,0,0,0,0,1
1966,66,Structure Capacitor Battery,None,0,0,0,0,1 1966,66,Structure Capacitor Battery,None,0,0,0,0,1
1967,66,Structure Capacitor Power Relay,None,0,0,0,0,1 1967,66,Structure Capacitor Power Relay,26455,0,0,0,0,1
1968,66,Structure Armor Reinforcer,None,0,0,0,0,1 1968,66,Structure Armor Reinforcer,None,0,0,0,0,1
1969,7,Abyssal Modules,None,0,0,0,0,0 1969,7,Abyssal Modules,None,0,0,0,0,0
1971,2,Abyssal Hazards,None,0,0,0,0,0 1971,2,Abyssal Hazards,None,0,0,0,0,0
@@ -1523,6 +1523,12 @@ groupID,categoryID,groupName,iconID,useBasePrice,anchored,anchorable,fittableNon
4802,11,Asteroid Sansha's Nation Officer Frigate,None,0,0,0,0,0 4802,11,Asteroid Sansha's Nation Officer Frigate,None,0,0,0,0,0
4803,11,Asteroid Serpentis Officer Cruiser,None,0,0,0,0,0 4803,11,Asteroid Serpentis Officer Cruiser,None,0,0,0,0,0
4804,11,Asteroid Serpentis Officer Frigate,None,0,0,0,0,0 4804,11,Asteroid Serpentis Officer Frigate,None,0,0,0,0,0
4807,7,Breacher Pod Launchers,None,0,0,0,0,1
4808,8,SCARAB Breacher Pods,None,0,0,0,1,1
4810,22,Mercenary Den,None,0,0,0,0,1
4811,9,Mercenary Den Blueprint,None,1,0,0,0,1
4820,9,Mutaplasmid Blueprint,None,1,0,0,0,1
4821,17,Atavum,None,1,0,0,0,1
350858,350001,Infantry Weapons,None,1,0,0,0,0 350858,350001,Infantry Weapons,None,1,0,0,0,0
351064,350001,Infantry Dropsuits,None,1,0,0,0,0 351064,350001,Infantry Dropsuits,None,1,0,0,0,0
351121,350001,Infantry Modules,None,1,0,0,0,0 351121,350001,Infantry Modules,None,1,0,0,0,0
1 groupID categoryID groupName iconID useBasePrice anchored anchorable fittableNonSingleton published
31 31 6 Shuttle 0 0 0 0 0 1
32 32 1 Alliance None 0 0 0 0 0
33 38 7 Shield Extender 82 0 0 0 0 1
34 39 7 Shield Recharger 83 26451 0 0 0 0 1
35 40 7 Shield Booster 84 0 0 0 0 1
36 41 7 Remote Shield Booster 86 0 0 0 0 1
37 43 7 Capacitor Recharger 90 26457 0 0 0 0 1
38 46 7 Propulsion Module 96 0 0 0 0 1
39 47 7 Cargo Scanner 106 0 0 0 0 1
40 48 7 Ship Scanner 107 0 0 0 0 1
44 54 7 Mining Laser 138 0 0 0 0 1
45 55 7 Projectile Weapon 384 0 0 0 0 1
46 56 7 Missile Launcher 168 0 0 0 0 0
47 57 7 Shield Power Relay 0 26450 0 0 0 0 1
48 59 7 Gyrostabilizer 0 26452 0 0 0 0 1
49 60 7 Damage Control 0 0 0 0 0 1
50 61 7 Capacitor Battery 0 0 0 0 0 1
51 62 7 Armor Repair Unit 0 1 0 0 0 1
86 110 9 Titan Blueprint None 1 0 0 0 1
87 111 9 Shuttle Blueprint 0 1 0 0 0 1
88 118 9 Shield Extender Blueprint 82 1 0 0 0 1
89 119 9 Shield Recharger Blueprint 83 26451 1 0 0 0 1
90 120 9 Shield Booster Blueprint 84 1 0 0 0 1
91 121 9 Remote Shield Booster Blueprint 86 1 0 0 0 1
92 123 9 Capacitor Recharger Blueprint 90 26457 1 0 0 0 1
93 126 9 Propulsion Module Blueprint 96 1 0 0 0 1
94 127 9 Cargo Scanner Blueprint 106 1 0 0 0 1
95 128 9 Ship Scanner Blueprint 107 1 0 0 0 1
149 201 7 ECM 0 0 0 0 0 1
150 202 7 ECCM 0 0 0 0 0 1
151 203 7 Sensor Backup Array 0 0 0 0 0 1
152 205 7 Heat Sink 0 26453 0 0 0 0 1
153 208 7 Sensor Dampener 105 0 0 0 0 1
154 209 7 Remote Tracking Computer 3346 0 0 0 0 1
155 210 7 Signal Amplifier 0 0 0 0 0 1
156 211 7 Tracking Enhancer 0 0 0 0 0 1
157 212 7 Sensor Booster 74 0 0 0 0 1
158 213 7 Tracking Computer 3346 0 0 0 0 1
159 218 9 Heat Sink Blueprint 0 26453 1 0 0 0 1
160 223 9 Sensor Booster Blueprint 0 1 0 0 0 1
161 224 9 Tracking Computer Blueprint 0 1 0 0 0 1
162 225 7 Cheat Module Group 0 0 0 0 0 0
197 299 18 Repair Drone 0 0 0 0 0 0
198 300 20 Cyberimplant 0 1 0 0 0 1
199 301 11 Concord Drone 0 0 0 0 0 0
200 302 7 Magnetic Field Stabilizer 0 26454 0 0 0 0 1
201 303 20 Booster 0 0 0 0 0 1
202 304 20 DNA Mutator 0 0 0 0 0 0
203 305 2 Comet 0 0 0 0 0 0
595 764 7 Overdrive Injector System 0 0 0 0 0 1
596 765 7 Expanded Cargohold 0 0 0 0 0 1
597 766 7 Power Diagnostic System 0 0 0 0 0 1
598 767 7 Capacitor Power Relay 0 26455 0 0 0 0 1
599 768 7 Capacitor Flux Coil 0 26456 0 0 0 0 1
600 769 7 Reactor Control Unit 0 0 0 0 0 1
601 770 7 Shield Flux Coil 0 26449 0 0 0 0 1
602 771 7 Missile Launcher Heavy Assault 3241 0 0 0 0 1
603 772 8 Heavy Assault Missile 3237 0 0 0 1 1
604 773 7 Rig Armor 0 0 0 0 0 1
1322 1962 66 Structure QA Modules None 0 0 0 0 0
1323 1964 17 Mutaplasmids None 0 0 0 0 1
1324 1966 66 Structure Capacitor Battery None 0 0 0 0 1
1325 1967 66 Structure Capacitor Power Relay None 26455 0 0 0 0 1
1326 1968 66 Structure Armor Reinforcer None 0 0 0 0 1
1327 1969 7 Abyssal Modules None 0 0 0 0 0
1328 1971 2 Abyssal Hazards None 0 0 0 0 0
1523 4802 11 Asteroid Sansha's Nation Officer Frigate None 0 0 0 0 0
1524 4803 11 Asteroid Serpentis Officer Cruiser None 0 0 0 0 0
1525 4804 11 Asteroid Serpentis Officer Frigate None 0 0 0 0 0
1526 4807 7 Breacher Pod Launchers None 0 0 0 0 1
1527 4808 8 SCARAB Breacher Pods None 0 0 0 1 1
1528 4810 22 Mercenary Den None 0 0 0 0 1
1529 4811 9 Mercenary Den Blueprint None 1 0 0 0 1
1530 4820 9 Mutaplasmid Blueprint None 1 0 0 0 1
1531 4821 17 Atavum None 1 0 0 0 1
1532 350858 350001 Infantry Weapons None 1 0 0 0 0
1533 351064 350001 Infantry Dropsuits None 1 0 0 0 0
1534 351121 350001 Infantry Modules None 1 0 0 0 0

File diff suppressed because it is too large Load Diff