Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 181 additions & 6 deletions FlipViewer/src/FlipBook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,46 @@ import { createRoot } from 'react-dom/client';
import styles from './styles.module.css';
import React, { createRef } from 'react';
import { renderImage } from "./Render";
import { ImageContainer, OnClickHandler } from './ImageContainer';
import { ImageContainer, OnClickHandler, OnWheelHandler, OnMouseOverHandler, ImageContainerState, OnKeyHandler } from './ImageContainer';
import { ToneMapControls } from './ToneMapControls';
import { MethodList } from './MethodList';
import { Tools } from './Tools';
import { Popup } from './Popup';
import { ToneMapSettings, ZoomLevel } from './flipviewer';

const UPDATE_INTERVAL_MS = 100;
const UPDATE_INTERVAL_MS = 500;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?


// Registry to update flipbooks -------------
export type BookRef = React.RefObject<FlipBook>;
const registry = new Map<string, Set<BookRef>>();

export function keyToString(key: number[]){
if(key)
return key.join(',');
else
return "";
}
Comment on lines +18 to +23
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not make key a string everywhere in the first place?


export function getBooks(key: number[]): BookRef[] {
return Array.from(registry.get(keyToString(key)) ?? new Set());
}
export function registerBook(key: number[], ref: BookRef) {
if (!key) return;
const set = registry.get(keyToString(key)) ?? new Set<BookRef>();
set.add(ref);
registry.set(keyToString(key), set);
}
export function unregisterBook(key: number[], ref: BookRef) {
const set = registry.get(keyToString(key));
if (!set) return;
set.delete(ref);
if (set.size === 0) registry.delete(keyToString(key));
}
// ---------------------------------------------------------------

// to get only key presses and not is down state
// Idea is to only fire events if state of key changes
const keyPressed = new Set<string>();

export class ToneMappingImage {
currentTMO: string;
Expand All @@ -25,7 +57,8 @@ export class ToneMappingImage {

let hdrImg = this;
setInterval(function() {
if (!hdrImg.dirty) return;
if (!hdrImg.dirty)
return;
hdrImg.dirty = false;
renderImage(hdrImg.canvas, hdrImg.pixels, hdrImg.currentTMO);
onAfterRender();
Expand All @@ -35,16 +68,43 @@ export class ToneMappingImage {
this.currentTMO = tmo;
this.dirty = true;
}
setPixels(p: Float32Array | ImageData) {
this.pixels = p;
this.dirty = true;
}
}


type SelectUpdateFn = (groupName: string, newIdx: number) => void;
var selectUpdateListeners: SelectUpdateFn[] = [];


type TMOUpdateFn = (groupName: string, newTMOSettings: ToneMapSettings) => void;
var tmoUpdateListeners: TMOUpdateFn[] = [];

type imageConStateUpdateFn = (groupName: string, newImgConState: ImageContainerState) => void;
var imgConStateUpdateListeners: imageConStateUpdateFn[] = [];

export function SetGroupIndex(groupName: string, newIdx: number) {
for (let fn of selectUpdateListeners)
fn(groupName, newIdx);
}


export function SetGroupTMOSettings(groupName: string, newTMOSettings: ToneMapSettings)
{
for (let fn of tmoUpdateListeners)
fn(groupName, newTMOSettings);
}


export function SetGroupImageContainerSettings(groupName: string, newImgConState: ImageContainerState)
{
for (let fn of imgConStateUpdateListeners)
fn(groupName, newImgConState);
}


export interface FlipProps {
names: string[];
width: number;
Expand All @@ -57,11 +117,16 @@ export interface FlipProps {
initialTMOOverrides: ToneMapSettings[];
style?: React.CSSProperties;
onClick?: OnClickHandler;
onWheel?: OnWheelHandler;
onMouseOver?: OnMouseOverHandler;
onKeyIC?: OnKeyHandler;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onKeyIC is not a very clear name --- what does IC stand for?

// onKeyUpIC?: OnKeyUpHandler;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

groupName?: string;
hideTools: boolean;
keyStr: string;
}

interface FlipState {
export interface FlipState {
selectedIdx: number;
popupContent?: React.ReactNode;
popupDurationMs?: number;
Expand All @@ -75,6 +140,7 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {

constructor(props : FlipProps) {
super(props);

this.state = {
selectedIdx: 0,
hideTools: props.hideTools
Expand All @@ -85,10 +151,30 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
this.tools = createRef();

this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
this.onSelectUpdate = this.onSelectUpdate.bind(this);
this.onTMOUpdate = this.onTMOUpdate.bind(this);
}

onKeyUp(evt: React.KeyboardEvent<HTMLDivElement>) {
// c# listener
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// c# listener
// trigger callbacks

The JS side should be independent of the code generation lang: There is support for generating FlipBooks from Python and we might turn this into a standalone JS lib at some point, too...

if(this.props.onKeyIC && keyPressed.has(evt.key) && (evt.key === "Alt" || evt.key === "Control"))
{
keyPressed.delete(evt.key);
evt.preventDefault();
this.props.onKeyIC(this.state.selectedIdx, this.props.keyStr, evt.key, false);
}
}

onKeyDown(evt: React.KeyboardEvent<HTMLDivElement>) {
// c# listener
if(this.props.onKeyIC && !keyPressed.has(evt.key) && (evt.key === "Alt" || evt.key === "Control"))
{
keyPressed.add(evt.key);
evt.preventDefault();
this.props.onKeyIC(this.state.selectedIdx, this.props.keyStr, evt.key, true);
}

let newIdx = this.state.selectedIdx;
if (evt.key === "ArrowLeft" || evt.key === "ArrowDown") {
newIdx = this.state.selectedIdx - 1;
Expand Down Expand Up @@ -138,7 +224,13 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
evt.stopPropagation();
}

if (evt.key === "r") {
if (evt.ctrlKey && evt.key === 'r') {
this.tmoCtrls.current.state.globalSettings.exposure = 0;
evt.stopPropagation();
evt.preventDefault();
}

if (!evt.ctrlKey && evt.key === "r") {
this.reset();
evt.stopPropagation();
}
Expand All @@ -147,6 +239,9 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
this.setState({hideTools: !this.state.hideTools});
evt.stopPropagation();
}


this.updateTMOSettings(this.tmoCtrls.current.state.globalSettings);
}

reset() {
Expand Down Expand Up @@ -236,6 +331,13 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
else this.setState({selectedIdx: newIdx});
}


updateTMOSettings(newTMOSettings: ToneMapSettings){
if (this.props.groupName) SetGroupTMOSettings(this.props.groupName, newTMOSettings);
else this.tmoCtrls.current.applySettings(newTMOSettings);

}

render(): React.ReactNode {
let popup = null;
if (this.state.popupContent) {
Expand All @@ -249,7 +351,7 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
}

return (
<div className={styles['flipbook']} style={this.props.style} onKeyDown={this.onKeyDown}>
<div className={styles['flipbook']} style={this.props.style} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp}>
<div style={{display: "contents"}}>
<MethodList
names={this.props.names}
Expand All @@ -265,6 +367,9 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
selectedIdx={this.state.selectedIdx}
onZoom={(zoom) => this.tools.current.onZoom(zoom)}
onClick={this.props.onClick}
onWheel={this.props.onWheel}
onMouseOver={this.props.onMouseOver}
onStateChange={(st) => this.onImageContainerUpdate(st)}
>
{popup}
<button className={styles.toolsBtn}
Expand Down Expand Up @@ -302,16 +407,69 @@ export class FlipBook extends React.Component<FlipProps, FlipState> {
}
}


onTMOUpdate(groupName: string, newTMOSettings: ToneMapSettings) {
if (groupName == this.props.groupName) {
this.tmoCtrls.current.applySettings(newTMOSettings);
}
}


// is called when onStateIsChanged in ImageContainer is called
// everytime when the ImageContainerState changes (pos, zoom, etc.)
// calls onImageContainerGroupUpdate = ()
onImageContainerUpdate(newImageContainerState: ImageContainerState) {
if (this.props.groupName) {
SetGroupImageContainerSettings(this.props.groupName, newImageContainerState);
}
}


// is called when other flipbook's ImageContainerStates changes
onImageContainerGroupUpdate = (groupName: string, newImageContainerState: ImageContainerState) => {
if (groupName === this.props.groupName && this.imageContainer.current) {
this.imageContainer.current.setState({
posX: newImageContainerState.posX,
posY: newImageContainerState.posY,
scale: newImageContainerState.scale,

magnifierX: newImageContainerState.magnifierX,
magnifierY: newImageContainerState.magnifierY,
magnifierVisible: newImageContainerState.magnifierVisible,
magnifierRow: newImageContainerState.magnifierRow,
magnifierCol: newImageContainerState.magnifierCol,

cropX: newImageContainerState.cropX,
cropY: newImageContainerState.cropY,
cropWidth: newImageContainerState.cropWidth,
cropHeight: newImageContainerState.cropHeight,
cropActive: newImageContainerState.cropActive,
cropDragging: newImageContainerState.cropDragging,
cropMeans: newImageContainerState.cropMeans
Comment on lines +432 to +448
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there must be a more compact and less typo-prone way to achieve this

Doesn't setState(newImageContainerState) work?

});
}
}

componentDidMount(): void {
if (this.props.initialZoom)
this.imageContainer.current.setZoom(this.props.initialZoom);

selectUpdateListeners.push(this.onSelectUpdate);
tmoUpdateListeners.push(this.onTMOUpdate);
imgConStateUpdateListeners.push(this.onImageContainerGroupUpdate);
}

componentWillUnmount(): void {
let idx = selectUpdateListeners.findIndex(v => v === this.onSelectUpdate);
selectUpdateListeners.splice(idx, 1);


idx = tmoUpdateListeners.findIndex(v => v === this.onTMOUpdate);
tmoUpdateListeners.splice(idx, 1);


idx = imgConStateUpdateListeners.findIndex(v => v === this.onImageContainerGroupUpdate);
imgConStateUpdateListeners.splice(idx, 1);
}

connect(other: React.RefObject<FlipBook>) {
Expand Down Expand Up @@ -381,8 +539,14 @@ export type FlipBookParams = {
initialTMO: ToneMapSettings,
initialTMOOverrides: ToneMapSettings[],
onClick?: OnClickHandler,
onWheel?: OnWheelHandler,
onMouseOver?: OnMouseOverHandler,
onKeyIC?: OnKeyHandler,
// onKeyUpIC?: OnKeyUpHandler,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

colorTheme?: string,
hideTools: boolean,
containerId: string,
key: number[],
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meaning is not obvious, should be documented

}

export function AddFlipBook(params: FlipBookParams, groupName?: string) {
Expand Down Expand Up @@ -415,8 +579,10 @@ export function AddFlipBook(params: FlipBookParams, groupName?: string) {
let themeStyle = colorThemes[params.colorTheme ?? "dark"];

const root = createRoot(params.parentElement);
const bookRef = createRef<FlipBook>();
root.render(
<FlipBook
ref={bookRef}
names={params.names}
width={params.width}
height={params.height}
Expand All @@ -427,14 +593,23 @@ export function AddFlipBook(params: FlipBookParams, groupName?: string) {
initialTMO={params.initialTMO}
initialTMOOverrides={params.initialTMOOverrides}
onClick={params.onClick}
onWheel={params.onWheel}
onMouseOver={params.onMouseOver}
onKeyIC={params.onKeyIC}
// onKeyUpIC={params.onKeyUpIC}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this commented out?

style={themeStyle}
groupName={groupName}
hideTools={params.hideTools}
keyStr={keyToString(params.key)}
/>
);

if(params.key)
registerBook(params.key, bookRef);
Comment on lines +607 to +608
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

registerBook does the same check you also do here.
There should be a clear responsibility on one side. Either registerBook should be expected to cope with null / empty key or it should expect never to get those and the check should be here.


new MutationObserver(_ => {
if (!document.body.contains(params.parentElement)) {
unregisterBook(params.key, bookRef);
root.unmount();
}
}).observe(document.body, {childList: true, subtree: true});
Expand Down
Loading