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 * @flow
*/ */
import Hammer from 'hammerjs';
import keycode from 'keycode'; import keycode from 'keycode';
import { import {
@ -28,100 +27,108 @@ import {
getOffsetOfPixel, getOffsetOfPixel,
} from '../core/utils'; } 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) { this.onMouseDown = this.onMouseDown.bind(this);
// ignore key presses if modal is open or chat is used this.onKeyPress = this.onKeyPress.bind(this);
if (event.target.nodeName === 'INPUT' this.onAuxClick = this.onAuxClick.bind(this);
|| event.target.nodeName === 'TEXTAREA' this.onMouseOut = this.onMouseOut.bind(this);
) { this.onMouseMove = this.onMouseMove.bind(this);
return; 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)) { dispose() {
case 'up': document.removeEventListener('keydown', this.onKeyPress, false);
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 initControls(renderer, viewport: HTMLCanvasElement, curStore) { onMouseDown(event: MouseEvent) {
store = curStore; event.preventDefault();
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));
};
// fingers controls on touch if (event.button === 0) {
const hammertime = new Hammer(viewport); this.isMouseDown = true;
hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); const { clientX, clientY } = event;
hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL }); this.clickTabStartTime = Date.now();
// Zoom-in Zoom-out in touch devices this.clickTabStartCoords = [clientX, clientY];
hammertime.get('pinch').set({ enable: true }); 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 state = store.getState();
const { autoZoomIn } = state.gui; const { autoZoomIn } = state.gui;
const { placeAllowed } = state.user; const { placeAllowed } = state.user;
@ -133,8 +140,7 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
if (isHistoricalView) return; if (isHistoricalView) return;
const { x, y } = center; const cell = screenToWorld(state, viewport, center);
const cell = screenToWorld(state, viewport, [x, y]);
if (autoZoomIn && scale < 8) { if (autoZoomIn && scale < 8) {
store.dispatch(setViewCoordinates(cell)); store.dispatch(setViewCoordinates(cell));
@ -142,7 +148,7 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
return; return;
} }
// don't allow placing of pixel just on low zoomlevels // allow placing of pixel just on low zoomlevels
if (scale < 3) return; if (scale < 3) return;
if (!placeAllowed) return; if (!placeAllowed) return;
@ -156,61 +162,218 @@ export function initControls(renderer, viewport: HTMLCanvasElement, curStore) {
selectedColor, selectedColor,
)); ));
} }
}); }
const initialState: State = store.getState(); static getMultiTouchDistance(event: TouchEvent) {
[window.lastPosX, window.lastPosY] = initialState.canvas.view; if (event.touches.length < 2) {
let lastScale = initialState.canvas.scale; return 1;
hammertime.on( }
'panstart pinchstart pan pinch panend pinchend', const a = event.touches[0];
({ const b = event.touches[1];
type, deltaX, deltaY, scale, return Math.sqrt(
}) => { (b.pageX - a.pageX) ** 2 + (b.pageY - a.pageY) ** 2,
viewport.style.cursor = 'move'; // like google maps );
const { scale: viewportScale } = store.getState().canvas; }
// pinch start onTouchStart(event: TouchEvent) {
if (type === 'pinchstart') { event.preventDefault();
store.dispatch(unsetHover());
lastScale = viewportScale; 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 onTouchMove(event: TouchEvent) {
if (type === 'panstart') { event.preventDefault();
store.dispatch(unsetHover());
const { view: initView } = store.getState().canvas;
[window.lastPosX, window.lastPosY] = initView;
}
// pinch const multiTouch = (event.touches.length > 1);
if (type === 'pinch') {
store.dispatch(setScale(lastScale * scale));
}
// 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 // 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([ store.dispatch(setViewCoordinates([
window.lastPosX - (deltaX / viewportScale), lastPosX - (deltaX / scale),
window.lastPosY - (deltaY / viewportScale), lastPosY - (deltaY / scale),
])); ]));
// pinch end // pinch
if (type === 'pinchend') { if (multiTouch) {
lastScale = viewportScale; 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 onWheel(event: MouseEvent) {
if (type === 'panend') { const { deltaY } = event;
store.dispatch(onViewFinishChange()); const { store } = this;
const { view } = store.getState().canvas; const state = store.getState();
[window.lastPosX, window.lastPosY] = view; const { hover } = state.gui;
viewport.style.cursor = 'auto'; 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() { export default PixelPlainterControls;
document.removeEventListener('keydown', onKeyPress, false);
}

View File

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

View File

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