Skip to content

Commit

Permalink
Merge branch 'develop' into feature/bpmn-diagrams
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiaslehnertum committed Oct 10, 2023
2 parents e7f187a + 39f3a4e commit b74d1b1
Show file tree
Hide file tree
Showing 19 changed files with 682 additions and 431 deletions.
30 changes: 15 additions & 15 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ls1intum/apollon",
"version": "2.14.5",
"version": "2.15.0",
"description": "A UML diagram editor.",
"keywords": [],
"homepage": "https://github.com/ls1intum/apollon#readme",
Expand Down Expand Up @@ -63,7 +63,7 @@
"redux-thunk": "2.4.2",
"styled-components": "5.3.11",
"tslib": "2.6.2",
"uuid": "9.0.0"
"uuid": "9.0.1"
},
"resolutions": {
"semver": "7.5.4",
Expand All @@ -72,36 +72,36 @@
},
"devDependencies": {
"@stylelint/postcss-css-in-js": "0.38.0",
"@testing-library/jest-dom": "6.1.2",
"@testing-library/jest-dom": "6.1.3",
"@testing-library/react": "14.0.0",
"@types/jest": "29.5.4",
"@types/jest": "29.5.5",
"@types/react": "18.0.27",
"@types/react-color": "3.0.6",
"@types/react-color": "3.0.7",
"@types/react-dom": "18.0.10",
"@types/react-redux": "7.1.26",
"@types/redux-mock-store": "1.0.3",
"@types/styled-components": "5.1.26",
"@types/uuid": "9.0.3",
"@types/react-redux": "7.1.27",
"@types/redux-mock-store": "1.0.4",
"@types/styled-components": "5.1.28",
"@types/uuid": "9.0.4",
"circular-dependency-plugin": "5.2.2",
"copy-webpack-plugin": "11.0.0",
"css-loader": "6.8.1",
"cypress": "13.1.0",
"cypress-real-events": "1.10.1",
"cypress": "13.3.0",
"cypress-real-events": "1.10.3",
"fork-ts-checker-notifier-webpack-plugin": "7.0.0",
"fork-ts-checker-webpack-plugin": "8.0.0",
"html-webpack-plugin": "5.5.3",
"husky": "8.0.3",
"jest": "29.6.4",
"jest-environment-jsdom": "29.6.4",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"jest-html-reporter": "3.10.2",
"jest-styled-components": "7.1.1",
"lint-staged": "14.0.1",
"pinst": "3.0.0",
"postcss": "8.4.29",
"postcss": "8.4.31",
"postcss-syntax": "0.36.2",
"prettier": "3.0.3",
"redux-mock-store": "1.5.4",
"start-server-and-test": "2.0.0",
"start-server-and-test": "2.0.1",
"style-loader": "3.3.3",
"stylelint": "15.10.3",
"stylelint-config-recommended": "13.0.0",
Expand Down
36 changes: 24 additions & 12 deletions public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,7 @@ aside.sidebar section>div {
justify-content: space-between;
}

.switch button {
width: 100%;
button {
background: var(--apollon-background);
color: var(--apollon-primary-contrast);
border: 1px solid var(--apollon-gray);
Expand All @@ -156,6 +155,29 @@ aside.sidebar section>div {
outline: none;
}

button:hover {
background-color: var(--apollon-gray);
border-color: var(--apollon-gray-variant);
}

button:active {
background-color: var(--apollon-gray);
border-color: var(--apollon-gray-variant);
}

.button-rounded {
border-radius: 0.25rem;
width: 2.25em;
height: 2.25em;
display: flex;
align-items: center;
justify-content: center;
}

.switch button {
width: 100%;
}

.switch button:first-child {
border-top-left-radius: 0.25rem;
border-bottom-left-radius: 0.25rem;
Expand All @@ -167,16 +189,6 @@ aside.sidebar section>div {
border-bottom-right-radius: 0.25rem;
}

.switch button:hover {
background-color: var(--apollon-gray);
border-color: var(--apollon-gray-variant);
}

.switch button:active {
background-color: var(--apollon-gray);
border-color: var(--apollon-gray-variant);
}

.switch button.selected {
background-color: var(--apollon-gray);
border-color: var(--apollon-gray-variant);
Expand Down
2 changes: 1 addition & 1 deletion src/main/components/canvas/canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class CanvasComponent extends Component<Props> implements Omit<ILayer, 'l
ref={this.layer}
data-cy="modeling-editor-canvas"
>
<g style={{ transform: translateCoordinate() }}>
<g style={{ transformOrigin: 'top left', transform: `${translateCoordinate()}` }}>
{this.layer.current && (
<svg x="50%" y="50%">
{/* be careful to change the drawing order -> if relationships are drawn first -> relationships will not be visible in containers */}
Expand Down
98 changes: 81 additions & 17 deletions src/main/components/canvas/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,30 @@ import { ModelState } from '../store/model-state';
import isMobile from 'is-mobile';
import { UMLElementRepository } from '../../services/uml-element/uml-element-repository';
import { AsyncDispatch } from '../../utils/actions/actions';
import { EditorRepository } from '../../services/editor/editor-repository';
import { clamp } from '../../utils/clamp';
import { ZoomPane } from './zoom-pane';

const grid = 10;
const subdivisions = 5;
const borderWidth = 1;
const minScale: number = 0.5;
const maxScale: number = 5.0;

const StyledEditor = styled.div`
const grid: number = 10;
const subdivisions: number = 5;
const borderWidth: number = 1;

const StyledEditor = styled.div<{ scale: number }>`
display: block;
width: 100%;
overflow: auto;
position: relative;
min-height: inherit;
max-height: inherit;
max-width: inherit;
overflow: auto;
width: ${(props) => clamp(100 / props.scale, 100, 100 / minScale)}%;
height: ${(props) => clamp(100 / props.scale, 100, 100 / minScale)}%;
-ms-overflow-style: -ms-autohiding-scrollbar;
border: ${borderWidth}px solid ${(props) => props.theme.color.gray};
background-position: calc(50% + ${(grid * subdivisions - borderWidth) / 2}px)
calc(50% + ${(grid * subdivisions - borderWidth) / 2}px);
background-size:
Expand All @@ -35,22 +42,33 @@ const StyledEditor = styled.div`
linear-gradient(to bottom, ${(props) => props.theme.color.gray} 1px, transparent 1px);
background-repeat: repeat;
background-attachment: local;
transition:
transform 500ms,
width 500ms,
height 500ms;
transform-origin: top left;
transform: scale(${(props) => props.scale ?? 1});
`;

type OwnProps = { children: ReactNode };

type StateProps = { moving: string[]; connecting: boolean; reconnecting: boolean };
type StateProps = { moving: string[]; connecting: boolean; reconnecting: boolean; scale: number };

type DispatchProps = { move: AsyncDispatch<typeof UMLElementRepository.move> };
type DispatchProps = {
move: AsyncDispatch<typeof UMLElementRepository.move>;
changeZoomFactor: typeof EditorRepository.changeZoomFactor;
};

const enhance = connect<StateProps, DispatchProps, OwnProps, ModelState>(
(state) => ({
moving: [...state.moving],
connecting: state.connecting.length > 0,
reconnecting: Object.keys(state.reconnecting).length > 0,
scale: state.editor.zoomFactor,
}),
{
move: UMLElementRepository.move,
changeZoomFactor: EditorRepository.changeZoomFactor,
},
);

Expand All @@ -59,6 +77,7 @@ type Props = OwnProps & StateProps & DispatchProps;
const getInitialState = () => {
return {
scrollingDisabled: false,
gestureStartZoomFactor: 1.0 as number,
isMobile: isMobile({ tablet: true }),
};
};
Expand All @@ -71,6 +90,19 @@ const SCROLL_DISTANCE = 5;
class EditorComponent extends Component<Props, State> {
state = getInitialState();
editor = createRef<HTMLDivElement>();
zoomContainer = createRef<HTMLDivElement>();

componentDidMount() {
window.addEventListener(
'wheel',
(event) => {
if (event.ctrlKey) {
event.preventDefault();
}
},
{ passive: false },
);
}

componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any) {
if (this.state.isMobile) {
Expand All @@ -88,31 +120,62 @@ class EditorComponent extends Component<Props, State> {
}

render() {
const { moving, connecting, reconnecting, ...props } = this.props;
const { moving, connecting, reconnecting, scale = 1.0, ...props } = this.props;

if (this.state.isMobile) {
return <StyledEditor ref={this.editor} {...props} onTouchMove={this.customScrolling} />;
return (
<div
ref={this.zoomContainer}
style={{ height: '100%', width: '100%', overflow: scale > 1.0 ? 'auto' : 'hidden' }}
>
<StyledEditor ref={this.editor} {...props} onTouchMove={this.customScrolling} scale={scale} />
<ZoomPane
value={scale}
onChange={(zoomFactor) => this.props.changeZoomFactor(zoomFactor)}
min={minScale}
max={maxScale}
step={0.2}
/>
</div>
);
} else {
return <StyledEditor {...props} />;
return (
<div
ref={this.zoomContainer}
style={{ height: '100%', width: '100%', overflow: scale > 1.0 ? 'auto' : 'hidden' }}
>
<StyledEditor ref={this.editor} {...props} scale={scale} />
<ZoomPane
value={scale}
onChange={(zoomFactor) => this.props.changeZoomFactor(zoomFactor)}
min={minScale}
max={maxScale}
step={0.2}
/>
</div>
);
}
}

customScrolling = (event: React.TouchEvent) => {
const { scale = 1 } = this.props;

if (this.editor.current) {
const clientRect = this.editor.current.getBoundingClientRect();

const touch = event.touches[event.touches.length - 1];

// scroll when on the edge of the element
const scrollHorizontally =
touch.clientX < clientRect.x + SCROLL_BORDER
touch.clientX * scale < clientRect.x + SCROLL_BORDER
? -SCROLL_DISTANCE
: touch.clientX > clientRect.x + clientRect.width - SCROLL_BORDER
: touch.clientX * scale > clientRect.x + clientRect.width - SCROLL_BORDER
? SCROLL_DISTANCE
: 0;
const scrollVertically =
touch.clientY < clientRect.y + SCROLL_BORDER
touch.clientY * scale < clientRect.y + SCROLL_BORDER
? -SCROLL_DISTANCE
: touch.clientY > clientRect.y + clientRect.height - SCROLL_BORDER
: touch.clientY * scale > clientRect.y + clientRect.height - SCROLL_BORDER
? SCROLL_DISTANCE
: 0;
this.editor.current.scrollBy(scrollHorizontally, scrollVertically);
Expand All @@ -139,6 +202,7 @@ class EditorComponent extends Component<Props, State> {
if (target) {
// disables default scrolling in editor
(target as HTMLElement).style.overflow = 'hidden';

// disables pull to refresh
document.body.style.overflowY = 'hidden';
(target as HTMLElement).style.overscrollBehavior = 'none';
Expand Down
31 changes: 31 additions & 0 deletions src/main/components/canvas/zoom-pane.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React, { FunctionComponent } from 'react';
import { clamp } from '../../utils/clamp';

type Props = {
min?: number;
max?: number;
step?: number;
value: number;
onChange: (value: number) => void;
};

export const ZoomPaneComponent: FunctionComponent<Props> = (props) => {
const { min = 0.5, max = 5, step = 0.5, value, onChange } = props;

return (
<div style={{ position: 'absolute', left: '0.75em', bottom: '0.75em', display: 'flex' }}>
<button
style={{ marginRight: '0.5em' }}
className="button-rounded"
onClick={() => onChange(clamp(value + step, min, max))}
>
+
</button>
<button className="button-rounded" onClick={() => onChange(clamp(value - step, min, max))}>
-
</button>
</div>
);
};

export const ZoomPane = ZoomPaneComponent;
13 changes: 11 additions & 2 deletions src/main/components/connectable/connection-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type OwnProps = {};

type StateProps = {
connecting: IUMLElementPort[];
zoomFactor: number;
};

type DispatchProps = {
Expand All @@ -36,6 +37,7 @@ const enhance = compose<ComponentType<OwnProps>>(
(id) => (state.elements[id] as IUMLRelationship)[state.reconnecting[id]],
),
],
zoomFactor: state.editor.zoomFactor,
}),
{
endConnecting: UMLElementRepository.endConnecting,
Expand Down Expand Up @@ -86,13 +88,20 @@ class Preview extends Component<Props, State> {
}

onPointerMove = (event: PointerEvent | TouchEvent) => {
const { zoomFactor = 1 } = this.props;

const offset = this.props.canvas.origin();

let position: Point;
if (event instanceof PointerEvent) {
position = new Point(event.clientX - offset.x, event.clientY - offset.y);
position = new Point(event.clientX - offset.x, event.clientY - offset.y).scale(1 / zoomFactor);
} else {
position = new Point(event.targetTouches[0].clientX - offset.x, event.targetTouches[0].clientY - offset.y);
position = new Point(
event.targetTouches[0].clientX - offset.x,
event.targetTouches[0].clientY - offset.y,
).scale(1 / zoomFactor);
}

this.setState({ position });
};

Expand Down
Loading

0 comments on commit b74d1b1

Please sign in to comment.