Skip to content

Commit

Permalink
Feature/floating action buttons (#326)
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiaslehnertum authored Nov 27, 2023
1 parent b524d20 commit 2128887
Show file tree
Hide file tree
Showing 20 changed files with 281 additions and 139 deletions.
48 changes: 48 additions & 0 deletions src/main/components/uml-element/updatable/FloatingButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { styled } from '../../theme/styles';

const FloatingButtonContainer = styled.g.attrs((props) => ({
...props,
}))`
transition: all 180ms ease-in-out;
pointer-events: all;
path {
pointer-events: all;
fill: var(--apollon-primary-contrast);
}
rect {
pointer-events: all;
fill: var(--apollon-background);
stroke: var(--apollon-gray);
}
:hover {
transform: translate(0px, -30px);
}
:active {
transform: translate(0px, -30px);
}
:hover rect {
fill: var(--apollon-gray);
stroke: var(--apollon-gray-variant);
}
:active rect {
fill: var(--apollon-gray);
stroke: var(--apollon-gray-variant);
}
`;

export interface FloatingButtonProps {
style?: React.CSSProperties | undefined;
children?: React.ReactNode;
onClick?: () => void;
}

export const FloatingButton: React.FC<FloatingButtonProps> = ({ children, ...props }) => {
return (
<FloatingButtonContainer {...props}>
<rect height={30} width={30} rx="0.25rem" ry="0.25rem" />
{children}
</FloatingButtonContainer>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { FunctionComponent } from 'react';

export const DeleteIcon: FunctionComponent<React.SVGProps<SVGSVGElement>> = (props) => (
<svg {...props} height={20} width={20}>
<path d="M4.66666 14C4.29999 14 3.9861 13.8694 3.72499 13.6083C3.46388 13.3472 3.33332 13.0333 3.33332 12.6667V4H2.66666V2.66667H5.99999V2H9.99999V2.66667H13.3333V4H12.6667V12.6667C12.6667 13.0333 12.5361 13.3472 12.275 13.6083C12.0139 13.8694 11.7 14 11.3333 14H4.66666ZM11.3333 4H4.66666V12.6667H11.3333V4ZM5.99999 11.3333H7.33332V5.33333H5.99999V11.3333ZM8.66666 11.3333H9.99999V5.33333H8.66666V11.3333Z" />
</svg>
);
7 changes: 7 additions & 0 deletions src/main/components/uml-element/updatable/icons/EditIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React, { FunctionComponent } from 'react';

export const EditIcon: FunctionComponent<React.SVGProps<SVGSVGElement>> = (props) => (
<svg {...props} height={20} width={20}>
<path d="M2.39999 13.6H3.53999L11.36 5.77999L10.22 4.63999L2.39999 12.46V13.6ZM0.799988 15.2V11.8L11.36 1.25999C11.52 1.11332 11.6967 0.999988 11.89 0.919988C12.0833 0.839988 12.2867 0.799988 12.5 0.799988C12.7133 0.799988 12.92 0.839988 13.12 0.919988C13.32 0.999988 13.4933 1.11999 13.64 1.27999L14.74 2.39999C14.9 2.54665 15.0167 2.71999 15.09 2.91999C15.1633 3.11999 15.2 3.31999 15.2 3.51999C15.2 3.73332 15.1633 3.93665 15.09 4.12999C15.0167 4.32332 14.9 4.49999 14.74 4.65999L4.19999 15.2H0.799988ZM10.78 5.21999L10.22 4.63999L11.36 5.77999L10.78 5.21999Z" />
</svg>
);
100 changes: 90 additions & 10 deletions src/main/components/uml-element/updatable/updatable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,47 +5,127 @@ import { UMLElementRepository } from '../../../services/uml-element/uml-element-
import { AsyncDispatch } from '../../../utils/actions/actions';
import { ModelState } from '../../store/model-state';
import { UMLElementComponentProps } from '../uml-element-component-props';
import { UMLElement } from '../../../services/uml-element/uml-element';
import { UMLRelationship } from '../../../services/uml-relationship/uml-relationship';
import { FloatingButton } from './FloatingButton';
import { EditIcon } from './icons/EditIcon';
import { DeleteIcon } from './icons/DeleteIcon';

const initialState = {};
const FAB_TIMEOUT = 500;

type StateProps = {};
const initialState = {
showActionButtons: false,
};

type StateProps = {
hovered: boolean;
selected: boolean;
};

type DispatchProps = {
updateStart: AsyncDispatch<typeof UMLElementRepository.updateStart>;
delete: AsyncDispatch<typeof UMLElementRepository.delete>;
getById: (id: string) => UMLElement | null;
};

type Props = UMLElementComponentProps & StateProps & DispatchProps;

type State = typeof initialState;

const enhance = connect<StateProps, DispatchProps, UMLElementComponentProps, ModelState>(null, {
updateStart: UMLElementRepository.updateStart,
});
const enhance = connect<StateProps, DispatchProps, UMLElementComponentProps, ModelState>(
(state, props) => ({
hovered: state.hovered[0] === props.id,
selected: state.selected.includes(props.id),
}),
{
updateStart: UMLElementRepository.updateStart,
delete: UMLElementRepository.delete,
getById: UMLElementRepository.getById as any as AsyncDispatch<typeof UMLElementRepository.getById>,
},
);

export const updatable = (
WrappedComponent: ComponentType<UMLElementComponentProps>,
): ConnectedComponent<ComponentType<Props>, UMLElementComponentProps> => {
class Updatable extends Component<Props, State> {
state = initialState;

timer: ReturnType<typeof setTimeout> | null = null;

componentDidMount() {
const node = findDOMNode(this) as HTMLElement;
node.addEventListener('dblclick', this.onDoubleClick);
node.addEventListener('dblclick', this.onStartUpdate);
}

componentWillUnmount() {
const node = findDOMNode(this) as HTMLElement;
node.removeEventListener('dblclick', this.onDoubleClick);
node.removeEventListener('dblclick', this.onStartUpdate);
}

render() {
const { updateStart, ...props } = this.props;
return <WrappedComponent {...props} />;
const { updateStart, getById, hovered, selected, ...props } = this.props;

const element = getById(props.id);

// We wait a few milliseconds before hiding the float action buttons
// to prevent the actions from being hidden un
if (!this.state.showActionButtons && (hovered || selected)) {
this.setState({ ...this.state, showActionButtons: true });

if (this.timer) {
clearTimeout(this.timer);
}
}

if (this.state.showActionButtons && !(hovered || selected)) {
this.timer = setTimeout(() => {
this.setState({ ...this.state, showActionButtons: false });
}, FAB_TIMEOUT);
}

const shouldRenderFABs = element && !UMLRelationship.isUMLRelationship(element);

return (
<WrappedComponent {...props}>
{shouldRenderFABs && (
<FloatingButton
style={{
opacity: this.state.showActionButtons ? 1 : 0,
transform: `translate(${element.bounds.width}px, ${this.state.showActionButtons ? -40 : -30}px)`,
}}
onClick={this.onStartUpdate}
>
<EditIcon x={7} y={7} />
</FloatingButton>
)}
{shouldRenderFABs && (
<FloatingButton
style={{
opacity: this.state.showActionButtons ? 1 : 0,
transform: `translate(${element.bounds.width}px, ${this.state.showActionButtons ? -80 : -30}px)`,
}}
onClick={this.onDelete}
>
<DeleteIcon x={7} y={7} />
</FloatingButton>
)}
</WrappedComponent>
);
}

private onDoubleClick = () => {
/**
* Show the update dialog of the wrapped element
*/
private onStartUpdate = () => {
this.props.updateStart(this.props.id);
};

/**
* Show the delete dialog of the wrapped element
*/
private onDelete = () => {
this.props.delete(this.props.id);
};
}

return enhance(Updatable);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ exports[`test default element popup render 1`] = `
<div>
<section>
<div
class="sc-lcIPJg hPdAaT"
class="sc-kdBSHD gzfaPq"
>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO kIvhJv"
class="sc-dtBdUo sc-kOHTFB nbRZj bHpNZf"
maxLength="100"
/>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ exports[`test default relationship popup render 1`] = `
<div>
<section>
<div
class="sc-tagGq kaljVw"
class="sc-esYiGF jnEhCX"
>
<h1
class="sc-kdBSHD bunwHc"
class="sc-tagGq gknvxe"
>
Relationship
</h1>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ exports[`test class association popup render 1`] = `
<div>
<section>
<div
class="sc-JrDLc cQSxzb"
class="sc-fjvvzt cQBzZO"
>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO kIvhJv"
class="sc-dtBdUo sc-kOHTFB nbRZj bHpNZf"
maxlength="100"
value=""
/>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand All @@ -32,52 +32,52 @@ exports[`test class association popup render 1`] = `
</button>
</div>
<hr
class="sc-esYiGF iLojZW"
class="sc-fXSgeo kpgPkJ"
/>
</section>
<section>
<div
class="sc-bXCLTC leCMrB"
class="sc-jsJBEP jEuSgr"
>
<button
class="sc-fHjqPf sc-hmdomO egzafX dFvOaw sc-jsJBEP gDTmhe"
class="sc-hmdomO sc-bXCLTC czBNuA gCyQAW sc-eeDRCY eGoSrK"
color="primary"
>
Abstract
</button>
<button
class="sc-fHjqPf sc-hmdomO egzafX dFvOaw sc-jsJBEP gDTmhe"
class="sc-hmdomO sc-bXCLTC czBNuA gCyQAW sc-eeDRCY eGoSrK"
color="primary"
>
Interface
</button>
<button
class="sc-fHjqPf sc-hmdomO egzafX dFvOaw sc-jsJBEP gDTmhe"
class="sc-hmdomO sc-bXCLTC czBNuA gCyQAW sc-eeDRCY eGoSrK"
color="primary"
>
Enumeration
</button>
</div>
<hr
class="sc-esYiGF iLojZW"
class="sc-fXSgeo kpgPkJ"
/>
</section>
<section>
<h1
class="sc-kdBSHD bunwHc"
class="sc-tagGq gknvxe"
>
Attributes
</h1>
<div
class="sc-fXSgeo hRUsyw"
class="sc-JrDLc cQSxzb"
>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO cpgdNd"
class="sc-dtBdUo sc-kOHTFB nbRZj dERxbl"
maxlength="100"
value="attribute"
/>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand All @@ -95,30 +95,30 @@ exports[`test class association popup render 1`] = `
</button>
</div>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO jRogEI"
class="sc-dtBdUo sc-kOHTFB nbRZj fzqSng"
maxlength="100"
value=""
/>
</section>
<section>
<hr
class="sc-esYiGF iLojZW"
class="sc-fXSgeo kpgPkJ"
/>
<h1
class="sc-kdBSHD bunwHc"
class="sc-tagGq gknvxe"
>
Methods
</h1>
<div
class="sc-fXSgeo hRUsyw"
class="sc-JrDLc cQSxzb"
>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO cpgdNd"
class="sc-dtBdUo sc-kOHTFB nbRZj dERxbl"
maxlength="100"
value="classMethod"
/>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand All @@ -136,7 +136,7 @@ exports[`test class association popup render 1`] = `
</button>
</div>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO jRogEI"
class="sc-dtBdUo sc-kOHTFB nbRZj fzqSng"
maxlength="100"
value=""
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ exports[`test flowchart decision update render 1`] = `
<div>
<section>
<div
class="sc-dZoequ jKvDgE"
class="sc-eZkCL cXjuPz"
>
<input
class="sc-iHGNWf sc-dtBdUo jSLZkO kIvhJv"
class="sc-dtBdUo sc-kOHTFB nbRZj bHpNZf"
maxlength="100"
placeholder="Decision"
value=""
/>
<button
class="sc-fHjqPf sc-hmdomO egzafX OnZRE"
class="sc-hmdomO sc-bXCLTC czBNuA iklrvW"
color="link"
tabindex="-1"
>
Expand Down
Loading

0 comments on commit 2128887

Please sign in to comment.