Merge branch 'master' into production

This commit is contained in:
HF 2020-11-06 17:06:08 +01:00
commit 09790a9c25
3 changed files with 306 additions and 140 deletions

View File

@ -4,7 +4,6 @@
* @flow
*/
import Hammer from 'hammerjs';
import keycode from 'keycode';
import {
@ -28,100 +27,108 @@ import {
getOffsetOfPixel,
} from '../core/utils';
let store = null;
class PixelPlainterControls {
constructor(renderer, viewport: HTMLCanvasElement, curStore) {
this.store = curStore;
this.renderer = renderer;
this.viewport = viewport;
function onKeyPress(event: KeyboardEvent) {
// ignore key presses if modal is open or chat is used
if (event.target.nodeName === 'INPUT'
|| event.target.nodeName === 'TEXTAREA'
) {
return;
this.onMouseDown = this.onMouseDown.bind(this);
this.onKeyPress = this.onKeyPress.bind(this);
this.onAuxClick = this.onAuxClick.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onWheel = this.onWheel.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
this.onTouchMove = this.onTouchMove.bind(this);
this.clickTabStartView = [0, 0];
this.clickTabStartTime = 0;
this.clickTabStartCoords = [0, 0];
this.startTabDist = 50;
this.startTabScale = this.store.getState().scale;
this.isMultiTab = false;
this.isMouseDown = false;
document.addEventListener('keydown', this.onKeyPress, false);
viewport.addEventListener('auxclick', this.onAuxClick, false);
viewport.addEventListener('mousedown', this.onMouseDown, false);
viewport.addEventListener('mousemove', this.onMouseMove, false);
viewport.addEventListener('mouseup', this.onMouseUp, false);
viewport.addEventListener('wheel', this.onWheel, false);
viewport.addEventListener('touchstart', this.onTouchStart, false);
viewport.addEventListener('touchend', this.onTouchEnd, false);
viewport.addEventListener('touchmove', this.onTouchMove, false);
viewport.addEventListener('mouseout', this.onMouseOut, false);
}
switch (keycode(event)) {
case 'up':
case 'w':
store.dispatch(moveNorth());
break;
case 'left':
case 'a':
store.dispatch(moveWest());
break;
case 'down':
case 's':
store.dispatch(moveSouth());
break;
case 'right':
case 'd':
store.dispatch(moveEast());
break;
/*
case 'space':
if ($viewport) $viewport.click();
return;
*/
case '+':
case 'e':
store.dispatch(zoomIn());
break;
case '-':
case 'q':
store.dispatch(zoomOut());
break;
default:
dispose() {
document.removeEventListener('keydown', this.onKeyPress, false);
}
}
export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
store = curStore;
viewport.onmousemove = ({ clientX, clientY }: MouseEvent) => {
const state = store.getState();
const screenCoor = screenToWorld(state, viewport, [clientX, clientY]);
store.dispatch(setHover(screenCoor));
};
viewport.onmouseout = () => {
store.dispatch(unsetHover());
};
viewport.onwheel = ({ deltaY }: WheelEvent) => {
const state = store.getState();
const { hover } = state.gui;
let zoompoint = null;
if (hover) {
zoompoint = hover;
}
if (deltaY < 0) {
store.dispatch(zoomIn(zoompoint));
}
if (deltaY > 0) {
store.dispatch(zoomOut(zoompoint));
}
store.dispatch(onViewFinishChange());
};
viewport.onauxclick = ({ which, clientX, clientY }: MouseEvent) => {
// middle mouse button
if (which !== 2) {
return;
}
const state = store.getState();
if (state.canvas.scale < 3) {
return;
}
const coords = screenToWorld(state, viewport, [clientX, clientY]);
const clrIndex = renderer.getColorIndexOfPixel(...coords);
if (clrIndex === null) {
return;
}
store.dispatch(selectColor(clrIndex));
};
onMouseDown(event: MouseEvent) {
event.preventDefault();
// fingers controls on touch
const hammertime = new Hammer(viewport);
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL });
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
// Zoom-in Zoom-out in touch devices
hammertime.get('pinch').set({ enable: true });
if (event.button === 0) {
this.isMouseDown = true;
const { clientX, clientY } = event;
this.clickTabStartTime = Date.now();
this.clickTabStartCoords = [clientX, clientY];
this.clickTabStartView = this.store.getState().canvas.view;
const { viewport } = this;
setTimeout(() => {
viewport.style.cursor = 'move';
}, 300);
}
}
hammertime.on('tap', ({ center }) => {
onMouseUp(event: MouseEvent) {
event.preventDefault();
if (event.button === 0) {
this.isMouseDown = false;
const { clientX, clientY } = event;
const { clickTabStartCoords, clickTabStartTime } = this;
const coordsDiff = [
clickTabStartCoords[0] - clientX,
clickTabStartCoords[1] - clientY,
];
// thresholds for single click / holding
if (clickTabStartTime > Date.now() - 300
&& coordsDiff[0] < 10 && coordsDiff[1] < 10) {
PixelPlainterControls.placePixel(
this.store,
this.viewport,
this.renderer,
[clientX, clientY],
);
}
this.viewport.style.cursor = 'auto';
}
}
static getTouchCenter(event: TouchEvent) {
switch (event.touches.length) {
case 1: {
const { pageX, pageY } = event.touches[0];
return [pageX, pageY];
}
case 2: {
const pageX = Math.floor(0.5
* (event.touches[0].pageX + event.touches[1].pageX));
const pageY = Math.floor(0.5
* (event.touches[0].pageY + event.touches[1].pageY));
return [pageX, pageY];
}
default:
break;
}
return null;
}
static placePixel(store, viewport, renderer, center) {
const state = store.getState();
const { autoZoomIn } = state.gui;
const { placeAllowed } = state.user;
@ -133,8 +140,7 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
if (isHistoricalView) return;
const { x, y } = center;
const cell = screenToWorld(state, viewport, [x, y]);
const cell = screenToWorld(state, viewport, center);
if (autoZoomIn && scale < 8) {
store.dispatch(setViewCoordinates(cell));
@ -142,7 +148,7 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
return;
}
// don't allow placing of pixel just on low zoomlevels
// allow placing of pixel just on low zoomlevels
if (scale < 3) return;
if (!placeAllowed) return;
@ -156,61 +162,218 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
selectedColor,
));
}
});
}
const initialState: State = store.getState();
[window.lastPosX, window.lastPosY] = initialState.canvas.view;
let lastScale = initialState.canvas.scale;
hammertime.on(
'panstart pinchstart pan pinch panend pinchend',
({
type, deltaX, deltaY, scale,
}) => {
viewport.style.cursor = 'move'; // like google maps
const { scale: viewportScale } = store.getState().canvas;
static getMultiTouchDistance(event: TouchEvent) {
if (event.touches.length < 2) {
return 1;
}
const a = event.touches[0];
const b = event.touches[1];
return Math.sqrt(
(b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
);
}
// pinch start
if (type === 'pinchstart') {
store.dispatch(unsetHover());
lastScale = viewportScale;
onTouchStart(event: TouchEvent) {
event.preventDefault();
this.clickTabStartTime = Date.now();
this.clickTabStartCoords = PixelPlainterControls.getTouchCenter(event);
const state = this.store.getState();
this.clickTabStartView = state.canvas.view;
if (event.touches.length > 1) {
this.startTabScale = state.canvas.scale;
this.startTabDist = PixelPlainterControls.getMultiTouchDistance(event);
this.isMultiTab = true;
} else {
this.isMultiTab = false;
}
}
onTouchEnd(event: TouchEvent) {
event.preventDefault();
if (event.changedTouches.length < 2) {
const { pageX, pageY } = event.changedTouches[0];
const { clickTabStartCoords, clickTabStartTime } = this;
const coordsDiff = [
clickTabStartCoords[0] - pageX,
clickTabStartCoords[1] - pageY,
];
// thresholds for single click / holding
if (clickTabStartTime > Date.now() - 300
&& coordsDiff[0] < 10 && coordsDiff[1] < 10) {
const { store, viewport } = this;
PixelPlainterControls.placePixel(
store,
viewport,
this.renderer,
[pageX, pageY],
);
setTimeout(() => {
store.dispatch(unsetHover());
}, 500);
}
}
}
// panstart
if (type === 'panstart') {
store.dispatch(unsetHover());
const { view: initView } = store.getState().canvas;
[window.lastPosX, window.lastPosY] = initView;
}
onTouchMove(event: TouchEvent) {
event.preventDefault();
// pinch
if (type === 'pinch') {
store.dispatch(setScale(lastScale * scale));
}
const multiTouch = (event.touches.length > 1);
// pan
const [clientX, clientY] = PixelPlainterControls.getTouchCenter(event);
const { store } = this;
const state = store.getState();
if (this.isMultiTab !== multiTouch) {
// if one finger got lifted or added, reset clickTabStart
this.isMultiTab = multiTouch;
this.clickTabStartCoords = [clientX, clientY];
this.clickTabStartView = state.canvas.view;
this.startTabDist = PixelPlainterControls.getMultiTouchDistance(event);
this.startTabScale = state.canvas.scale;
} else {
// pan
const { clickTabStartView, clickTabStartCoords } = this;
const [lastPosX, lastPosY] = clickTabStartView;
const deltaX = clientX - clickTabStartCoords[0];
const deltaY = clientY - clickTabStartCoords[1];
const { scale } = state.canvas;
store.dispatch(setViewCoordinates([
window.lastPosX - (deltaX / viewportScale),
window.lastPosY - (deltaY / viewportScale),
lastPosX - (deltaX / scale),
lastPosY - (deltaY / scale),
]));
// pinch end
if (type === 'pinchend') {
lastScale = viewportScale;
// pinch
if (multiTouch) {
const a = event.touches[0];
const b = event.touches[1];
const { startTabDist, startTabScale } = this;
const dist = Math.sqrt(
(b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
);
const pinchScale = dist / startTabDist;
store.dispatch(setScale(startTabScale * pinchScale));
}
}
}
// panend
if (type === 'panend') {
store.dispatch(onViewFinishChange());
const { view } = store.getState().canvas;
[window.lastPosX, window.lastPosY] = view;
viewport.style.cursor = 'auto';
}
},
);
onWheel(event: MouseEvent) {
const { deltaY } = event;
const { store } = this;
const state = store.getState();
const { hover } = state.gui;
let zoompoint = null;
if (hover) {
zoompoint = hover;
}
if (deltaY < 0) {
store.dispatch(zoomIn(zoompoint));
}
if (deltaY > 0) {
store.dispatch(zoomOut(zoompoint));
}
store.dispatch(onViewFinishChange());
}
document.addEventListener('keydown', onKeyPress, false);
onMouseMove(event: MouseEvent) {
event.preventDefault();
const { clientX, clientY } = event;
const { store, isMouseDown } = this;
const state = store.getState();
if (isMouseDown) {
const { clickTabStartView, clickTabStartCoords } = this;
const [lastPosX, lastPosY] = clickTabStartView;
const deltaX = clientX - clickTabStartCoords[0];
const deltaY = clientY - clickTabStartCoords[1];
const { scale } = state.canvas;
store.dispatch(setViewCoordinates([
lastPosX - (deltaX / scale),
lastPosY - (deltaY / scale),
]));
} else {
const screenCoor = screenToWorld(
state,
this.viewport,
[clientX, clientY],
);
store.dispatch(setHover(screenCoor));
}
}
onMouseOut() {
const { store } = this;
store.dispatch(unsetHover());
}
onAuxClick(event: MouseEvent) {
const { which, clientX, clientY } = event;
const { store } = this;
// middle mouse button
if (which !== 2) {
return;
}
event.preventDefault();
const state = store.getState();
if (state.canvas.scale < 3) {
return;
}
const coords = screenToWorld(state, this.viewport, [clientX, clientY]);
const clrIndex = this.renderer.getColorIndexOfPixel(...coords);
if (clrIndex === null) {
return;
}
store.dispatch(selectColor(clrIndex));
}
onKeyPress(event: KeyboardEvent) {
// ignore key presses if modal is open or chat is used
if (event.target.nodeName === 'INPUT'
|| event.target.nodeName === 'TEXTAREA'
) {
return;
}
const { store } = this;
switch (keycode(event)) {
case 'up':
case 'w':
store.dispatch(moveNorth());
break;
case 'left':
case 'a':
store.dispatch(moveWest());
break;
case 'down':
case 's':
store.dispatch(moveSouth());
break;
case 'right':
case 'd':
store.dispatch(moveEast());
break;
/*
case 'space':
if ($viewport) $viewport.click();
return;
*/
case '+':
case 'e':
store.dispatch(zoomIn());
break;
case '-':
case 'q':
store.dispatch(zoomOut());
break;
default:
}
}
}
export function removeControls() {
document.removeEventListener('keydown', onKeyPress, false);
}
export default PixelPlainterControls;

View File

@ -11,6 +11,12 @@ import chatProvider from '../../core/ChatProvider';
async function chatHistory(req: Request, res: Response) {
let { cid, limit } = req.query;
res.set({
'Cache-Control': 'no-cache, no-store, must-revalidate',
Pragma: 'no-cache',
Expires: '0',
});
if (!cid || !limit) {
res.status(400);
res.json({

View File

@ -18,10 +18,7 @@ import {
renderPlaceholder,
renderPotatoPlaceholder,
} from './render2Delements';
import {
initControls,
removeControls,
} from '../controls/PixelPainterControls';
import PixelPainterControls from '../controls/PixelPainterControls';
import ChunkLoader from './ChunkLoader2D';
@ -90,7 +87,7 @@ class Renderer {
}
destructor() {
removeControls(this.viewport);
this.controls.dispose();
window.removeEventListener('resize', this.resizeHandle);
this.viewport.remove();
}
@ -120,8 +117,8 @@ class Renderer {
canvasSize,
} = state.canvas;
this.updateCanvasData(state);
initControls(this, this.viewport, store);
this.updateScale(viewscale, canvasMaxTiledZoom, view, canvasSize);
this.controls = new PixelPainterControls(this, this.viewport, store);
}
updateCanvasData(state: State) {