webui: Add basis for Hashing Controls

This commit is contained in:
Kevin Kandlbinder 2022-09-07 16:12:09 +02:00
parent a9bdf17a5c
commit 03cc6a60a7
29 changed files with 2292 additions and 98 deletions

View file

@ -39,6 +39,7 @@ type Config struct {
type ResolverRoot interface {
Comment() CommentResolver
Entry() EntryResolver
HashCheckerConfig() HashCheckerConfigResolver
List() ListResolver
Mutation() MutationResolver
Query() QueryResolver
@ -90,7 +91,7 @@ type ComplexityRoot struct {
HashCheckerConfig struct {
ChatNotice func(childComplexity int) int
HashCheckMode func(childComplexity int) int
SubscribedLists func(childComplexity int) int
SubscribedLists func(childComplexity int, first *int, after *string) int
}
List struct {
@ -198,6 +199,9 @@ type EntryResolver interface {
AddedBy(ctx context.Context, obj *model.Entry) (*model.User, error)
Comments(ctx context.Context, obj *model.Entry, first *int, after *string) (*model.CommentConnection, error)
}
type HashCheckerConfigResolver interface {
SubscribedLists(ctx context.Context, obj *model.HashCheckerConfig, first *int, after *string) (*model.ListConnection, error)
}
type ListResolver interface {
Creator(ctx context.Context, obj *model.List) (*model.User, error)
Comments(ctx context.Context, obj *model.List, first *int, after *string) (*model.CommentConnection, error)
@ -402,7 +406,12 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
break
}
return e.complexity.HashCheckerConfig.SubscribedLists(childComplexity), true
args, err := ec.field_HashCheckerConfig_subscribedLists_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.HashCheckerConfig.SubscribedLists(childComplexity, args["first"].(*int), args["after"].(*string)), true
case "List.comments":
if e.complexity.List.Comments == nil {
@ -1081,7 +1090,7 @@ enum HashCheckerMode {
type HashCheckerConfig {
chatNotice: Boolean!
hashCheckMode: HashCheckerMode!
subscribedLists: [ID!]
subscribedLists(first: Int, after: String): ListConnection!
}
type Room {
@ -1457,6 +1466,30 @@ func (ec *executionContext) field_Entry_partOf_args(ctx context.Context, rawArgs
return args, nil
}
func (ec *executionContext) field_HashCheckerConfig_subscribedLists_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *int
if tmp, ok := rawArgs["first"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("first"))
arg0, err = ec.unmarshalOInt2ᚖint(ctx, tmp)
if err != nil {
return nil, err
}
}
args["first"] = arg0
var arg1 *string
if tmp, ok := rawArgs["after"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
arg1, err = ec.unmarshalOString2ᚖstring(ctx, tmp)
if err != nil {
return nil, err
}
}
args["after"] = arg1
return args, nil
}
func (ec *executionContext) field_List_comments_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@ -3037,30 +3070,50 @@ func (ec *executionContext) _HashCheckerConfig_subscribedLists(ctx context.Conte
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.SubscribedLists, nil
return ec.resolvers.HashCheckerConfig().SubscribedLists(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*string))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.([]string)
res := resTmp.(*model.ListConnection)
fc.Result = res
return ec.marshalOID2ᚕstringᚄ(ctx, field.Selections, res)
return ec.marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_HashCheckerConfig_subscribedLists(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "HashCheckerConfig",
Field: field,
IsMethod: false,
IsResolver: false,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type ID does not have child fields")
switch field.Name {
case "pageInfo":
return ec.fieldContext_ListConnection_pageInfo(ctx, field)
case "edges":
return ec.fieldContext_ListConnection_edges(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ListConnection", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_HashCheckerConfig_subscribedLists_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
@ -10224,19 +10277,35 @@ func (ec *executionContext) _HashCheckerConfig(ctx context.Context, sel ast.Sele
out.Values[i] = ec._HashCheckerConfig_chatNotice(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "hashCheckMode":
out.Values[i] = ec._HashCheckerConfig_hashCheckMode(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
atomic.AddUint32(&invalids, 1)
}
case "subscribedLists":
field := field
out.Values[i] = ec._HashCheckerConfig_subscribedLists(ctx, field, obj)
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._HashCheckerConfig_subscribedLists(ctx, field, obj)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
}
out.Concurrently(i, func() graphql.Marshaler {
return innerFunc(ctx)
})
default:
panic("unknown field " + strconv.Quote(field.Name))
}
@ -11713,6 +11782,20 @@ func (ec *executionContext) marshalNList2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrix
return ec._List(ctx, sel, v)
}
func (ec *executionContext) marshalNListConnection2githubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v model.ListConnection) graphql.Marshaler {
return ec._ListConnection(ctx, sel, &v)
}
func (ec *executionContext) marshalNListConnection2ᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListConnection(ctx context.Context, sel ast.SelectionSet, v *model.ListConnection) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ListConnection(ctx, sel, v)
}
func (ec *executionContext) marshalNListEdge2ᚕᚖgithubᚗcomᚋUnkn0wnCatᚋmatrixᚑvelesᚋgraphᚋmodelᚐListEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.ListEdge) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup

View file

@ -1,6 +1,9 @@
package model
import "github.com/Unkn0wnCat/matrix-veles/internal/config"
import (
"github.com/Unkn0wnCat/matrix-veles/internal/config"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Room struct {
ID string `json:"id"`
@ -16,16 +19,10 @@ type Room struct {
type HashCheckerConfig struct {
ChatNotice bool `json:"chatNotice"`
HashCheckMode HashCheckerMode `json:"hashCheckMode"`
SubscribedLists []string `json:"subscribedLists"`
SubscribedLists []*primitive.ObjectID
}
func MakeRoom(room *config.RoomConfig) *Room {
var subscribed []string
for _, subId := range room.HashChecker.SubscribedLists {
subscribed = append(subscribed, subId.Hex())
}
return &Room{
ID: room.ID.Hex(),
Name: room.Name,
@ -37,7 +34,7 @@ func MakeRoom(room *config.RoomConfig) *Room {
HashCheckerConfig: &HashCheckerConfig{
ChatNotice: room.HashChecker.NoticeToChat,
HashCheckMode: AllHashCheckerMode[room.HashChecker.HashCheckMode],
SubscribedLists: subscribed,
SubscribedLists: room.HashChecker.SubscribedLists,
},
}
}

View file

@ -40,7 +40,7 @@ enum HashCheckerMode {
type HashCheckerConfig {
chatNotice: Boolean!
hashCheckMode: HashCheckerMode!
subscribedLists: [ID!]
subscribedLists(first: Int, after: String): ListConnection!
}
type Room {

View file

@ -136,6 +136,83 @@ func (r *entryResolver) Comments(ctx context.Context, obj *model.Entry, first *i
return ResolveComments(comments, first, after)
}
// SubscribedLists is the resolver for the subscribedLists field.
func (r *hashCheckerConfigResolver) SubscribedLists(ctx context.Context, obj *model.HashCheckerConfig, first *int, after *string) (*model.ListConnection, error) {
ids := obj.SubscribedLists
if len(ids) == 0 {
return nil, nil
}
startIndex := 0
if after != nil {
afterInt := new(big.Int)
afterInt.SetString(*after, 16)
idInt := new(big.Int)
set := false
for i, id := range obj.SubscribedLists {
idInt.SetString(id.Hex(), 16)
if idInt.Cmp(afterInt) > 0 {
startIndex = i
set = true
break
}
}
if !set {
return nil, nil
}
}
if startIndex >= len(ids) {
return nil, nil
}
ids = ids[startIndex:]
length := 25
if first != nil {
length = *first
}
cut := false
if len(ids) > length {
cut = true
ids = ids[:length]
}
var edges []*model.ListEdge
for _, id := range ids {
dbList, err := db.GetListByID(*id)
if err != nil {
return nil, err
}
edges = append(edges, &model.ListEdge{
Node: model.MakeList(dbList),
Cursor: dbList.ID.Hex(),
})
}
return &model.ListConnection{
PageInfo: &model.PageInfo{
HasPreviousPage: startIndex > 0,
HasNextPage: cut,
StartCursor: edges[0].Cursor,
EndCursor: edges[len(edges)-1].Cursor,
},
Edges: edges,
}, nil
}
// Creator is the resolver for the creator field.
func (r *listResolver) Creator(ctx context.Context, obj *model.List) (*model.User, error) {
user, err := db.GetUserByID(obj.CreatorID)
@ -1359,6 +1436,11 @@ func (r *Resolver) Comment() generated.CommentResolver { return &commentResolver
// Entry returns generated.EntryResolver implementation.
func (r *Resolver) Entry() generated.EntryResolver { return &entryResolver{r} }
// HashCheckerConfig returns generated.HashCheckerConfigResolver implementation.
func (r *Resolver) HashCheckerConfig() generated.HashCheckerConfigResolver {
return &hashCheckerConfigResolver{r}
}
// List returns generated.ListResolver implementation.
func (r *Resolver) List() generated.ListResolver { return &listResolver{r} }
@ -1370,6 +1452,7 @@ func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type commentResolver struct{ *Resolver }
type entryResolver struct{ *Resolver }
type hashCheckerConfigResolver struct{ *Resolver }
type listResolver struct{ *Resolver }
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

View file

@ -40,7 +40,7 @@ enum HashCheckerMode {
type HashCheckerConfig {
chatNotice: Boolean!
hashCheckMode: HashCheckerMode!
subscribedLists: [ID!]
subscribedLists(first: Int, after: String): ListConnection!
}
type Room {

View file

@ -18,6 +18,14 @@ import Rooms from "./components/panel/rooms/Rooms";
import RoomsQueryGraphql, {RoomsQuery} from "./components/panel/rooms/__generated__/RoomsQuery.graphql";
import RoomDetailQueryGraphql, {RoomDetailQuery} from "./components/panel/rooms/__generated__/RoomDetailQuery.graphql";
import RoomDetail from "./components/panel/rooms/RoomDetail";
import ListsQueryGraphql, {ListsQuery} from "./components/panel/hashing/lists/__generated__/ListsQuery.graphql";
import ListDetailQueryGraphql, {ListDetailQuery} from "./components/panel/hashing/lists/__generated__/ListDetailQuery.graphql";
import Lists from "./components/panel/hashing/lists/Lists";
import ListDetail from "./components/panel/hashing/lists/ListDetail";
import EntriesQueryGraphql, {EntriesQuery} from "./components/panel/hashing/entries/__generated__/EntriesQuery.graphql";
import EntryDetailQueryGraphql, {EntryDetailQuery} from "./components/panel/hashing/entries/__generated__/EntryDetailQuery.graphql";
import Entries from "./components/panel/hashing/entries/Entries";
import EntryDetail from "./components/panel/hashing/entries/EntryDetail";
function App() {
const dispatch = useAppDispatch()
@ -37,6 +45,22 @@ function App() {
RoomDetailQueryGraphql
)
const [listsInitialState, loadListsQuery, disposeListsQuery] = useQueryLoader<ListsQuery>(
ListsQueryGraphql
)
const [listDetailInitialState, loadListDetailQuery, disposeListDetailQuery] = useQueryLoader<ListDetailQuery>(
ListDetailQueryGraphql
)
const [entriesInitialState, loadEntriesQuery, disposeEntriesQuery] = useQueryLoader<EntriesQuery>(
EntriesQueryGraphql
)
const [entryDetailInitialState, loadEntryDetailQuery, disposeEntryDetailQuery] = useQueryLoader<EntryDetailQuery>(
EntryDetailQueryGraphql
)
// This needs to be here to prevent a weird bug
useTranslation()
@ -50,13 +74,17 @@ function App() {
if(auth.jwt !== null) {
loadQuery({})
loadRoomsQuery({})
loadListsQuery({})
loadEntriesQuery({})
return
}
disposeQuery()
disposeRoomsQuery()
disposeListsQuery()
disposeEntriesQuery()
environment.getStore().notify(undefined, true)
}, [auth, disposeQuery, disposeRoomsQuery, environment, loadQuery, loadRoomsQuery])
}, [auth, disposeQuery, disposeRoomsQuery, environment, loadQuery, loadRoomsQuery, loadListsQuery, disposeListsQuery, loadEntriesQuery, disposeEntriesQuery])
return (
@ -70,11 +98,11 @@ function App() {
<Route path={"rooms"} element={<RequireAuth>{roomsInitialState && <Rooms initialQueryRef={roomsInitialState}/>}</RequireAuth>}>
<Route path={":id"} element={<RequireAuth><RoomDetail initialQueryRef={roomDetailInitialState} fetch={loadRoomDetailQuery} dispose={disposeRoomDetailQuery}/></RequireAuth>} />
</Route>
<Route path={"hashing/lists"} element={<RequireAuth><h1>lists</h1></RequireAuth>}>
<Route path={":id"} element={<h1>list detail</h1>} />
<Route path={"hashing/lists"} element={<RequireAuth>{listsInitialState && <Lists initialQueryRef={listsInitialState}/>}</RequireAuth>}>
<Route path={":id"} element={<RequireAuth><ListDetail initialQueryRef={listDetailInitialState} fetch={loadListDetailQuery} dispose={disposeListDetailQuery}/></RequireAuth>} />
</Route>
<Route path={"hashing/entries"} element={<RequireAuth><h1>entries</h1></RequireAuth>}>
<Route path={":id"} element={<h1>entry detail</h1>} />
<Route path={"hashing/entries"} element={<RequireAuth>{entriesInitialState && <Entries initialQueryRef={entriesInitialState}/>}</RequireAuth>}>
<Route path={":id"} element={<RequireAuth><EntryDetail initialQueryRef={entryDetailInitialState} fetch={loadEntryDetailQuery} dispose={disposeEntryDetailQuery}/></RequireAuth>} />
</Route>
</Route>
</Routes>

View file

@ -0,0 +1,77 @@
@import "../../../../globals";
$slideOverBreakpoint: 1000px;
.roomsContainer {
display: flex;
height: calc(100% + 2*var(--veles-layout-padding));
margin: var(--veles-layout-padding-inverse);
width: calc(100% + 2*var(--veles-layout-padding));
overflow: hidden;
.roomsOverview {
flex-grow: 1;
flex-shrink: 1;
width: 100px;
padding: var(--veles-layout-padding);
transition: margin-right .25s;
&.leaveSpace {
margin-right: 400px;
@media(max-width: $slideOverBreakpoint) {
margin-right: 0;
}
}
}
.slideOver {
position: absolute;
top: 0;
right: -400px;
height: 100%;
width: 400px;
border-left: thin solid var(--veles-color-border);
transition: right .25s, border-left .25s, width .25s;
@media(max-width: $slideOverBreakpoint) {
width: 100%;
border-left: 0 solid var(--veles-color-border);
margin-right: 0;
right: -100%;
}
background-color: var(--veles-color-background);
&.active {
right: 0;
}
.slideOverContent {
padding: var(--veles-layout-padding);
}
.slideOverHeader {
display: flex;
border-bottom: thin solid var(--veles-color-border);
align-items: center;
>* {
padding: var(--veles-layout-padding-slim) var(--veles-layout-padding);
}
> span {
flex-grow: 1;
}
> button {
margin: 0;
background: transparent;
font: inherit;
color: inherit;
border: none;
cursor: pointer;
}
}
}
}

View file

@ -0,0 +1,61 @@
import React, {useState} from "react";
import {useNavigate, useOutlet} from "react-router-dom";
import styles from "./Entries.module.scss";
import {Trans, useTranslation} from "react-i18next";
import EntriesTable from "./EntriesTable";
import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {EntriesQuery} from "./__generated__/EntriesQuery.graphql";
import {X} from "lucide-react";
import {LoaderSuspense} from "../../../common/Loader";
type Props = {
initialQueryRef: PreloadedQuery<EntriesQuery>,
}
const Entries = ({initialQueryRef}: Props) => {
const outlet = useOutlet()
const navigate = useNavigate()
const {t} = useTranslation()
const data = usePreloadedQuery(
graphql`
query EntriesQuery($first: String, $count: Int) {
...EntriesTableFragment
}
`,
initialQueryRef
)
const defaultTitle = t("entries.details", "Details")
const [title, setTitle] = useState(defaultTitle)
return <div className={styles.roomsContainer}>
<div className={styles.roomsOverview + (outlet ? " "+styles.leaveSpace : "")}>
<h1><Trans i18nKey={"entries.title"}>Entry Management</Trans></h1>
<EntriesTable initialQueryRef={data}/>
</div>
<div className={styles.slideOver + (outlet ? " "+styles.active : "")}>
<EntriesSlideOverTitleContext.Provider value={setTitle}>
<div className={styles.slideOverHeader}>
<span>{title}</span>
<button onClick={() => navigate("/hashing/entries")}><X/></button>
</div>
<div className={styles.slideOverContent}>
<LoaderSuspense>
{outlet}
</LoaderSuspense>
</div>
</EntriesSlideOverTitleContext.Provider>
</div>
</div>
}
export const EntriesSlideOverTitleContext = React.createContext<React.Dispatch<React.SetStateAction<string>>|null>(null)
export default Entries

View file

@ -0,0 +1,5 @@
@import "../../../../globals";
.roomsTableWrapper {
@include tableWrapper;
}

View file

@ -0,0 +1,50 @@
import React from "react";
import {usePaginationFragment} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {EntriesTableFragment$key} from "./__generated__/EntriesTableFragment.graphql";
import styles from "./EntriesTable.module.scss";
import {useNavigate} from "react-router-dom";
import {Trans} from "react-i18next";
type Props = {
initialQueryRef: EntriesTableFragment$key,
}
const EntriesTable = ({initialQueryRef}: Props) => {
const {data} = usePaginationFragment(graphql`
fragment EntriesTableFragment on Query @refetchable(queryName: "EntriesTableFragment") {
entries(after: $first, first: $count) @connection(key: "EntriesTableFragment_entries") {
edges {
node {
id
tags
}
}
}
}
`, initialQueryRef)
const navigate = useNavigate()
return <div className={styles.roomsTableWrapper}>
<table className={styles.roomsTable}>
<thead>
<tr>
<th><Trans i18nKey={"entries.id"}>Entry ID</Trans></th>
</tr>
</thead>
<tbody>
{
data.entries?.edges.map((edge) => {
return <tr onClick={() => {navigate("/hashing/entries/"+edge.node.id)}} key={edge.node.id}>
<td>{edge.node.id}</td>
</tr>;
})
}
</tbody>
</table>
</div>
}
export default EntriesTable

View file

@ -0,0 +1,47 @@
@import "../../../../globals";
.title {
display: flex;
gap: var(--veles-layout-padding)
}
.label {
display: block;
padding: var(--veles-layout-padding-slim);
}
.powerLevelInput {
@include inputGroup;
input {
flex-grow: 1;
}
}
.hashCheckModeChooser {
@include input;
width: 100%;
}
.listTable {
@include tableWrapper;
padding: var(--veles-layout-padding);
}
.settingsGroup {
display: block;
margin: var(--veles-layout-padding) var(--veles-layout-padding-inverse);
border-top: thin solid var(--veles-color-border);
border-bottom: thin solid var(--veles-color-border);
.settingsGroupTitle {
display: block;
border-bottom: thin solid var(--veles-color-border);
padding: var(--veles-layout-padding-slim) var(--veles-layout-padding);
}
.settingsGroupContent {
padding: var(--veles-layout-padding);
}
}

View file

@ -0,0 +1,171 @@
import React, {useContext, useEffect} from "react";
import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {EntryDetailQuery, EntryDetailQuery$variables} from "./__generated__/EntryDetailQuery.graphql";
import {DisposeFn} from "relay-runtime";
import {useParams} from "react-router-dom";
import {EntriesSlideOverTitleContext} from "./Entries";
//import styles from "./EntryDetail.module.scss";
type Props = {
initialQueryRef: PreloadedQuery<EntryDetailQuery> | null | undefined,
fetch: (variables: EntryDetailQuery$variables) => void
dispose: DisposeFn
}
type PropsFinal = {
initialQueryRef: PreloadedQuery<EntryDetailQuery>,
}
const RoomDetailInner = ({initialQueryRef}: PropsFinal) => {
/*const {t} = useTranslation()
const navigate = useNavigate()*/
const data = usePreloadedQuery(
graphql`
query EntryDetailQuery($id: ID) {
entry(id:$id) {
id
tags
}
}
`,
initialQueryRef
)
const titleSetContext = useContext(EntriesSlideOverTitleContext)
titleSetContext && data.entry?.id && titleSetContext(data.entry.id);
return <>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
/*return <>
<ToggleButton name={"activeSwitch"} label={t("panel:rooms.detail.activate.label", {defaultValue: "Activate Room"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
deactivate: !ev.currentTarget.checked
}
}
})
}} disabled={reconfiguringRoom || ((data.room || false) && !data.room.active && !data.room.deactivated)} checked={data.room?.active}/>
<ToggleButton name={"debugSwitch"} label={t("panel:rooms.detail.debug.label", {defaultValue: "Debug-Mode"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
debug: ev.currentTarget.checked
}
}
})
}} disabled={reconfiguringRoom || (!data.room)} checked={data.room?.debug}/>
<label htmlFor={"adminPowerLevelInput"} className={styles.label}><Trans i18nKey={"panel:rooms.detail.adminPowerLevel.label"}>Admin-Power Level</Trans></label>
<div className={styles.powerLevelInput}>
<input type={"number"} min={"50"} max={"100"} step={"1"} value={newAdminPowerLevel || data.room?.adminPowerLevel} onChange={(ev) => {
if(Number.parseInt(ev.currentTarget.value) === data.room?.adminPowerLevel) {
setNewAdminPowerLevel(null)
return
}
setNewAdminPowerLevel(Number.parseInt(ev.currentTarget.value))
}} id={"adminPowerLevelInput"} />
<button disabled={reconfiguringRoom || (!newAdminPowerLevel)} onClick={() => {
if(!newAdminPowerLevel) {
return
}
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
adminPowerLevel: newAdminPowerLevel
}
}
})
setNewAdminPowerLevel(null)
}}><Trans i18nKey={"panel:rooms.detail.set.label"}>Set</Trans></button>
</div>
<div className={styles.settingsGroup}>
<span className={styles.settingsGroupTitle}><Trans i18nKey={"panel:rooms.detail.hashChecker.label"}>Hash-Checker</Trans></span>
<div className={styles.settingsGroupContent}>
<ToggleButton name={"chatNoticeSwitch"} label={t("panel:rooms.detail.hashChecker.notice.label", {defaultValue: "Send Notice to Chat"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
hashChecker: {
chatNotice: ev.currentTarget.checked
}
}
}
})
}} disabled={reconfiguringRoom || (!data.room)} checked={data.room?.hashCheckerConfig.chatNotice}/>
<label htmlFor={"hashCheckModeChooser"} className={styles.label}><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.label"}>Behaviour on Match</Trans></label>
<select value={data.room?.hashCheckerConfig.hashCheckMode} disabled={reconfiguringRoom || (!data.room)} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
hashChecker: {
hashCheckMode: ev.currentTarget.value as HashCheckerMode
}
}
}
})
}} className={styles.hashCheckModeChooser} id={"hashCheckModeChooser"}>
<option value="NOTICE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.NOTICE.label"}>Only send a Notice</Trans></option>
<option value="DELETE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.DELETE.label"}>Delete the Message</Trans></option>
<option value="MUTE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.MUTE.label"}>Mute User & Delete</Trans></option>
<option value="BAN"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.BAN.label"}>Ban User & Delete</Trans></option>
</select>
</div>
<div className={styles.listTable}>
<table>
<thead>
<tr>
<th><Trans i18nKey={"lists.name"}>Name</Trans></th>
<th><Trans i18nKey={"lists.id"}>List ID</Trans></th>
</tr>
</thead>
<tbody>
{
data.room?.hashCheckerConfig.subscribedLists.edges.map((edge) => {
return <tr onClick={() => {navigate("/hashing/lists/"+edge.node.id)}} key={edge.node.id}>
<td>{edge.node.name}</td>
<td>{edge.node.id}</td>
</tr>;
})
}
</tbody>
</table>
</div>
</div>
</>*/
}
const EntryDetail = ({initialQueryRef, fetch, dispose}: Props) => {
const {id} = useParams()
useEffect(() => {
fetch({id})
return () => {
dispose();
}
}, [id, dispose, fetch])
return initialQueryRef ? <RoomDetailInner initialQueryRef={initialQueryRef} /> : null
}
export default EntryDetail;

View file

@ -0,0 +1,186 @@
/**
* @generated SignedSource<<0d6e62e3c8c5ff07cb1c9f683624d372>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type EntriesQuery$variables = {
first?: string | null;
count?: number | null;
};
export type EntriesQuery$data = {
readonly " $fragmentSpreads": FragmentRefs<"EntriesTableFragment">;
};
export type EntriesQuery = {
variables: EntriesQuery$variables;
response: EntriesQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
v2 = [
{
"kind": "Variable",
"name": "after",
"variableName": "first"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "EntriesQuery",
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "EntriesTableFragment"
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "EntriesQuery",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "EntryConnection",
"kind": "LinkedField",
"name": "entries",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "EntryEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Entry",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"filters": null,
"handle": "connection",
"key": "EntriesTableFragment_entries",
"kind": "LinkedHandle",
"name": "entries"
}
]
},
"params": {
"cacheID": "cb7fcbe76a323cc0ce2ae532aa6fa861",
"id": null,
"metadata": {},
"name": "EntriesQuery",
"operationKind": "query",
"text": "query EntriesQuery(\n $first: String\n $count: Int\n) {\n ...EntriesTableFragment\n}\n\nfragment EntriesTableFragment on Query {\n entries(after: $first, first: $count) {\n edges {\n node {\n id\n tags\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "06398f7547759ada0c2066a0d13cf04c";
export default node;

View file

@ -0,0 +1,163 @@
/**
* @generated SignedSource<<38a8e1d2b568b81072536ddacd114e6f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type EntriesTableFragment$data = {
readonly entries: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly tags: ReadonlyArray<string> | null;
};
}>;
} | null;
readonly " $fragmentType": "EntriesTableFragment";
};
export type EntriesTableFragment$key = {
readonly " $data"?: EntriesTableFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"EntriesTableFragment">;
};
const node: ReaderFragment = (function(){
var v0 = [
"entries"
];
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "first"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "first",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "first"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [],
"operation": require('./EntriesTableFragment.graphql')
}
},
"name": "EntriesTableFragment",
"selections": [
{
"alias": "entries",
"args": null,
"concreteType": "EntryConnection",
"kind": "LinkedField",
"name": "__EntriesTableFragment_entries_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "EntryEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Entry",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
};
})();
(node as any).hash = "999faf383df2d42007f73da008f4c910";
export default node;

View file

@ -0,0 +1,97 @@
/**
* @generated SignedSource<<5a8663de6323dd94e27c8d5b53db80eb>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type EntryDetailQuery$variables = {
id?: string | null;
};
export type EntryDetailQuery$data = {
readonly entry: {
readonly id: string;
readonly tags: ReadonlyArray<string> | null;
} | null;
};
export type EntryDetailQuery = {
variables: EntryDetailQuery$variables;
response: EntryDetailQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
"concreteType": "Entry",
"kind": "LinkedField",
"name": "entry",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "EntryDetailQuery",
"selections": (v1/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "EntryDetailQuery",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "1f01a8cbc9fbd149cc3511fbfb5a0929",
"id": null,
"metadata": {},
"name": "EntryDetailQuery",
"operationKind": "query",
"text": "query EntryDetailQuery(\n $id: ID\n) {\n entry(id: $id) {\n id\n tags\n }\n}\n"
}
};
})();
(node as any).hash = "2121d3309f0f6813613afae4a045e275";
export default node;

View file

@ -0,0 +1,47 @@
@import "../../../../globals";
.title {
display: flex;
gap: var(--veles-layout-padding)
}
.label {
display: block;
padding: var(--veles-layout-padding-slim);
}
.powerLevelInput {
@include inputGroup;
input {
flex-grow: 1;
}
}
.hashCheckModeChooser {
@include input;
width: 100%;
}
.listTable {
@include tableWrapper;
padding: var(--veles-layout-padding);
}
.settingsGroup {
display: block;
margin: var(--veles-layout-padding) var(--veles-layout-padding-inverse);
border-top: thin solid var(--veles-color-border);
border-bottom: thin solid var(--veles-color-border);
.settingsGroupTitle {
display: block;
border-bottom: thin solid var(--veles-color-border);
padding: var(--veles-layout-padding-slim) var(--veles-layout-padding);
}
.settingsGroupContent {
padding: var(--veles-layout-padding);
}
}

View file

@ -0,0 +1,186 @@
import React, {useContext, useEffect} from "react";
import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {ListDetailQuery, ListDetailQuery$variables} from "./__generated__/ListDetailQuery.graphql";
import {DisposeFn} from "relay-runtime";
import {useParams} from "react-router-dom";
import {ListsSlideOverTitleContext} from "./Lists";
//import styles from "./ListDetail.module.scss";
type Props = {
initialQueryRef: PreloadedQuery<ListDetailQuery> | null | undefined,
fetch: (variables: ListDetailQuery$variables) => void
dispose: DisposeFn
}
type PropsFinal = {
initialQueryRef: PreloadedQuery<ListDetailQuery>,
}
const RoomDetailInner = ({initialQueryRef}: PropsFinal) => {
/*const {t} = useTranslation()
const navigate = useNavigate()*/
const data = usePreloadedQuery(
graphql`
query ListDetailQuery($id: ID) {
list(id:$id) {
id
name
tags
creator {
id
username
matrixLinks
}
maintainers(first: 100) {
edges {
node {
id
username
matrixLinks
}
}
}
}
}
`,
initialQueryRef
)
const titleSetContext = useContext(ListsSlideOverTitleContext)
titleSetContext && data.list?.name && titleSetContext(data.list.name);
return <>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
/*return <>
<ToggleButton name={"activeSwitch"} label={t("panel:rooms.detail.activate.label", {defaultValue: "Activate Room"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
deactivate: !ev.currentTarget.checked
}
}
})
}} disabled={reconfiguringRoom || ((data.room || false) && !data.room.active && !data.room.deactivated)} checked={data.room?.active}/>
<ToggleButton name={"debugSwitch"} label={t("panel:rooms.detail.debug.label", {defaultValue: "Debug-Mode"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
debug: ev.currentTarget.checked
}
}
})
}} disabled={reconfiguringRoom || (!data.room)} checked={data.room?.debug}/>
<label htmlFor={"adminPowerLevelInput"} className={styles.label}><Trans i18nKey={"panel:rooms.detail.adminPowerLevel.label"}>Admin-Power Level</Trans></label>
<div className={styles.powerLevelInput}>
<input type={"number"} min={"50"} max={"100"} step={"1"} value={newAdminPowerLevel || data.room?.adminPowerLevel} onChange={(ev) => {
if(Number.parseInt(ev.currentTarget.value) === data.room?.adminPowerLevel) {
setNewAdminPowerLevel(null)
return
}
setNewAdminPowerLevel(Number.parseInt(ev.currentTarget.value))
}} id={"adminPowerLevelInput"} />
<button disabled={reconfiguringRoom || (!newAdminPowerLevel)} onClick={() => {
if(!newAdminPowerLevel) {
return
}
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
adminPowerLevel: newAdminPowerLevel
}
}
})
setNewAdminPowerLevel(null)
}}><Trans i18nKey={"panel:rooms.detail.set.label"}>Set</Trans></button>
</div>
<div className={styles.settingsGroup}>
<span className={styles.settingsGroupTitle}><Trans i18nKey={"panel:rooms.detail.hashChecker.label"}>Hash-Checker</Trans></span>
<div className={styles.settingsGroupContent}>
<ToggleButton name={"chatNoticeSwitch"} label={t("panel:rooms.detail.hashChecker.notice.label", {defaultValue: "Send Notice to Chat"})} labelSrOnly={false} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
hashChecker: {
chatNotice: ev.currentTarget.checked
}
}
}
})
}} disabled={reconfiguringRoom || (!data.room)} checked={data.room?.hashCheckerConfig.chatNotice}/>
<label htmlFor={"hashCheckModeChooser"} className={styles.label}><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.label"}>Behaviour on Match</Trans></label>
<select value={data.room?.hashCheckerConfig.hashCheckMode} disabled={reconfiguringRoom || (!data.room)} onChange={(ev) => {
reconfigureRoom({
variables: {
reconfigureInput: {
id: data.room?.id!,
hashChecker: {
hashCheckMode: ev.currentTarget.value as HashCheckerMode
}
}
}
})
}} className={styles.hashCheckModeChooser} id={"hashCheckModeChooser"}>
<option value="NOTICE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.NOTICE.label"}>Only send a Notice</Trans></option>
<option value="DELETE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.DELETE.label"}>Delete the Message</Trans></option>
<option value="MUTE"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.MUTE.label"}>Mute User & Delete</Trans></option>
<option value="BAN"><Trans i18nKey={"panel:rooms.detail.hashChecker.hashCheckMode.BAN.label"}>Ban User & Delete</Trans></option>
</select>
</div>
<div className={styles.listTable}>
<table>
<thead>
<tr>
<th><Trans i18nKey={"lists.name"}>Name</Trans></th>
<th><Trans i18nKey={"lists.id"}>List ID</Trans></th>
</tr>
</thead>
<tbody>
{
data.room?.hashCheckerConfig.subscribedLists.edges.map((edge) => {
return <tr onClick={() => {navigate("/hashing/lists/"+edge.node.id)}} key={edge.node.id}>
<td>{edge.node.name}</td>
<td>{edge.node.id}</td>
</tr>;
})
}
</tbody>
</table>
</div>
</div>
</>*/
}
const ListDetail = ({initialQueryRef, fetch, dispose}: Props) => {
const {id} = useParams()
useEffect(() => {
fetch({id})
return () => {
dispose();
}
}, [id, dispose, fetch])
return initialQueryRef ? <RoomDetailInner initialQueryRef={initialQueryRef} /> : null
}
export default ListDetail;

View file

@ -0,0 +1,77 @@
@import "../../../../globals";
$slideOverBreakpoint: 1000px;
.roomsContainer {
display: flex;
height: calc(100% + 2*var(--veles-layout-padding));
margin: var(--veles-layout-padding-inverse);
width: calc(100% + 2*var(--veles-layout-padding));
overflow: hidden;
.roomsOverview {
flex-grow: 1;
flex-shrink: 1;
width: 100px;
padding: var(--veles-layout-padding);
transition: margin-right .25s;
&.leaveSpace {
margin-right: 400px;
@media(max-width: $slideOverBreakpoint) {
margin-right: 0;
}
}
}
.slideOver {
position: absolute;
top: 0;
right: -400px;
height: 100%;
width: 400px;
border-left: thin solid var(--veles-color-border);
transition: right .25s, border-left .25s, width .25s;
@media(max-width: $slideOverBreakpoint) {
width: 100%;
border-left: 0 solid var(--veles-color-border);
margin-right: 0;
right: -100%;
}
background-color: var(--veles-color-background);
&.active {
right: 0;
}
.slideOverContent {
padding: var(--veles-layout-padding);
}
.slideOverHeader {
display: flex;
border-bottom: thin solid var(--veles-color-border);
align-items: center;
>* {
padding: var(--veles-layout-padding-slim) var(--veles-layout-padding);
}
> span {
flex-grow: 1;
}
> button {
margin: 0;
background: transparent;
font: inherit;
color: inherit;
border: none;
cursor: pointer;
}
}
}
}

View file

@ -0,0 +1,61 @@
import React, {useState} from "react";
import {useNavigate, useOutlet} from "react-router-dom";
import styles from "./Lists.module.scss";
import {Trans, useTranslation} from "react-i18next";
import ListsTable from "./ListsTable";
import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {ListsQuery} from "./__generated__/ListsQuery.graphql";
import {X} from "lucide-react";
import {LoaderSuspense} from "../../../common/Loader";
type Props = {
initialQueryRef: PreloadedQuery<ListsQuery>,
}
const Lists = ({initialQueryRef}: Props) => {
const outlet = useOutlet()
const navigate = useNavigate()
const {t} = useTranslation()
const data = usePreloadedQuery(
graphql`
query ListsQuery($first: String, $count: Int) {
...ListsTableFragment
}
`,
initialQueryRef
)
const defaultTitle = t("lists.details", "Details")
const [title, setTitle] = useState(defaultTitle)
return <div className={styles.roomsContainer}>
<div className={styles.roomsOverview + (outlet ? " "+styles.leaveSpace : "")}>
<h1><Trans i18nKey={"lists.title"}>Available Lists</Trans></h1>
<ListsTable initialQueryRef={data}/>
</div>
<div className={styles.slideOver + (outlet ? " "+styles.active : "")}>
<ListsSlideOverTitleContext.Provider value={setTitle}>
<div className={styles.slideOverHeader}>
<span>{title}</span>
<button onClick={() => navigate("/hashing/lists")}><X/></button>
</div>
<div className={styles.slideOverContent}>
<LoaderSuspense>
{outlet}
</LoaderSuspense>
</div>
</ListsSlideOverTitleContext.Provider>
</div>
</div>
}
export const ListsSlideOverTitleContext = React.createContext<React.Dispatch<React.SetStateAction<string>>|null>(null)
export default Lists

View file

@ -0,0 +1,5 @@
@import "../../../../globals";
.roomsTableWrapper {
@include tableWrapper;
}

View file

@ -0,0 +1,60 @@
import React from "react";
import {usePaginationFragment} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {ListsTableFragment$key} from "./__generated__/ListsTableFragment.graphql";
import styles from "./ListsTable.module.scss";
import {useNavigate} from "react-router-dom";
import {Trans} from "react-i18next";
type Props = {
initialQueryRef: ListsTableFragment$key,
}
const ListsTable = ({initialQueryRef}: Props) => {
const {data} = usePaginationFragment(graphql`
fragment ListsTableFragment on Query @refetchable(queryName: "ListsTableFragment") {
lists(after: $first, first: $count) @connection(key: "ListsTableFragment_lists") {
edges {
node {
id
name
tags
creator {
id
username
matrixLinks
}
}
}
}
}
`, initialQueryRef)
const navigate = useNavigate()
return <div className={styles.roomsTableWrapper}>
<table className={styles.roomsTable}>
<thead>
<tr>
<th><Trans i18nKey={"lists.name"}>Name</Trans></th>
<th><Trans i18nKey={"lists.id"}>List ID</Trans></th>
<th><Trans i18nKey={"lists.creator"}>List Creator</Trans></th>
</tr>
</thead>
<tbody>
{
data.lists?.edges.map((edge) => {
return <tr onClick={() => {navigate("/hashing/lists/"+edge.node.id)}} key={edge.node.id}>
<td>{edge.node.name}</td>
<td>{edge.node.id}</td>
<td>{edge.node.creator.username}</td>
</tr>;
})
}
</tbody>
</table>
</div>
}
export default ListsTable

View file

@ -0,0 +1,185 @@
/**
* @generated SignedSource<<387ef79509f6e2371f71fa3d72d073fd>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
export type ListDetailQuery$variables = {
id?: string | null;
};
export type ListDetailQuery$data = {
readonly list: {
readonly id: string;
readonly name: string;
readonly tags: ReadonlyArray<string> | null;
readonly creator: {
readonly id: string;
readonly username: string;
readonly matrixLinks: ReadonlyArray<string> | null;
};
readonly maintainers: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly username: string;
readonly matrixLinks: ReadonlyArray<string> | null;
};
}>;
};
} | null;
};
export type ListDetailQuery = {
variables: ListDetailQuery$variables;
response: ListDetailQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "matrixLinks",
"storageKey": null
}
],
v3 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
"concreteType": "List",
"kind": "LinkedField",
"name": "list",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "creator",
"plural": false,
"selections": (v2/*: any*/),
"storageKey": null
},
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "UserConnection",
"kind": "LinkedField",
"name": "maintainers",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "UserEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": (v2/*: any*/),
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "maintainers(first:100)"
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ListDetailQuery",
"selections": (v3/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ListDetailQuery",
"selections": (v3/*: any*/)
},
"params": {
"cacheID": "25b9482991b61f15c2856c99875b239e",
"id": null,
"metadata": {},
"name": "ListDetailQuery",
"operationKind": "query",
"text": "query ListDetailQuery(\n $id: ID\n) {\n list(id: $id) {\n id\n name\n tags\n creator {\n id\n username\n matrixLinks\n }\n maintainers(first: 100) {\n edges {\n node {\n id\n username\n matrixLinks\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "7cef889868efd30077c76c270e95474e";
export default node;

View file

@ -0,0 +1,220 @@
/**
* @generated SignedSource<<5aab59ac780b7eaef3dee3a8745fbc28>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest, Query } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ListsQuery$variables = {
first?: string | null;
count?: number | null;
};
export type ListsQuery$data = {
readonly " $fragmentSpreads": FragmentRefs<"ListsTableFragment">;
};
export type ListsQuery = {
variables: ListsQuery$variables;
response: ListsQuery$data;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "count"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "first"
},
v2 = [
{
"kind": "Variable",
"name": "after",
"variableName": "first"
},
{
"kind": "Variable",
"name": "first",
"variableName": "count"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "ListsQuery",
"selections": [
{
"args": null,
"kind": "FragmentSpread",
"name": "ListsTableFragment"
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "ListsQuery",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "lists",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "creator",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "matrixLinks",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"filters": null,
"handle": "connection",
"key": "ListsTableFragment_lists",
"kind": "LinkedHandle",
"name": "lists"
}
]
},
"params": {
"cacheID": "97b29f2758a0594dc8e7260db058e120",
"id": null,
"metadata": {},
"name": "ListsQuery",
"operationKind": "query",
"text": "query ListsQuery(\n $first: String\n $count: Int\n) {\n ...ListsTableFragment\n}\n\nfragment ListsTableFragment on Query {\n lists(after: $first, first: $count) {\n edges {\n node {\n id\n name\n tags\n creator {\n id\n username\n matrixLinks\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n}\n"
}
};
})();
(node as any).hash = "af7a7db395ee385cea027ca6d8320a42";
export default node;

View file

@ -0,0 +1,203 @@
/**
* @generated SignedSource<<9406112d41db017f2dbc6315c2cd7064>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment, RefetchableFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type ListsTableFragment$data = {
readonly lists: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly tags: ReadonlyArray<string> | null;
readonly creator: {
readonly id: string;
readonly username: string;
readonly matrixLinks: ReadonlyArray<string> | null;
};
};
}>;
} | null;
readonly " $fragmentType": "ListsTableFragment";
};
export type ListsTableFragment$key = {
readonly " $data"?: ListsTableFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"ListsTableFragment">;
};
const node: ReaderFragment = (function(){
var v0 = [
"lists"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"kind": "RootArgument",
"name": "count"
},
{
"kind": "RootArgument",
"name": "first"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "count",
"cursor": "first",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "count",
"cursor": "first"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [],
"operation": require('./ListsTableFragment.graphql')
}
},
"name": "ListsTableFragment",
"selections": [
{
"alias": "lists",
"args": null,
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "__ListsTableFragment_lists_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "creator",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "username",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "matrixLinks",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
};
})();
(node as any).hash = "8b14551a242c87b90d76b77b05535cb2";
export default node;

View file

@ -3,7 +3,7 @@ import {PreloadedQuery, usePreloadedQuery} from "react-relay/hooks";
import {graphql} from "babel-plugin-relay/macro";
import {RoomDetailQuery, RoomDetailQuery$variables} from "./__generated__/RoomDetailQuery.graphql";
import {DisposeFn} from "relay-runtime";
import {useParams} from "react-router-dom";
import {useNavigate, useParams} from "react-router-dom";
import {RoomsSlideOverTitleContext} from "./Rooms";
import ToggleButton from "../../form_components/ToggleButton";
@ -26,6 +26,7 @@ const RoomDetailInner = ({initialQueryRef}: PropsFinal) => {
const [reconfigureRoom, reconfiguringRoom] = useReconfigureRoomMutation();
const [newAdminPowerLevel, setNewAdminPowerLevel] = useState<number|null>(null)
const {t} = useTranslation()
const navigate = useNavigate()
const data = usePreloadedQuery(
@ -42,7 +43,14 @@ const RoomDetailInner = ({initialQueryRef}: PropsFinal) => {
hashCheckerConfig {
chatNotice
hashCheckMode
subscribedLists
subscribedLists(first: 100) {
edges {
node {
id
name
}
}
}
}
}
}
@ -150,26 +158,18 @@ const RoomDetailInner = ({initialQueryRef}: PropsFinal) => {
</tr>
</thead>
<tbody>
{/*
data.rooms?.edges.map((edge) => {
return <tr onClick={() => {navigate("/rooms/"+edge.node.id)}} key={edge.node.id}>
<td>
{edge.node.debug && <span className={styles.badge + " " + styles.blue}><Trans i18nKey={"rooms.debug"}>Debug</Trans></span>}
{!edge.node.active && <span className={styles.badge + " " + styles.red}><Trans i18nKey={"rooms.inactive"}>Inactive</Trans></span>}
{edge.node.active && <span className={styles.badge + " " + styles.green}><Trans i18nKey={"rooms.active"}>Active</Trans></span>}
</td>
{
data.room?.hashCheckerConfig.subscribedLists.edges.map((edge) => {
return <tr onClick={() => {navigate("/hashing/lists/"+edge.node.id)}} key={edge.node.id}>
<td>{edge.node.name}</td>
<td>{edge.node.roomId}</td>
<td>{edge.node.id}</td>
</tr>;
})
*/}
}
</tbody>
</table>
</div>
</div>
<pre style={{opacity: .25}}>{JSON.stringify(data, null, 2)}</pre>
</>
}

View file

@ -1,5 +1,5 @@
/**
* @generated SignedSource<<58fc708823f0e42ade18c16f92c3b0d2>>
* @generated SignedSource<<1663469824a6a66c6959a37976f2a05e>>
* @lightSyntaxTransform
* @nogrep
*/
@ -25,7 +25,14 @@ export type RoomDetailQuery$data = {
readonly hashCheckerConfig: {
readonly chatNotice: boolean;
readonly hashCheckMode: HashCheckerMode;
readonly subscribedLists: ReadonlyArray<string> | null;
readonly subscribedLists: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
};
}>;
};
};
} | null;
};
@ -42,7 +49,21 @@ var v0 = [
"name": "id"
}
],
v1 = [
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v3 = [
{
"alias": null,
"args": [
@ -57,13 +78,7 @@ v1 = [
"name": "room",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
@ -92,13 +107,7 @@ v1 = [
"name": "debug",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
(v2/*: any*/),
{
"alias": null,
"args": null,
@ -130,10 +139,44 @@ v1 = [
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "subscribedLists",
"storageKey": null
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "subscribedLists(first:100)"
}
],
"storageKey": null
@ -148,7 +191,7 @@ return {
"kind": "Fragment",
"metadata": null,
"name": "RoomDetailQuery",
"selections": (v1/*: any*/),
"selections": (v3/*: any*/),
"type": "Query",
"abstractKey": null
},
@ -157,19 +200,19 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "RoomDetailQuery",
"selections": (v1/*: any*/)
"selections": (v3/*: any*/)
},
"params": {
"cacheID": "397376ecc93b3b405d66d01b7fd4de21",
"cacheID": "12bcb055f358f8be4fc522afa4daf9e3",
"id": null,
"metadata": {},
"name": "RoomDetailQuery",
"operationKind": "query",
"text": "query RoomDetailQuery(\n $id: ID\n) {\n room(id: $id) {\n id\n active\n deactivated\n adminPowerLevel\n debug\n name\n roomId\n hashCheckerConfig {\n chatNotice\n hashCheckMode\n subscribedLists\n }\n }\n}\n"
"text": "query RoomDetailQuery(\n $id: ID\n) {\n room(id: $id) {\n id\n active\n deactivated\n adminPowerLevel\n debug\n name\n roomId\n hashCheckerConfig {\n chatNotice\n hashCheckMode\n subscribedLists(first: 100) {\n edges {\n node {\n id\n name\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "338b222f1e379335edc6847c401f52f5";
(node as any).hash = "c5785d54de6de8e5986d7119f5dd7527";
export default node;

View file

@ -1,5 +1,4 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.scss';
import App from './App';
import {store} from './app/store';
@ -13,9 +12,11 @@ import {
import "./i18n";
import RelayEnvironment from "./RelayEnvironment";
import {LoaderSuspense} from "./components/common/Loader";
import {createRoot} from "react-dom/client";
const root = createRoot(document.getElementById('root')!);
ReactDOM.render(
root.render(
<React.StrictMode>
<Provider store={store}>
<RelayEnvironmentProvider environment={RelayEnvironment({store})}>
@ -26,8 +27,7 @@ ReactDOM.render(
</LoaderSuspense>
</RelayEnvironmentProvider>
</Provider>
</React.StrictMode>,
document.getElementById('root')
</React.StrictMode>
);
// If you want your app to work offline and load faster, you can change

View file

@ -23,7 +23,15 @@ const mutation = graphql`
hashCheckerConfig {
chatNotice
hashCheckMode
subscribedLists
subscribedLists(first: 100) {
edges {
node {
id
name
tags
}
}
}
}
}
}

View file

@ -1,5 +1,5 @@
/**
* @generated SignedSource<<c6a0a35dc80ab80f3fc8e42c6fe8a0f6>>
* @generated SignedSource<<dc9c36fa6948f6592ee63829e7c07251>>
* @lightSyntaxTransform
* @nogrep
*/
@ -36,7 +36,15 @@ export type ReconfigureRoomMutation$data = {
readonly hashCheckerConfig: {
readonly chatNotice: boolean;
readonly hashCheckMode: HashCheckerMode;
readonly subscribedLists: ReadonlyArray<string> | null;
readonly subscribedLists: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly id: string;
readonly name: string;
readonly tags: ReadonlyArray<string> | null;
};
}>;
};
};
};
};
@ -53,7 +61,21 @@ var v0 = [
"name": "reconfigureInput"
}
],
v1 = [
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v3 = [
{
"alias": null,
"args": [
@ -68,13 +90,7 @@ v1 = [
"name": "reconfigureRoom",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
(v1/*: any*/),
{
"alias": null,
"args": null,
@ -89,13 +105,7 @@ v1 = [
"name": "deactivated",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
(v2/*: any*/),
{
"alias": null,
"args": null,
@ -141,10 +151,51 @@ v1 = [
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
}
],
"concreteType": "ListConnection",
"kind": "LinkedField",
"name": "subscribedLists",
"storageKey": null
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "ListEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "List",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "tags",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "subscribedLists(first:100)"
}
],
"storageKey": null
@ -159,7 +210,7 @@ return {
"kind": "Fragment",
"metadata": null,
"name": "ReconfigureRoomMutation",
"selections": (v1/*: any*/),
"selections": (v3/*: any*/),
"type": "Mutation",
"abstractKey": null
},
@ -168,19 +219,19 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ReconfigureRoomMutation",
"selections": (v1/*: any*/)
"selections": (v3/*: any*/)
},
"params": {
"cacheID": "4186d7d18c6230e79d890ca0043dc104",
"cacheID": "0961d774133f607eb0b0145371e73f32",
"id": null,
"metadata": {},
"name": "ReconfigureRoomMutation",
"operationKind": "mutation",
"text": "mutation ReconfigureRoomMutation(\n $reconfigureInput: RoomConfigUpdate!\n) {\n reconfigureRoom(input: $reconfigureInput) {\n id\n active\n deactivated\n name\n roomId\n debug\n adminPowerLevel\n hashCheckerConfig {\n chatNotice\n hashCheckMode\n subscribedLists\n }\n }\n}\n"
"text": "mutation ReconfigureRoomMutation(\n $reconfigureInput: RoomConfigUpdate!\n) {\n reconfigureRoom(input: $reconfigureInput) {\n id\n active\n deactivated\n name\n roomId\n debug\n adminPowerLevel\n hashCheckerConfig {\n chatNotice\n hashCheckMode\n subscribedLists(first: 100) {\n edges {\n node {\n id\n name\n tags\n }\n }\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "e4ccf905922a56c92f44336d58c96a53";
(node as any).hash = "9041b8d9fb6c1da957feb5f33ec8ac1b";
export default node;