mirror of
https://codeberg.org/demostf/frontend.git
synced 2026-06-03 18:24:12 +02:00
import analyser and migrate to solidjs, untested
This commit is contained in:
parent
95d48e48e2
commit
fff554c3d3
42 changed files with 2910 additions and 4 deletions
102
script/viewer/Panner/CenteredPanZoom.ts
Normal file
102
script/viewer/Panner/CenteredPanZoom.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import {Viewport, Point} from './Viewport';
|
||||
|
||||
export interface CenteredPanZoomOptions {
|
||||
screenWidth: number;
|
||||
screenHeight: number;
|
||||
scale?: number;
|
||||
contentSize: {
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
}
|
||||
|
||||
export class CenteredPanZoom {
|
||||
screen: Viewport;
|
||||
viewport: Viewport;
|
||||
scale: number;
|
||||
contentSize: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
|
||||
constructor(options: CenteredPanZoomOptions) {
|
||||
this.screen = new Viewport({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: options.screenWidth,
|
||||
height: options.screenHeight
|
||||
});
|
||||
this.viewport = new Viewport({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: options.screenWidth,
|
||||
height: options.screenHeight
|
||||
});
|
||||
this.scale = options.scale || 1;
|
||||
this.contentSize = options.contentSize;
|
||||
}
|
||||
|
||||
setSize(width, height) {
|
||||
this.screen.width = width;
|
||||
this.screen.height = height;
|
||||
this.viewport.width = width * this.scale;
|
||||
this.viewport.height = height * this.scale;
|
||||
this.constrainPan(true);
|
||||
}
|
||||
|
||||
setContentSize(width, height) {
|
||||
this.contentSize = {width, height};
|
||||
this.constrainPan(true);
|
||||
}
|
||||
|
||||
pan(screenX, screenY) {
|
||||
this.viewport.x = this.viewport.x + screenX;
|
||||
this.viewport.y = this.viewport.y + screenY;
|
||||
this.constrainPan();
|
||||
}
|
||||
|
||||
constrainPan(strict = false) {
|
||||
const minX = (strict) ? 0 : this.screen.width * 0.5;
|
||||
const minY = (strict) ? 0 : this.screen.height * 0.5;
|
||||
this.viewport.x = Math.min(minX, this.viewport.x);
|
||||
this.viewport.y = Math.min(minY * 0.5, this.viewport.y);
|
||||
const maxY = (this.screen.height - (this.contentSize.height * this.scale)) - (strict ? 0 : this.screen.height * 0.5);
|
||||
const maxX = (this.screen.width - (this.contentSize.width * this.scale)) - (strict ? 0 : this.screen.width * 0.5);
|
||||
this.viewport.y = Math.max(maxY, this.viewport.y);
|
||||
this.viewport.x = Math.max(maxX, this.viewport.x);
|
||||
if (maxY > ((strict) ? 0 : this.screen.height * 0.5)) {
|
||||
this.viewport.y = Math.floor(maxY / 2);
|
||||
}
|
||||
if (maxX > ((strict) ? 0 : this.screen.width * 0.5)) {
|
||||
this.viewport.x = Math.floor(maxX / 2);
|
||||
}
|
||||
}
|
||||
|
||||
panFrom(screenStart: Point, screenEnd: Point) {
|
||||
this.pan(screenEnd.x - screenStart.x, screenEnd.y - screenStart.y);
|
||||
}
|
||||
|
||||
//find zoom point in pre-zoom viewport
|
||||
//make that point the same in the post-zoom viewport
|
||||
zoom(scale: number, screenCenter: Point) {
|
||||
const v1 = Viewport.convert(screenCenter, {
|
||||
from: this.screen,
|
||||
to: this.viewport
|
||||
});
|
||||
this.viewport.x = this.viewport.x * (scale / this.scale);
|
||||
this.viewport.y = this.viewport.y * (scale / this.scale);
|
||||
this.viewport.width = this.screen.width * scale;
|
||||
this.viewport.height = this.screen.height * scale;
|
||||
const minScale = Math.min(this.screen.width / this.contentSize.width, this.screen.height / this.contentSize.height);
|
||||
scale = Math.max(minScale, scale);
|
||||
this.scale = scale;
|
||||
|
||||
const v2 = Viewport.convert(screenCenter, {
|
||||
from: this.screen,
|
||||
to: this.viewport
|
||||
});
|
||||
const deltaX = v2.x - v1.x;
|
||||
const deltaY = v2.y - v1.y;
|
||||
this.pan(deltaX * scale, deltaY * scale);
|
||||
}
|
||||
}
|
||||
54
script/viewer/Panner/Panner.css
Normal file
54
script/viewer/Panner/Panner.css
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
.pan-zoom-element {
|
||||
border: 1px solid #ccc;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: default;
|
||||
|
||||
.content-container {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.noselect {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.zoommenu {
|
||||
position: fixed;
|
||||
color: #888;
|
||||
|
||||
bottom: 100px;
|
||||
right: 0;
|
||||
margin: 10px;
|
||||
font-size: 200%;
|
||||
|
||||
opacity: 0.5;
|
||||
transition: all 0.5s;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 5px;
|
||||
|
||||
div {
|
||||
transition: all 0.5s;
|
||||
width: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
&:hover {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
div:first-child {
|
||||
border-bottom: 1px solid #ddd;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
116
script/viewer/Panner/Panner.tsx
Normal file
116
script/viewer/Panner/Panner.tsx
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import {CenteredPanZoom} from './CenteredPanZoom';
|
||||
import {createSignal} from "solid-js";
|
||||
|
||||
export interface PannerProps {
|
||||
width: number;
|
||||
height: number;
|
||||
scale: number;
|
||||
contentSize: {
|
||||
width: number;
|
||||
height: number;
|
||||
};
|
||||
onScale?: (scale: number) => any;
|
||||
children: Element;
|
||||
}
|
||||
|
||||
export const Panner = (props: PannerProps) => {
|
||||
const [scale, setScale] = createSignal(0);
|
||||
const [translateX, setTranslateX] = createSignal(0);
|
||||
const [translateY, setTranslateY] = createSignal(0);
|
||||
|
||||
const panner = new CenteredPanZoom({
|
||||
screenHeight: props.height,
|
||||
screenWidth: props.width,
|
||||
scale: props.scale,
|
||||
contentSize: props.contentSize
|
||||
});
|
||||
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
|
||||
const mouseMove = (event) => {
|
||||
const {pageX, pageY} = event;
|
||||
panner.panFrom(
|
||||
{
|
||||
x: startX,
|
||||
y: startY
|
||||
},
|
||||
{
|
||||
x: pageX,
|
||||
y: pageY
|
||||
}
|
||||
);
|
||||
startX = event.pageX;
|
||||
startY = event.pageY;
|
||||
|
||||
setTranslateX(panner.viewport.x);
|
||||
setTranslateY(panner.viewport.y);
|
||||
setScale(panner.scale);
|
||||
}
|
||||
|
||||
const mouseDown = (event) => {
|
||||
startX = event.pageX;
|
||||
startY = event.pageY;
|
||||
|
||||
const mouseUp = () => {
|
||||
document.removeEventListener('mouseup', mouseUp, true);
|
||||
document.removeEventListener('mousemove', mouseMove, true);
|
||||
};
|
||||
document.addEventListener('mouseup', mouseUp, true);
|
||||
document.addEventListener('mousemove', mouseMove, true);
|
||||
}
|
||||
|
||||
const applyZoom = (factor: number, center) => {
|
||||
panner.zoom(factor, center);
|
||||
|
||||
setTranslateX(panner.viewport.x);
|
||||
setTranslateY(panner.viewport.y);
|
||||
setScale(panner.scale);
|
||||
|
||||
if (props.onScale) {
|
||||
props.onScale(panner.scale);
|
||||
}
|
||||
}
|
||||
|
||||
const mouseWheel = (event) => {
|
||||
event.preventDefault();
|
||||
const center = {x: event.pageX, y: event.pageY};
|
||||
let zoomFactor = (event.deltaY < 0) ? 1.05 : 0.95;
|
||||
|
||||
const factor = scale() * zoomFactor;
|
||||
applyZoom(factor, center);
|
||||
}
|
||||
|
||||
const center = () => ({
|
||||
x: Math.floor(panner.screen.width / 2),
|
||||
y: Math.floor(panner.screen.height / 2)
|
||||
});
|
||||
|
||||
return (
|
||||
<div class="pan-zoom-element"
|
||||
style={{width: `${props.width}px`, height: `${props.height}px`}}
|
||||
onMouseDown={mouseDown}
|
||||
onWheel={mouseWheel}>
|
||||
<div class="content-container noselect"
|
||||
style={{
|
||||
transform: `translate(${translateX()}px, ${translateY()}px) scale(${scale()})`,
|
||||
"transform-origin": 'top left'
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
<div class="zoommenu">
|
||||
<div class="plus"
|
||||
onClick={() => {
|
||||
applyZoom(1.10, center())
|
||||
}}>+
|
||||
</div>
|
||||
<div class="minus"
|
||||
onClick={() => {
|
||||
applyZoom(0.90, center())
|
||||
}}>
|
||||
-
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
36
script/viewer/Panner/Viewport.ts
Normal file
36
script/viewer/Panner/Viewport.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
export interface ViewPortOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
export interface Point {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export class Viewport {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
|
||||
constructor(options: ViewPortOptions) {
|
||||
this.x = options.x || 0;
|
||||
this.y = options.y || 0;
|
||||
this.width = options.width;
|
||||
this.height = options.height;
|
||||
}
|
||||
|
||||
static convert(point: Point, {from, to}: {from: Viewport, to: Viewport}): Point {
|
||||
const widthScale = from.width / to.width;
|
||||
const heightScale = from.height / to.height;
|
||||
|
||||
return {
|
||||
x: point.x * widthScale - to.x * widthScale,
|
||||
y: point.y * heightScale - to.y * heightScale
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue