Group changes

This commit is contained in:
Bill 2022-02-24 13:27:20 -05:00
parent 263212f650
commit f9714c066f
10 changed files with 259 additions and 189 deletions

View File

@ -72,6 +72,9 @@ $room-info-width: 325px;
$nitro-group-creator-width: 383px; $nitro-group-creator-width: 383px;
$nitro-mod-tools-width: 175px; $nitro-mod-tools-width: 175px;
$nitro-group-manager-width: 375px;
$nitro-group-manager-height: 355px;
.nitro-app { .nitro-app {
width: 100%; width: 100%;
height: 100%; height: 100%;

View File

@ -1,8 +1,3 @@
.nitro-group-manager {
width: 420px;
max-height: 400px;
}
.nitro-group-tab-image { .nitro-group-tab-image {
width: 122px; width: 122px;
height: 68px; height: 68px;
@ -82,6 +77,55 @@
} }
} }
.group-badge-preview {
width: 42px;
height: 42px;
background-color: $grid-bg-color;
&.active {
border-color: $grid-active-border-color !important;
background-color: $grid-active-bg-color;
&:before {
position: absolute;
content: ' ';
width: 0;
height: 0;
border-top: 10px solid white;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
bottom: -10px;
}
}
}
.group-badge-color-swatch,
.group-badge-position-swatch {
position: relative;
border-radius: $border-radius;
width: 16px;
height: 16px;
background: $white;
border: 2px solid $white;
box-shadow: inset 3px 3px rgba(0, 0, 0, .1);
&.active {
box-shadow: none;
}
}
.group-badge-position-swatch {
box-shadow: inset 3px 3px rgba(0, 0, 0, .1);
&.active {
background: $primary;
}
}
.group-badge-color-swatch {
box-shadow: inset 2px 2px rgba(0, 0, 0, .2);
}
.group-color-swatch { .group-color-swatch {
width: 30px; width: 30px;
height: 40px; height: 40px;

View File

@ -0,0 +1,107 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Dispatch, FC, SetStateAction, useEffect, useRef, useState } from 'react';
import { Base, Column, Flex, Grid } from '../../../common';
import { BatchUpdates } from '../../../hooks';
import { BadgeImageView } from '../../../views/shared/badge-image/BadgeImageView';
import { GroupBadgePart } from '../common/GroupBadgePart';
import { useGroupsContext } from '../GroupsContext';
interface GroupBadgeCreatorViewProps
{
badgeParts: GroupBadgePart[];
setBadgeParts: Dispatch<SetStateAction<GroupBadgePart[]>>;
}
const POSITIONS: number[] = [ 0, 1, 2, 3, 4, 5, 6, 7, 8 ];
export const GroupBadgeCreatorView: FC<GroupBadgeCreatorViewProps> = props =>
{
const { badgeParts = [], setBadgeParts = null } = props;
const [ selectedIndex, setSelectedIndex ] = useState<number>(-1);
const [ copiedBadgeParts, setCopiedBadgeParts ] = useState<GroupBadgePart[]>(null);
const { groupsState = null } = useGroupsContext();
const { badgeBases = null, badgeSymbols = null, badgePartColors = null } = groupsState;
const willUnmount = useRef(false);
const setPartProperty = (partIndex: number, property: string, value: number) =>
{
const newBadgeParts = [ ...copiedBadgeParts ];
newBadgeParts[partIndex][property] = value;
BatchUpdates(() =>
{
setCopiedBadgeParts(newBadgeParts);
if(property === 'key') setSelectedIndex(-1);
});
}
useEffect(() =>
{
BatchUpdates(() =>
{
setCopiedBadgeParts(badgeParts);
setSelectedIndex(-1);
});
}, [ badgeParts ]);
useEffect(() =>
{
if(!copiedBadgeParts || (copiedBadgeParts === badgeParts)) return;
setBadgeParts(copiedBadgeParts);
}, [ copiedBadgeParts, badgeParts, setBadgeParts ]);
if(!copiedBadgeParts || !copiedBadgeParts.length) return null;
return (
<>
{ ((selectedIndex < 0) && copiedBadgeParts && (copiedBadgeParts.length > 0)) && copiedBadgeParts.map((part, index) =>
{
return (
<Flex key={ index } alignItems="center" justifyContent="between" gap={ 2 } className="bg-muted rounded px-2 py-1">
<Flex pointer center className="bg-muted rounded p-1" onClick={ event => setSelectedIndex(index) }>
{ (copiedBadgeParts[index].code && (copiedBadgeParts[index].code.length > 0)) &&
<BadgeImageView badgeCode={ copiedBadgeParts[index].code } isGroup={ true } /> }
{ (!copiedBadgeParts[index].code || !copiedBadgeParts[index].code.length) &&
<Flex center className="badge-image group-badge">
<FontAwesomeIcon icon="plus" />
</Flex> }
</Flex>
{ (part.type !== GroupBadgePart.BASE) &&
<Grid gap={ 1 } columnCount={ 3 }>
{ POSITIONS.map((position, posIndex) =>
{
return <Base key={ posIndex } pointer className={ `group-badge-position-swatch ${ (copiedBadgeParts[index].position === position) ? 'active' : '' }` } onClick={ event => setPartProperty(index, 'position', position) }></Base>
}) }
</Grid> }
<Grid gap={ 1 } columnCount={ 8 }>
{ (badgePartColors.length > 0) && badgePartColors.map((item, colorIndex) =>
{
return <Base key={ colorIndex } pointer className={ `group-badge-color-swatch ${ (copiedBadgeParts[index].color === (colorIndex + 1)) ? 'active' : '' }` } style={{ backgroundColor: '#' + item.color }} onClick={ event => setPartProperty(index, 'color', (colorIndex + 1)) }></Base>
}) }
</Grid>
</Flex>
);
}) }
{ (selectedIndex >= 0) &&
<Grid gap={ 1 } columnCount={ 5 }>
{ (copiedBadgeParts[selectedIndex].type === GroupBadgePart.SYMBOL) &&
<Column pointer center className="bg-muted rounded p-1" onClick={ event => setPartProperty(selectedIndex, 'key', 0) }>
<Flex center className="badge-image group-badge">
<FontAwesomeIcon icon="times" />
</Flex>
</Column> }
{ ((copiedBadgeParts[selectedIndex].type === GroupBadgePart.BASE) ? badgeBases : badgeSymbols).map((item, index) =>
{
return (
<Column key={ index } pointer center className="bg-muted rounded p-1" onClick={ event => setPartProperty(selectedIndex, 'key', item.id) }>
<BadgeImageView badgeCode={ GroupBadgePart.getCode(copiedBadgeParts[selectedIndex].type, item.id, copiedBadgeParts[selectedIndex].color, 4) } isGroup={ true } />
</Column>
);
}) }
</Grid> }
</>
);
}

View File

@ -108,8 +108,8 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
<> <>
<Grid overflow="hidden"> <Grid overflow="hidden">
<Column center size={ 3 } overflow="hidden"> <Column center size={ 3 } overflow="hidden">
<Flex alignItems="center" className="group-badge"> <Flex alignItems="center" overflow="hidden" className="group-badge">
<BadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } className="mx-auto d-block"/> <BadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } scale={ 2 } />
</Flex> </Flex>
<Column alignItems="center" gap={ 1 }> <Column alignItems="center" gap={ 1 }>
<Text small underline pointer onClick={ () => handleAction('members') }>{ LocalizeText('group.membercount', [ 'totalMembers' ], [ groupInformation.membersCount.toString() ]) }</Text> <Text small underline pointer onClick={ () => handleAction('members') }>{ LocalizeText('group.membercount', [ 'totalMembers' ], [ groupInformation.membersCount.toString() ]) }</Text>

View File

@ -1,3 +1,4 @@
.nitro-group-manager { .nitro-group-manager {
width: 385px; height: $nitro-group-manager-height;
width: $nitro-group-manager-width;
} }

View File

@ -1,4 +1,4 @@
import { GroupDeleteComposer, GroupSaveBadgeComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer } from '@nitrots/nitro-renderer'; import { GroupDeleteComposer, GroupSaveColorsComposer, GroupSaveInformationComposer, GroupSavePreferencesComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback, useState } from 'react'; import { FC, useCallback, useState } from 'react';
import { LocalizeText } from '../../../../api'; import { LocalizeText } from '../../../../api';
import { Base, Button, Column, Flex, Text } from '../../../../common'; import { Base, Button, Column, Flex, Text } from '../../../../common';
@ -44,7 +44,7 @@ export const GroupManagerView: FC<{}> = props =>
}); });
SendMessageHook(new GroupSaveInformationComposer(groupId, groupName, groupDescription)); SendMessageHook(new GroupSaveInformationComposer(groupId, groupName, groupDescription));
SendMessageHook(new GroupSaveBadgeComposer(groupId, badge)); //SendMessageHook(new GroupSaveBadgeComposer(groupId, badge));
SendMessageHook(new GroupSaveColorsComposer(groupId, groupColors[0], groupColors[1])); SendMessageHook(new GroupSaveColorsComposer(groupId, groupColors[0], groupColors[1]));
SendMessageHook(new GroupSavePreferencesComposer(groupId, groupState, groupCanMembersDecorate ? 0 : 1)); SendMessageHook(new GroupSavePreferencesComposer(groupId, groupState, groupCanMembersDecorate ? 0 : 1));

View File

@ -1,82 +1,7 @@
.nitro-groups { .nitro-groups {
.badge-preview {
position: relative;
width: 50px;
height: 50px;
background: $white;
border-radius: $border-radius;
&.active {
&:before {
position: absolute;
content: ' ';
width: 0;
height: 0;
border-top: 10px solid white;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
bottom: -10px;
}
}
}
.group-color-swatch { .group-color-swatch {
width: 30px; width: 30px;
height: 40px; height: 40px;
} }
} }
.nitro-groups-color-grid {
.color-swatch {
position: relative;
border-radius: $border-radius;
width: 16px;
height: 16px;
border: 2px solid $white;
box-shadow: inset 3px 3px rgba(0, 0, 0, .1);
&.active {
box-shadow: none;
}
}
}
.shared-tab-badge {
.position-swatch {
position: relative;
border-radius: $border-radius;
width: 16px;
height: 16px;
background: $white;
border: 2px solid $white;
box-shadow: inset 3px 3px rgba(0, 0, 0, .1);
&.active {
background: $primary;
box-shadow: none;
}
}
.selection-list {
height: 160px;
min-height: 160px;
max-height: 160px;
}
}
.shared-tab-colors {
.color-swatch {
position: relative;
border-radius: $border-radius;
width: 15px;
height: 15px;
border: 2px solid $white;
box-shadow: inset 2px 2px rgba(0, 0, 0, .2);
&.active {
box-shadow: none;
}
}
}

View File

@ -1,11 +1,12 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { GroupSaveBadgeComposer } from '@nitrots/nitro-renderer';
import classNames from 'classnames'; import { FC, useEffect, useState } from 'react';
import { FC, useCallback, useEffect, useState } from 'react';
import { Column, Flex, Grid } from '../../../../common'; import { Column, Flex, Grid } from '../../../../common';
import { SendMessageHook } from '../../../../hooks';
import { BadgeImageView } from '../../../../views/shared/badge-image/BadgeImageView'; import { BadgeImageView } from '../../../../views/shared/badge-image/BadgeImageView';
import { GroupBadgePart } from '../../common/GroupBadgePart'; import { GroupBadgePart } from '../../common/GroupBadgePart';
import { useGroupsContext } from '../../GroupsContext'; import { useGroupsContext } from '../../GroupsContext';
import { GroupsActions } from '../../reducers/GroupsReducer'; import { GroupsActions } from '../../reducers/GroupsReducer';
import { GroupBadgeCreatorView } from '../GroupBadgeCreatorView';
const POSITIONS: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8]; const POSITIONS: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8];
@ -17,57 +18,26 @@ interface GroupTabBadgeViewProps
export const GroupTabBadgeView: FC<GroupTabBadgeViewProps> = props => export const GroupTabBadgeView: FC<GroupTabBadgeViewProps> = props =>
{ {
const { skipDefault = null } = props; const { skipDefault = null } = props;
const [ editingIndex, setEditingIndex ] = useState<number>(0); const [ badgeParts, setBadgeParts ] = useState<GroupBadgePart[]>(null);
const [ isSelectingModel, setIsSelectingModel ] = useState<boolean>(false);
const { groupsState = null, dispatchGroupsState = null } = useGroupsContext(); const { groupsState = null, dispatchGroupsState = null } = useGroupsContext();
const { badgeBases = null, badgeSymbols = null, badgePartColors = null, groupBadgeParts = null } = groupsState; const { badgeBases = null, badgeSymbols = null, badgePartColors = null, groupId = -1, groupBadgeParts = null } = groupsState;
const switchIndex = useCallback((index: number) => const getModifiedBadgeCode = () =>
{ {
setIsSelectingModel(false); if(!badgeParts || !badgeParts.length) return '';
setEditingIndex(index);
}, []);
const selectPartProperty = useCallback((property: string, key: number) => let badgeCode = '';
{
const clonedBadgeParts = Array.from(groupBadgeParts);
clonedBadgeParts[editingIndex][property] = key; badgeParts.forEach(part => (part.code && (badgeCode += part.code)));
dispatchGroupsState({ return badgeCode;
type: GroupsActions.SET_GROUP_BADGE_PARTS, }
payload: {
badgeParts: clonedBadgeParts
}
});
if(property === 'key') setIsSelectingModel(false);
}, [ editingIndex, groupBadgeParts, dispatchGroupsState ]);
const getCompleteBadgeCode = useCallback(() =>
{
let code = '';
if(!groupBadgeParts) return code;
groupBadgeParts.forEach((badgePart) =>
{
if(badgePart.code) code = code + badgePart.code;
});
return code;
}, [ groupBadgeParts ]);
const getCurrentPart = useCallback((property: string) =>
{
return groupBadgeParts[editingIndex][property];
}, [ groupBadgeParts, editingIndex ]);
useEffect(() => useEffect(() =>
{ {
if(skipDefault || !badgeBases || !badgePartColors || groupBadgeParts) return; if(groupBadgeParts && groupBadgeParts.length) return;
const badgeParts: GroupBadgePart[] = [ const badgeParts = [
new GroupBadgePart(GroupBadgePart.BASE, badgeBases[0].id, badgePartColors[0].id), new GroupBadgePart(GroupBadgePart.BASE, badgeBases[0].id, badgePartColors[0].id),
new GroupBadgePart(GroupBadgePart.SYMBOL, 0, badgePartColors[0].id), new GroupBadgePart(GroupBadgePart.SYMBOL, 0, badgePartColors[0].id),
new GroupBadgePart(GroupBadgePart.SYMBOL, 0, badgePartColors[0].id), new GroupBadgePart(GroupBadgePart.SYMBOL, 0, badgePartColors[0].id),
@ -79,45 +49,63 @@ export const GroupTabBadgeView: FC<GroupTabBadgeViewProps> = props =>
type: GroupsActions.SET_GROUP_BADGE_PARTS, type: GroupsActions.SET_GROUP_BADGE_PARTS,
payload: { badgeParts } payload: { badgeParts }
}); });
}, [ groupBadgeParts, badgeBases, badgePartColors, dispatchGroupsState ]);
}, [ skipDefault, badgeBases, badgePartColors, groupBadgeParts, dispatchGroupsState ]); useEffect(() =>
{
setBadgeParts(groupBadgeParts);
}, [ groupBadgeParts ]);
useEffect(() =>
{
if((groupId <= 0) || !badgeParts || !badgeParts.length || !groupBadgeParts || !groupBadgeParts.length || (badgeParts === groupBadgeParts)) return;
const badge = [];
badgeParts.forEach((part) =>
{
if(!part.code) return;
badge.push(part.key);
badge.push(part.color);
badge.push(part.position);
});
console.log('send')
SendMessageHook(new GroupSaveBadgeComposer(groupId, badge));
}, [ groupId, badgeParts, groupBadgeParts ]);
// useEffect(() =>
// {
// if((groupId <= 0) || !badgeParts || !badgeParts.length || (badgeParts === groupBadgeParts)) return;
// const badge = [];
// badgeParts.forEach((part) =>
// {
// if(!part.code) return;
// badge.push(part.key);
// badge.push(part.color);
// badge.push(part.position);
// });
// console.log('send')
// SendMessageHook(new GroupSaveBadgeComposer(groupId, badge));
// }, [ groupId, groupBadgeParts, badgeParts ]);
return ( return (
<div className="shared-tab-badge"> <Grid overflow="hidden" gap={ 1 }>
<Flex gap={ 2 }> <Column size={ 2 }>
<Flex className="group-badge"> <Flex center className="bg-muted rounded p-1">
<BadgeImageView badgeCode={ getCompleteBadgeCode() } isGroup={ true } className="mx-auto d-block"/> <BadgeImageView badgeCode={ getModifiedBadgeCode() } isGroup={ true } />
</Flex> </Flex>
<Column> </Column>
{ (groupBadgeParts.length > 0) && groupBadgeParts.map((part, index) => <Column size={ 10 } overflow="auto">
{ <GroupBadgeCreatorView badgeParts={ badgeParts } setBadgeParts={ setBadgeParts } />
if(part.type === GroupBadgePart.BASE) return null; </Column>
</Grid>
return (
<Flex gap={ 2 }>
<Flex center shrink pointer className="badge-preview" onClick={ event => setIsSelectingModel(true) }>
{ (groupBadgeParts[index]['code'] && (groupBadgeParts[index]['code'].length > 0)) &&
<BadgeImageView badgeCode={ groupBadgeParts[index]['code'] } isGroup={ true } /> }
{ (!groupBadgeParts[index]['code'] || !groupBadgeParts[index]['code'].length) &&
<FontAwesomeIcon icon="plus" /> }
</Flex>
<Grid gap={ 1 } columnCount={ 3 }>
{ POSITIONS.map((position) =>
{
return <div key={ position } className={ 'position-swatch cursor-pointer' + classNames({ ' active': groupBadgeParts[index]['position'] === position }) } onClick={ () => selectPartProperty('position', position) }></div>
}) }
</Grid>
<Grid gap={ 1 } columnCount={ 8 }>
{ (badgePartColors.length > 0) && badgePartColors.map((item, colorIndex) =>
{
return <div key={ colorIndex } className={ 'color-swatch cursor-pointer' + classNames({ ' active': item.id === groupBadgeParts[index]['color'] }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectPartProperty('color', item.id) }></div>
}) }
</Grid>
</Flex>
);
}) }
</Column>
</Flex>
</div>
); );
}; };

View File

@ -60,19 +60,19 @@ export const GroupTabColorsView: FC<{}> = props =>
</Column> </Column>
<Column size={ 5 } gap={ 1 } overflow="hidden"> <Column size={ 5 } gap={ 1 } overflow="hidden">
<Text bold>{ LocalizeText('group.edit.color.primary.color') }</Text> <Text bold>{ LocalizeText('group.edit.color.primary.color') }</Text>
<AutoGrid gap={ 1 } columnCount={ 7 } columnMinWidth={ 16 } columnMinHeight={ 16 } className="nitro-groups-color-grid"> <AutoGrid gap={ 1 } columnCount={ 7 } columnMinWidth={ 16 } columnMinHeight={ 16 }>
{ groupColors && groupColorsA && groupColorsA.map((item, index) => { groupColors && groupColorsA && groupColorsA.map((item, index) =>
{ {
return <div key={ index } className={ 'color-swatch cursor-pointer' + classNames({ ' active': (groupColors[0] === item.id) }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectColor(0, item.id) }></div> return <div key={ index } className={ 'group-badge-color-swatch cursor-pointer' + classNames({ ' active': (groupColors[0] === item.id) }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectColor(0, item.id) }></div>
}) } }) }
</AutoGrid> </AutoGrid>
</Column> </Column>
<Column size={ 5 } gap={ 1 } overflow="hidden"> <Column size={ 5 } gap={ 1 } overflow="hidden">
<Text bold>{ LocalizeText('group.edit.color.secondary.color') }</Text> <Text bold>{ LocalizeText('group.edit.color.secondary.color') }</Text>
<AutoGrid gap={ 1 } columnCount={ 7 } columnMinWidth={ 16 } columnMinHeight={ 16 } className="nitro-groups-color-grid"> <AutoGrid gap={ 1 } columnCount={ 7 } columnMinWidth={ 16 } columnMinHeight={ 16 }>
{ groupColorsB && groupColorsB.map((item, index) => { groupColorsB && groupColorsB.map((item, index) =>
{ {
return <div key={ index } className={ 'color-swatch cursor-pointer' + classNames({ ' active': (groupColors[1] === item.id) }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectColor(1, item.id) }></div> return <div key={ index } className={ 'group-badge-color-swatch cursor-pointer' + classNames({ ' active': (groupColors[1] === item.id) }) } style={{ backgroundColor: '#' + item.color }} onClick={ () => selectColor(1, item.id) }></div>
}) } }) }
</AutoGrid> </AutoGrid>
</Column> </Column>

View File

@ -1,6 +1,6 @@
import { FC } from 'react'; import { FC } from 'react';
import { LocalizeText } from '../../../../api/utils/LocalizeText'; import { LocalizeText } from '../../../../api/utils/LocalizeText';
import { Column, Flex, Text } from '../../../../common'; import { Column, Flex, HorizontalRule, Text } from '../../../../common';
import { useGroupsContext } from '../../GroupsContext'; import { useGroupsContext } from '../../GroupsContext';
import { GroupsActions } from '../../reducers/GroupsReducer'; import { GroupsActions } from '../../reducers/GroupsReducer';
@ -32,23 +32,25 @@ export const GroupTabSettingsView: FC<{}> = props =>
} }
return ( return (
<> <Column overflow="auto">
{ STATES.map((state, index) => <Column>
{ { STATES.map((state, index) =>
return ( {
<Flex key={ index } alignItems="center" gap={ 1 }> return (
<input className="form-check-input" type="radio" name="groupState" checked={ (groupState === index) } onChange={ event => setState(index) } /> <Flex key={ index } alignItems="center" gap={ 1 }>
<Column gap={ 0 }> <input className="form-check-input" type="radio" name="groupState" checked={ (groupState === index) } onChange={ event => setState(index) } />
<Flex gap={ 1 }> <Column gap={ 0 }>
<i className={ `icon icon-group-type-${index}` } /> <Flex gap={ 1 }>
<Text bold>{ LocalizeText(`group.edit.settings.type.${state}.label`) }</Text> <i className={ `icon icon-group-type-${index}` } />
</Flex> <Text bold>{ LocalizeText(`group.edit.settings.type.${state}.label`) }</Text>
<Text>{ LocalizeText(`group.edit.settings.type.${state}.help`) }</Text> </Flex>
</Column> <Text>{ LocalizeText(`group.edit.settings.type.${state}.help`) }</Text>
</Flex> </Column>
); </Flex>
}) } );
<hr className="m-0 bg-dark" /> }) }
</Column>
<HorizontalRule />
<Flex alignItems="center" gap={ 1 }> <Flex alignItems="center" gap={ 1 }>
<input className="form-check-input" type="checkbox" checked={ groupCanMembersDecorate } onChange={() => toggleCanMembersDecorate() } /> <input className="form-check-input" type="checkbox" checked={ groupCanMembersDecorate } onChange={() => toggleCanMembersDecorate() } />
<Column gap={ 1 }> <Column gap={ 1 }>
@ -56,6 +58,6 @@ export const GroupTabSettingsView: FC<{}> = props =>
<Text>{ LocalizeText('group.edit.settings.rights.members.help') }</Text> <Text>{ LocalizeText('group.edit.settings.rights.members.help') }</Text>
</Column> </Column>
</Flex> </Flex>
</> </Column>
); );
}; };