group page / edit work with non admin users

This commit is contained in:
mbecker20
2023-04-09 22:38:36 +00:00
parent b13e624a66
commit 96fc5b0ca8
4 changed files with 241 additions and 516 deletions

View File

@@ -2,19 +2,16 @@ import {
Component,
} from "solid-js";
import { useAppDimensions } from "../../state/DimensionProvider";
import { useAppState } from "../../state/StateProvider";
import Grid from "../shared/layout/Grid";
import SimpleTabs from "../shared/tabs/SimpleTabs";
import Summary from "./Summary";
import Builds from "./Tree/Builds";
import Groups from "./Tree/Groups2";
import Groups from "./Tree/Groups";
import { TreeProvider } from "./Tree/Provider";
import Servers from "./Tree/Servers";
import Updates from "./Updates/Updates";
const Home: Component<{}> = (p) => {
const { isSemiMobile } = useAppDimensions();
const { servers } = useAppState();
return (
<>
<Grid
@@ -33,10 +30,6 @@ const Home: Component<{}> = (p) => {
title: "servers",
element: () => <Groups />,
},
// {
// title: "servers",
// element: () => <Servers serverIDs={servers.ids()!} showAdd />,
// },
{
title: "builds",
element: () => <Builds />

View File

@@ -1,27 +1,35 @@
import { Component, createMemo, createSignal, For, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useLocalStorageToggle } from "../../../util/hooks";
import Icon from "../../shared/Icon";
import Input from "../../shared/Input";
import Flex from "../../shared/layout/Flex";
import { Component, For, Show, createMemo, createSignal } from "solid-js";
import Grid from "../../shared/layout/Grid";
import { useLocalStorage, useToggle } from "../../../util/hooks";
import Flex from "../../shared/layout/Flex";
import { TREE_SORTS, TreeSortType, useTreeState } from "./Provider";
import { useAppState } from "../../../state/StateProvider";
import Input from "../../shared/Input";
import Selector from "../../shared/menu/Selector";
import { NewGroup } from "../../New";
import s from "../home.module.scss";
import { combineClasses } from "../../../util/helpers";
import Icon from "../../shared/Icon";
import { useAppDimensions } from "../../../state/DimensionProvider";
import ConfirmButton from "../../shared/ConfirmButton";
import Server from "./Server";
import { useWindowKeyDown } from "../../../util/hooks";
import Menu from "../../shared/menu/Menu";
import { client } from "../../..";
import ConfirmButton from "../../shared/ConfirmButton";
import { TreeSortType, TREE_SORTS, useTreeState } from "./Provider";
import Selector from "../../shared/menu/Selector";
import { useUser } from "../../../state/UserProvider";
import { PermissionLevel } from "../../../types";
const Groups: Component<{}> = (p) => {
const { isSemiMobile } = useAppDimensions();
const { groups } = useAppState();
const [groupFilter, setGroupFilter] = createSignal("");
const [selected, setSelected] = useLocalStorage<string | null>(
null,
"home-selected-group-v1"
);
const [searchFilter, setSearchFilter] = createSignal("");
const { sort, setSort, group_sorter } = useTreeState();
const [editing, toggleEditing, setEditing] = useToggle();
const groupIDs = createMemo(() => {
if (groups.loaded()) {
const filters = groupFilter()
const filters = searchFilter()
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
@@ -42,176 +50,249 @@ const Groups: Component<{}> = (p) => {
}
});
return (
<Grid style={{ height: "fit-content" }}>
<Grid gridTemplateColumns="1fr auto auto">
<Grid class="full-width">
<Grid gridTemplateColumns={selected() ? "auto 1fr auto" : "1fr auto"}>
<Show when={selected()}>
<Flex alignItems="center">
<button class="grey" onClick={() => setSelected(null)}>
<Icon type="arrow-left" />
</button>
<h1 style={{ margin: "0 1rem" }}>
{selected() === "all" ? "all" : groups.get(selected()!)?.name}
</h1>
</Flex>
</Show>
<Input
placeholder="filter groups"
value={groupFilter()}
onEdit={setGroupFilter}
placeholder={`filter ${selected() ? "servers" : "groups"}`}
value={searchFilter()}
onEdit={setSearchFilter}
style={{ width: "100%", padding: "0.5rem" }}
/>
<Selector
label={<div class="dimmed">sort by:</div>}
selected={sort()}
items={TREE_SORTS as any as string[]}
onSelect={(mode) => setSort(mode as TreeSortType)}
position="bottom right"
targetClass="blue"
targetStyle={{ height: "100%" }}
containerStyle={{ height: "100%" }}
/>
<NewGroup />
<Flex alignItems="center" style={{ width: "fit-content" }}>
<Selector
label={<div class="dimmed">sort by:</div>}
selected={sort()}
items={TREE_SORTS as any as string[]}
onSelect={(mode) => setSort(mode as TreeSortType)}
position="bottom right"
targetClass="blue"
targetStyle={{ height: "100%" }}
containerStyle={{ height: "100%" }}
/>
<Show when={selected()} fallback={<NewGroup />}>
<Show when={selected() !== "all"}>
<button
class="blue"
onClick={toggleEditing}
style={{ height: "100%" }}
>
<Icon type="edit" />
</button>
<AddServerToGroup groupId={selected()!} />
</Show>
</Show>
</Flex>
</Grid>
<For each={groupIDs()}>{(id) => <Group id={id} />}</For>
<Show
when={selected()}
fallback={
<Grid gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}>
<GroupButton id="all" setSelected={setSelected} />
<For each={groupIDs()}>
{(id) => <GroupButton id={id} setSelected={setSelected} />}
</For>
</Grid>
}
>
<Group
id={selected()!}
searchFilter={searchFilter()}
exit={() => {
setSelected(null);
setEditing(false);
}}
editing={editing()}
/>
</Show>
</Grid>
);
};
export default Groups;
const Group: Component<{ id: string }> = (p) => {
const { groups, servers, ungroupedServerIds } = useAppState();
const { server_sorter } = useTreeState();
const group = () => groups.get(p.id);
const serverIDs = () => group()?.servers.sort(server_sorter());
const [open, toggleOpen] = useLocalStorageToggle(p.id + "-group-homeopen-v1");
const [showAdd, setShowAdd] = createSignal(false);
const [edit, setEdit] = createSignal(false);
const GroupButton: Component<{
id: string;
setSelected: (s: string) => void;
}> = (p) => {
const { isSemiMobile } = useAppDimensions();
const { groups, servers } = useAppState();
const isAll = () => p.id === "all";
const name = () => {
if (isAll()) {
return "all";
}
return groups.get(p.id)?.name;
};
const serverCount = () => {
if (isAll()) {
return servers.ids()?.length || 0;
}
return (
groups.get(p.id)?.servers.filter((server_id) => servers.get(server_id))
.length || 0
);
};
return (
<Show when={group()}>
<button
class={combineClasses(s.ServerButton, "shadow")}
onClick={toggleOpen}
>
<Flex alignItems="center">
<Icon type={open() ? "chevron-up" : "chevron-down"} width="1rem" />
<h1 style={{ "font-size": "1.25rem" }}>{group()?.name}</h1>
<Flex
class="card light hover shadow"
style={{
"grid-column": isAll() && !isSemiMobile() ? "span 2" : undefined,
"justify-content": "space-between",
cursor: "pointer",
}}
onClick={() => p.setSelected(p.id)}
>
<h1>{name()}</h1>
<Flex alignItems="center">
<Flex gap="0.4rem">
<div>{serverCount()}</div>
<div class="dimmed">{`server${serverCount() > 1 ? "s" : ""}`}</div>
</Flex>
<Flex alignItems="center">
<h2>
{serverIDs()!.length} server{serverIDs()!.length > 1 ? "s" : ""}
</h2>
<Show when={open()}>
<button
class="blue"
onClick={(e) => {
e.stopPropagation();
setEdit((edit) => !edit);
}}
>
<Icon type="edit" />
</button>
<Show when={ungroupedServerIds()?.length || 0 > 0}>
<Menu
show={showAdd()}
close={(e) => {
e.stopPropagation();
setShowAdd(false);
}}
position="bottom right"
target={
<button
class="green"
onClick={(e) => {
e.stopPropagation();
setShowAdd(true);
}}
>
<Icon type="plus" />
</button>
}
menuStyle={{ gap: "0.5rem" }}
content={
<>
{/* <Input placeholder="search" style={{ width: "10rem" }} /> */}
<For each={ungroupedServerIds()!}>
{(server_id) => {
const server = () => servers.get(server_id)!;
return (
<ConfirmButton
class="lightgrey"
style={{ width: "100%" }}
onConfirm={() =>
client.update_group({
...group()!,
servers: [...group()!.servers, server_id],
})
}
>
{server().server.name}
</ConfirmButton>
);
}}
</For>
</>
}
/>
</Show>
</Show>
<Show when={!isAll()}>
<ConfirmButton
class="red"
onConfirm={() => client.delete_group(p.id)}
>
<Icon type="trash" />
</ConfirmButton>
</Show>
</Flex>
</Flex>
);
};
const Group: Component<{
id: string;
searchFilter: string;
editing: boolean;
exit: () => void;
}> = (p) => {
const { groups, servers } = useAppState();
const { server_sorter } = useTreeState();
const group = () => groups.get(p.id);
const serverIDs = createMemo(() => {
if (servers.loaded()) {
const filters = p.searchFilter
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
const serverIds = (
p.id === "all"
? servers.ids()
: groups
.get(p.id)
?.servers.filter((server_id) => servers.get(server_id))
)?.sort(server_sorter());
return serverIds
?.filter((id) => {
const name = servers.get(id)!.server.name;
for (const term of filters) {
if (!name.includes(term)) {
return false;
}
}
return true;
})
.sort(server_sorter());
} else {
return undefined;
}
});
useWindowKeyDown((e) => {
if (e.key === "ArrowLeft" || e.key === "Escape") {
p.exit();
}
});
return (
<For each={serverIDs()}>
{(id) => (
<Flex alignItems="center">
<Server id={id} />
<Show when={p.editing}>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_group({
...group()!,
servers: group()!.servers.filter((member) => member !== id),
});
}}
>
<Icon type="minus" />
</ConfirmButton>
</Show>
</Flex>
</button>
<Show when={serverIDs() && open()}>
<Grid
placeItems="center"
gridTemplateColumns="1fr auto 1fr"
style={{ width: "100%" }}
>
<div
class="lightgrey"
style={{ opacity: 0.7, width: "100%", height: "3px" }}
/>
<div style={{ opacity: 0.7 }}>servers</div>
<div
class="lightgrey"
style={{ opacity: 0.7, width: "100%", height: "3px" }}
/>
</Grid>
<For each={serverIDs()}>
{(id) => {
return (
<Flex alignItems="center">
<Server id={id} />
<Show when={edit()}>
)}
</For>
);
};
const AddServerToGroup: Component<{ groupId: string }> = (p) => {
const { user, user_id } = useUser();
const { groups, servers, ungroupedServerIds } = useAppState();
const [showAdd, setShowAdd] = createSignal(false);
const group = () => groups.get(p.groupId);
const canEdit = () =>
user().admin ||
group()?.permissions?.[user_id()] === PermissionLevel.Update;
return (
<Show
when={canEdit() && ((group() && ungroupedServerIds()?.length) || 0 > 0)}
>
<Menu
show={showAdd()}
close={(e) => {
e.stopPropagation();
setShowAdd(false);
}}
position="bottom right"
target={
<button
class="green"
onClick={(e) => {
e.stopPropagation();
setShowAdd(true);
}}
>
<Icon type="plus" />
</button>
}
menuStyle={{ gap: "0.5rem" }}
content={
<>
{/* <Input placeholder="search" style={{ width: "10rem" }} /> */}
<For each={ungroupedServerIds()!}>
{(server_id) => {
const server = () => servers.get(server_id)!;
return (
<ConfirmButton
class="red"
onConfirm={() => {
class="lightgrey"
style={{ width: "100%" }}
onConfirm={() =>
client.update_group({
...group()!,
servers: group()!.servers.filter(
(member) => member !== id
),
});
}}
servers: [...group()!.servers, server_id],
})
}
>
<Icon type="minus" />
{server().server.name}
</ConfirmButton>
</Show>
</Flex>
);
}}
</For>
<Grid
placeItems="center"
gridTemplateColumns="1fr auto 1fr"
style={{ width: "100%" }}
>
<div
class="lightgrey"
style={{ opacity: 0.7, width: "100%", height: "3px" }}
/>
<div style={{ opacity: 0.7 }}>end</div>
<div
class="lightgrey"
style={{ opacity: 0.7, width: "100%", height: "3px" }}
/>
</Grid>
</Show>
);
}}
</For>
</>
}
/>
</Show>
);
};

View File

@@ -1,283 +0,0 @@
import { Component, For, Show, createMemo, createSignal } from "solid-js";
import Grid from "../../shared/layout/Grid";
import { useLocalStorage, useToggle } from "../../../util/hooks";
import Flex from "../../shared/layout/Flex";
import { TREE_SORTS, TreeSortType, useTreeState } from "./Provider";
import { useAppState } from "../../../state/StateProvider";
import Input from "../../shared/Input";
import Selector from "../../shared/menu/Selector";
import { NewGroup } from "../../New";
import Icon from "../../shared/Icon";
import { useAppDimensions } from "../../../state/DimensionProvider";
import ConfirmButton from "../../shared/ConfirmButton";
import Server from "./Server";
import { useWindowKeyDown } from "../../../util/hooks";
import Menu from "../../shared/menu/Menu";
import { client } from "../../..";
const Groups: Component<{}> = (p) => {
const { isSemiMobile } = useAppDimensions();
const { groups } = useAppState();
const [selected, setSelected] = useLocalStorage<string | null>(
null,
"home-selected-group-v1"
);
const [searchFilter, setSearchFilter] = createSignal("");
const { sort, setSort, group_sorter } = useTreeState();
const [editing, toggleEditing, setEditing] = useToggle();
const groupIDs = createMemo(() => {
if (groups.loaded()) {
const filters = searchFilter()
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
return groups
.ids()
?.filter((id) => {
const name = groups.get(id)!.name;
for (const term of filters) {
if (!name.includes(term)) {
return false;
}
}
return true;
})
.sort(group_sorter());
} else {
return undefined;
}
});
return (
<Grid class="full-width">
<Grid gridTemplateColumns={selected() ? "auto 1fr auto" : "1fr auto"}>
<Show when={selected()}>
<Flex alignItems="center">
<button class="grey" onClick={() => setSelected(null)}>
<Icon type="arrow-left" />
</button>
<h1 style={{ margin: "0 1rem" }}>
{selected() === "all" ? "all" : groups.get(selected()!)?.name}
</h1>
</Flex>
</Show>
<Input
placeholder={`filter ${selected() ? "servers" : "groups"}`}
value={searchFilter()}
onEdit={setSearchFilter}
style={{ width: "100%", padding: "0.5rem" }}
/>
<Flex alignItems="center" style={{ width: "fit-content" }}>
<Selector
label={<div class="dimmed">sort by:</div>}
selected={sort()}
items={TREE_SORTS as any as string[]}
onSelect={(mode) => setSort(mode as TreeSortType)}
position="bottom right"
targetClass="blue"
targetStyle={{ height: "100%" }}
containerStyle={{ height: "100%" }}
/>
<Show when={selected()} fallback={<NewGroup />}>
<Show when={selected() !== "all"}>
<button
class="blue"
onClick={toggleEditing}
style={{ height: "100%" }}
>
<Icon type="edit" />
</button>
<AddServerToGroup groupId={selected()!} />
</Show>
</Show>
</Flex>
</Grid>
<Show
when={selected()}
fallback={
<Grid gridTemplateColumns={isSemiMobile() ? "1fr" : "1fr 1fr"}>
<GroupButton id="all" setSelected={setSelected} />
<For each={groupIDs()}>
{(id) => <GroupButton id={id} setSelected={setSelected} />}
</For>
</Grid>
}
>
<Group
id={selected()!}
searchFilter={searchFilter()}
exit={() => {
setSelected(null);
setEditing(false);
}}
editing={editing()}
/>
</Show>
</Grid>
);
};
export default Groups;
const GroupButton: Component<{
id: string;
setSelected: (s: string) => void;
}> = (p) => {
const { isSemiMobile } = useAppDimensions();
const { groups, servers } = useAppState();
const isAll = () => p.id === "all";
const name = () => {
if (isAll()) {
return "all";
}
return groups.get(p.id)?.name;
};
const serverCount = () => {
if (isAll()) {
return servers.ids()?.length || 0;
}
return groups.get(p.id)?.servers.length || 0;
};
return (
<Flex
class="card light hover shadow"
style={{
"grid-column": isAll() && !isSemiMobile() ? "span 2" : undefined,
"justify-content": "space-between",
cursor: "pointer",
}}
onClick={() => p.setSelected(p.id)}
>
<h1>{name()}</h1>
<Flex alignItems="center">
<Flex gap="0.4rem">
<div>{serverCount()}</div>
<div class="dimmed">{`server${serverCount() > 1 ? "s" : ""}`}</div>
</Flex>
<Show when={!isAll()}>
<ConfirmButton
class="red"
onConfirm={() => client.delete_group(p.id)}
>
<Icon type="trash" />
</ConfirmButton>
</Show>
</Flex>
</Flex>
);
};
const Group: Component<{
id: string;
searchFilter: string;
editing: boolean;
exit: () => void;
}> = (p) => {
const { groups, servers } = useAppState();
const { server_sorter } = useTreeState();
const group = () => groups.get(p.id);
const serverIDs = createMemo(() => {
if (servers.loaded()) {
const filters = p.searchFilter
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
const serverIds = (
p.id === "all" ? servers.ids() : groups.get(p.id)?.servers
)?.sort(server_sorter());
return serverIds
?.filter((id) => {
const name = servers.get(id)!.server.name;
for (const term of filters) {
if (!name.includes(term)) {
return false;
}
}
return true;
})
.sort(server_sorter());
} else {
return undefined;
}
});
useWindowKeyDown((e) => {
if (e.key === "ArrowLeft" || e.key === "Escape") {
p.exit();
}
});
return (
<For each={serverIDs()}>
{(id) => (
<Flex alignItems="center">
<Server id={id} />
<Show when={p.editing}>
<ConfirmButton
class="red"
onConfirm={() => {
client.update_group({
...group()!,
servers: group()!.servers.filter((member) => member !== id),
});
}}
>
<Icon type="minus" />
</ConfirmButton>
</Show>
</Flex>
)}
</For>
);
};
const AddServerToGroup: Component<{ groupId: string }> = (p) => {
const { groups, servers, ungroupedServerIds } = useAppState();
const [showAdd, setShowAdd] = createSignal(false);
const group = () => groups.get(p.groupId);
return (
<Show when={(group() && ungroupedServerIds()?.length) || 0 > 0}>
<Menu
show={showAdd()}
close={(e) => {
e.stopPropagation();
setShowAdd(false);
}}
position="bottom right"
target={
<button
class="green"
onClick={(e) => {
e.stopPropagation();
setShowAdd(true);
}}
>
<Icon type="plus" />
</button>
}
menuStyle={{ gap: "0.5rem" }}
content={
<>
{/* <Input placeholder="search" style={{ width: "10rem" }} /> */}
<For each={ungroupedServerIds()!}>
{(server_id) => {
const server = () => servers.get(server_id)!;
return (
<ConfirmButton
class="lightgrey"
style={{ width: "100%" }}
onConfirm={() =>
client.update_group({
...group()!,
servers: [...group()!.servers, server_id],
})
}
>
{server().server.name}
</ConfirmButton>
);
}}
</For>
</>
}
/>
</Show>
);
};

View File

@@ -1,66 +0,0 @@
import { Component, createMemo, createSignal, For, Show } from "solid-js";
import { useAppState } from "../../../state/StateProvider";
import { useUser } from "../../../state/UserProvider";
import Input from "../../shared/Input";
import Grid from "../../shared/layout/Grid";
import Selector from "../../shared/menu/Selector";
import AddServer from "../../topbar/AddServer";
import { TreeSortType, TREE_SORTS, useTreeState } from "./Provider";
import Server from "./Server";
const Servers: Component<{ serverIDs: string[]; showAdd?: boolean }> = (p) => {
const { user } = useUser();
const { servers } = useAppState();
const { sort, setSort, server_sorter } = useTreeState();
const [serverFilter, setServerFilter] = createSignal("");
const serverIDs = createMemo(() => {
if (servers.loaded()) {
const filters = serverFilter()
.split(" ")
.filter((term) => term.length > 0)
.map((term) => term.toLowerCase());
return p.serverIDs.filter((id) => {
const name = servers.get(id)!.server.name;
for (const term of filters) {
if (!name.includes(term)) {
return false;
}
}
return true;
})
.sort(server_sorter());
} else {
return undefined;
}
});
return (
<Grid style={{ height: "fit-content" }}>
<Grid gridTemplateColumns="1fr auto auto">
<Input
placeholder="filter servers"
value={serverFilter()}
onEdit={setServerFilter}
style={{ width: "100%", padding: "0.5rem" }}
/>
<Selector
label={<div class="dimmed">sort by:</div>}
selected={sort()}
items={TREE_SORTS as any as string[]}
onSelect={(mode) => setSort(mode as TreeSortType)}
position="bottom right"
targetClass="blue"
targetStyle={{ height: "100%" }}
containerStyle={{ height: "100%" }}
/>
<Show
when={p.showAdd && (user().admin || user().create_server_permissions)}
>
<AddServer />
</Show>
</Grid>
<For each={serverIDs()}>{(id) => <Server id={id} />}</For>
</Grid>
);
};
export default Servers;