Loading index.html +33 −14 Changes for index.html: 33 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -96,20 +96,39 @@ <label for="button-reset-controls" v-t="controls.reset"></label> </span> <div class="export-panel" v-show="phase === 'export'"> <span>X: </span> <span>{{ Math.round(exportParams.area.x) }}</span> <span>Y: </span> <span>{{ Math.round(exportParams.area.y) }}</span> <span>Width: </span> <span>{{ Math.round(exportParams.area.w) }}</span> <span>Height: </span> <span>{{ Math.round(exportParams.area.h) }}</span> <span>Inner: </span> <span>{{ `${exportParams.innerGrab.round()}` }}</span> <span>Outer: </span> <span>{{ `${exportParams.outerGrab.round()}` }}</span> <span>Keys:</span> <span>{{ modKeys }}</span> <div class="export-area-matrix"> <div></div> <div>Start</div> <div>Size</div> <div>End</div> <div>X</div> <div><input type="number" name="x-start" aria-label="X start" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.x" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'start'"></div> <div><input type="number" name="x-size" aria-label="X size" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.w" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'size'"></div> <div><input type="number" name="x-end" aria-label="X end" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.x2" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'end'"></div> <div>Y</div> <div><input type="number" name="y-start" aria-label="Y start" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.y" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'start'"></div> <div><input type="number" name="y-size" aria-label="Y size" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.h" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'size'"></div> <div><input type="number" name="y-end" aria-label="Y end" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.y2" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'end'"></div> <div></div> <div><label><input type="radio" name="lock-start" value="start" v-model="exportParams.lockedDim">🔒</label></div> <div><label><input type="radio" name="lock-size" value="size" v-model="exportParams.lockedDim">🔒</label> </div> <div><label><input type="radio" name="lock-end" value="end" v-model="exportParams.lockedDim">🔒</label> </div> </div> <span>Reset</span> <button v-icon="zoom-fit" @click="fn.setExportParams()"></button> <span>To view</span> Loading src/app.ts +52 −14 Changes for src/app.ts: 52 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -211,6 +211,7 @@ export class App { this.onExportDialogClose, this.downloadExportImage, this.setExportParams, this.onExportParamInput, ] as Array<(..._: any[]) => any> ) .map((m) => [m.name, m.bind(this)] as const) Loading @@ -227,7 +228,6 @@ export class App { cursor: null as string | null, datasetsDisabled: false, dataset: '', modKeys: '', requestFullscreen: null as (() => void) | null, t: (k: string) => this.i18n.t(k), loc: this.getLocString.bind(this), Loading @@ -236,6 +236,7 @@ export class App { description: null as LocString | null, output: null as string | null, exportParams: App.DEFAULT_EXPORT_PARAMS, totalSize: [0, 0] as [number, number], pointerState: this.state.pointerState, raw: () => rx, // Function to un-proxify the rx object }; Loading @@ -248,6 +249,7 @@ export class App { dezoom: 0, outerGrab: new Rect(0, 0, 0, 0), innerGrab: new Rect(0, 0, 0, 0), lockedDim: 'end', }; } Loading Loading @@ -370,6 +372,7 @@ export class App { this.rx.phase = 'render'; this.requestRender(); this.rx.description = value.dataset.description || null; this.rx.totalSize = value.totalSize; } else this.rx.description = null; } Loading Loading @@ -700,23 +703,58 @@ export class App { let area = Rect.fromCorners(vec2.floor(tr, tr), vec2.ceil(bl, bl)); area = area.intersection(canvas)!; let dezoom = Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))); this.rx.exportParams = { area, dezoom, outerGrab: area, innerGrab: area, }; this.rx.exportParams.area = area; this.rx.exportParams.dezoom = dezoom; } else if (from === 'full') { this.rx.exportParams = { area: canvas, dezoom: 0, outerGrab: canvas, innerGrab: canvas, }; this.rx.exportParams.area = canvas; this.rx.exportParams.dezoom = 0; } else throw new Error(`Unknown argument from = ${from}`); this.requestRender(); } private onExportParamInput(ev: InputEvent) { if (this.rx.phase !== 'export') return; if (!(ev.currentTarget instanceof HTMLInputElement)) throw new Error(); const val = ev.currentTarget.valueAsNumber; const area = this.rx.raw().exportParams.area, parts = ev.currentTarget.name.split('-'), ax = parts[0] === 'x' ? 0 : 1, start = area.xy, end = [area.x2, area.y2] as [number, number], total = this.rx.raw().totalSize, DIMS = ['start', 'size', 'end'], dim = DIMS.indexOf(parts[1]), lockDim = DIMS.indexOf(this.rx.exportParams.lockedDim); if (dim === -1 || lockDim === -1 || dim === lockDim) throw new Error(); if (lockDim === 1) { // Computing size const size = area.wh[ax]; if (dim === 0) { // Input start end[ax] = clamp(val + size, size, total[ax]); start[ax] = end[ax] - size; } else { // Input end start[ax] = clamp(val - size, 0, total[ax] - size); end[ax] = start[ax] + size; } } else if (dim === 1) { // Input size if (lockDim === 0) { // Computing end end[ax] = clamp(start[ax] + val, start[ax] + 1, total[ax]); } else { // Computing start start[ax] = clamp(end[ax] - val, 0, end[ax] - 1); } } else { if (dim === 0) { // Input start start[ax] = clamp(val, 0, total[ax] - 1); end[ax] = clamp(end[ax], start[ax] + 1, total[ax]); } else { // Input end end[ax] = clamp(val, 1, total[ax]); start[ax] = clamp(start[ax], 0, end[ax] - 1); } } const newArea = Rect.fromCorners(start, end); this.rx.exportParams.area = newArea; this.requestRender(); } private exportImage() { const source = this.source; if (!source || !this.currentDataset || !this.maxCanvasSize) return; Loading Loading @@ -910,7 +948,7 @@ export class App { let lightball = pointerState.dragging ? pointerState.overLightball : (ev.target as Element).id === 'ivoire-lightball'; : (ev.target as Element)?.id === 'ivoire-lightball'; const canvas = (lightball ? this.gls.lightball : this.gls.relight).canvas; const bb = canvas.getBoundingClientRect(); const evpos = [ev.clientX - bb.x, ev.clientY - bb.y] as vec2; Loading src/style.sass +16 −3 Changes for src/style.sass: 16 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -142,10 +142,23 @@ body top: 0 .export-panel .export-area-matrix width: 100% text-align: center display: grid grid-template-columns: auto 1fr grid-column-gap: 1rem justify-items: start grid-template-columns: auto 1fr 1fr 1fr grid-column-gap: .25rem align-items: baseline input[type=number] width: 100% margin: .25rem box-sizing: border-box appearance: textfield &:disabled background: silver color: black .drop-shadow Loading Loading
index.html +33 −14 Changes for index.html: 33 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -96,20 +96,39 @@ <label for="button-reset-controls" v-t="controls.reset"></label> </span> <div class="export-panel" v-show="phase === 'export'"> <span>X: </span> <span>{{ Math.round(exportParams.area.x) }}</span> <span>Y: </span> <span>{{ Math.round(exportParams.area.y) }}</span> <span>Width: </span> <span>{{ Math.round(exportParams.area.w) }}</span> <span>Height: </span> <span>{{ Math.round(exportParams.area.h) }}</span> <span>Inner: </span> <span>{{ `${exportParams.innerGrab.round()}` }}</span> <span>Outer: </span> <span>{{ `${exportParams.outerGrab.round()}` }}</span> <span>Keys:</span> <span>{{ modKeys }}</span> <div class="export-area-matrix"> <div></div> <div>Start</div> <div>Size</div> <div>End</div> <div>X</div> <div><input type="number" name="x-start" aria-label="X start" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.x" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'start'"></div> <div><input type="number" name="x-size" aria-label="X size" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.w" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'size'"></div> <div><input type="number" name="x-end" aria-label="X end" min="0" :max="totalSize[0]" step="1" :value="exportParams.area.x2" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'end'"></div> <div>Y</div> <div><input type="number" name="y-start" aria-label="Y start" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.y" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'start'"></div> <div><input type="number" name="y-size" aria-label="Y size" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.h" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'size'"></div> <div><input type="number" name="y-end" aria-label="Y end" min="0" :max="totalSize[1]" step="1" :value="exportParams.area.y2" @change="fn.onExportParamInput" :disabled="exportParams.lockedDim === 'end'"></div> <div></div> <div><label><input type="radio" name="lock-start" value="start" v-model="exportParams.lockedDim">🔒</label></div> <div><label><input type="radio" name="lock-size" value="size" v-model="exportParams.lockedDim">🔒</label> </div> <div><label><input type="radio" name="lock-end" value="end" v-model="exportParams.lockedDim">🔒</label> </div> </div> <span>Reset</span> <button v-icon="zoom-fit" @click="fn.setExportParams()"></button> <span>To view</span> Loading
src/app.ts +52 −14 Changes for src/app.ts: 52 added lines, 14 removed lines. Original line number Diff line number Diff line Loading @@ -211,6 +211,7 @@ export class App { this.onExportDialogClose, this.downloadExportImage, this.setExportParams, this.onExportParamInput, ] as Array<(..._: any[]) => any> ) .map((m) => [m.name, m.bind(this)] as const) Loading @@ -227,7 +228,6 @@ export class App { cursor: null as string | null, datasetsDisabled: false, dataset: '', modKeys: '', requestFullscreen: null as (() => void) | null, t: (k: string) => this.i18n.t(k), loc: this.getLocString.bind(this), Loading @@ -236,6 +236,7 @@ export class App { description: null as LocString | null, output: null as string | null, exportParams: App.DEFAULT_EXPORT_PARAMS, totalSize: [0, 0] as [number, number], pointerState: this.state.pointerState, raw: () => rx, // Function to un-proxify the rx object }; Loading @@ -248,6 +249,7 @@ export class App { dezoom: 0, outerGrab: new Rect(0, 0, 0, 0), innerGrab: new Rect(0, 0, 0, 0), lockedDim: 'end', }; } Loading Loading @@ -370,6 +372,7 @@ export class App { this.rx.phase = 'render'; this.requestRender(); this.rx.description = value.dataset.description || null; this.rx.totalSize = value.totalSize; } else this.rx.description = null; } Loading Loading @@ -700,23 +703,58 @@ export class App { let area = Rect.fromCorners(vec2.floor(tr, tr), vec2.ceil(bl, bl)); area = area.intersection(canvas)!; let dezoom = Math.max(-source.zoomDepth, Math.min(0, Math.ceil(Math.log2(this.state.viewport.zoom)))); this.rx.exportParams = { area, dezoom, outerGrab: area, innerGrab: area, }; this.rx.exportParams.area = area; this.rx.exportParams.dezoom = dezoom; } else if (from === 'full') { this.rx.exportParams = { area: canvas, dezoom: 0, outerGrab: canvas, innerGrab: canvas, }; this.rx.exportParams.area = canvas; this.rx.exportParams.dezoom = 0; } else throw new Error(`Unknown argument from = ${from}`); this.requestRender(); } private onExportParamInput(ev: InputEvent) { if (this.rx.phase !== 'export') return; if (!(ev.currentTarget instanceof HTMLInputElement)) throw new Error(); const val = ev.currentTarget.valueAsNumber; const area = this.rx.raw().exportParams.area, parts = ev.currentTarget.name.split('-'), ax = parts[0] === 'x' ? 0 : 1, start = area.xy, end = [area.x2, area.y2] as [number, number], total = this.rx.raw().totalSize, DIMS = ['start', 'size', 'end'], dim = DIMS.indexOf(parts[1]), lockDim = DIMS.indexOf(this.rx.exportParams.lockedDim); if (dim === -1 || lockDim === -1 || dim === lockDim) throw new Error(); if (lockDim === 1) { // Computing size const size = area.wh[ax]; if (dim === 0) { // Input start end[ax] = clamp(val + size, size, total[ax]); start[ax] = end[ax] - size; } else { // Input end start[ax] = clamp(val - size, 0, total[ax] - size); end[ax] = start[ax] + size; } } else if (dim === 1) { // Input size if (lockDim === 0) { // Computing end end[ax] = clamp(start[ax] + val, start[ax] + 1, total[ax]); } else { // Computing start start[ax] = clamp(end[ax] - val, 0, end[ax] - 1); } } else { if (dim === 0) { // Input start start[ax] = clamp(val, 0, total[ax] - 1); end[ax] = clamp(end[ax], start[ax] + 1, total[ax]); } else { // Input end end[ax] = clamp(val, 1, total[ax]); start[ax] = clamp(start[ax], 0, end[ax] - 1); } } const newArea = Rect.fromCorners(start, end); this.rx.exportParams.area = newArea; this.requestRender(); } private exportImage() { const source = this.source; if (!source || !this.currentDataset || !this.maxCanvasSize) return; Loading Loading @@ -910,7 +948,7 @@ export class App { let lightball = pointerState.dragging ? pointerState.overLightball : (ev.target as Element).id === 'ivoire-lightball'; : (ev.target as Element)?.id === 'ivoire-lightball'; const canvas = (lightball ? this.gls.lightball : this.gls.relight).canvas; const bb = canvas.getBoundingClientRect(); const evpos = [ev.clientX - bb.x, ev.clientY - bb.y] as vec2; Loading
src/style.sass +16 −3 Changes for src/style.sass: 16 added lines, 3 removed lines. Original line number Diff line number Diff line Loading @@ -142,10 +142,23 @@ body top: 0 .export-panel .export-area-matrix width: 100% text-align: center display: grid grid-template-columns: auto 1fr grid-column-gap: 1rem justify-items: start grid-template-columns: auto 1fr 1fr 1fr grid-column-gap: .25rem align-items: baseline input[type=number] width: 100% margin: .25rem box-sizing: border-box appearance: textfield &:disabled background: silver color: black .drop-shadow Loading