Skip to content
Merged
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
1 change: 1 addition & 0 deletions open-pdf-studio/js/annotations/stavenreeks.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,7 @@ export function labelLayout(count, diameter, fontSize, measure = approxTextWidth
* @param {object} [opts]
* @param {(text:string,fontSize:number)=>number} [opts.measureText]
* Echte tekstbreedte-meter (canvas of PDF-fontmetriek). Default: schatting.
* @param {number} [opts.pxPerMm] Pagina-pixels per werkelijke millimeter.
* @returns {{
* params: object, frame: object, line: object,
* legs: Array, dots: Array, label: object,
Expand Down
11 changes: 11 additions & 0 deletions open-pdf-studio/js/bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ export {
stavenreeksInputActive,
} from './solid/stores/stavenreeksInputStore.js';

// ============= PARAMETRISCH LABEL INLINE INVOER =============
export {
showParametricLabelInput,
hideParametricLabelInput,
parametricLabelInputActive,
} from './solid/stores/parametricLabelInputStore.js';

export {
validateSymbolParams,
} from './solid/stores/parametricSymbolStore.js';

// ============= PDF TEXT EDITOR =============
export {
showPdfTextEditor,
Expand Down
1 change: 1 addition & 0 deletions open-pdf-studio/js/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ export const DEFAULT_PREFERENCES: Preferences = {
symbolPaletteFloatY: 150,
customSymbolGroups: [],
disabledSymbolGroups: [],
parametricSymbolDefaults: {},
// User-edited symbol type geometry overrides, keyed by a hash of the
// original symbol SVG. Each entry: { svg, name }. Applied whenever a stamp
// of that type is placed or re-rendered.
Expand Down
2 changes: 2 additions & 0 deletions open-pdf-studio/js/solid/components/DialogHost.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import TitleBlockDialog from './dialogs/TitleBlockDialog.jsx';
import CompareDialog from './compare/CompareDialog.jsx';
import TextEditOverlay from './TextEditOverlay.jsx';
import StavenreeksInlineEditor from './StavenreeksInlineEditor.jsx';
import ParametricLabelInlineEditor from './ParametricLabelInlineEditor.jsx';
import PdfTextEditOverlay from './PdfTextEditOverlay.jsx';
import StickyNotePopupHost from './StickyNotePopup.jsx';
import ParametricSymbolPicker from './dialogs/ParametricSymbolPicker.jsx';
Expand Down Expand Up @@ -98,6 +99,7 @@ export default function DialogHost() {
</For>
<TextEditOverlay />
<StavenreeksInlineEditor />
<ParametricLabelInlineEditor />
<PdfTextEditOverlay />
<StickyNotePopupHost />
<ParametricSymbolPicker />
Expand Down
137 changes: 137 additions & 0 deletions open-pdf-studio/js/solid/components/ParametricLabelInlineEditor.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { For, Show, createEffect, onCleanup } from 'solid-js';
import {
active, anchor, setAnchor, fields, values, setFieldValue,
onCommit, onCancel, locator, hideParametricLabelInput,
returnFocusTarget as requestedReturnFocusTarget,
} from '../stores/parametricLabelInputStore.js';
import { createOutsideCommitController } from './parametric-label-outside-events.js';
import {
captureParametricLabelReturnFocus,
restoreParametricLabelFocus,
} from './parametric-label-focus.js';

export default function ParametricLabelInlineEditor() {
let rootRef;
let firstInputRef;
let capturedReturnFocusTarget = null;

const scheduleFocusRestore = () => {
const target = capturedReturnFocusTarget;
if (!target) return;
capturedReturnFocusTarget = null;
queueMicrotask(() => restoreParametricLabelFocus(target));
};

const commit = () => {
if (!active()) return;
const callback = onCommit();
const nextValues = { ...values() };
hideParametricLabelInput();
if (callback) callback(nextValues);
scheduleFocusRestore();
};

const cancel = () => {
if (!active()) return;
const callback = onCancel();
hideParametricLabelInput();
if (callback) callback();
scheduleFocusRestore();
};

const handleKeyDown = (e) => {
e.stopPropagation();
if (e.key === 'Enter') {
e.preventDefault();
commit();
return;
}
if (e.key === 'Escape') {
e.preventDefault();
cancel();
}
};

createEffect(() => {
if (!active()) return;

capturedReturnFocusTarget = captureParametricLabelReturnFocus(
document,
requestedReturnFocusTarget(),
);
queueMicrotask(() => {
firstInputRef?.focus();
firstInputRef?.select();
});

const outsideController = createOutsideCommitController({
isActive: active,
commit,
isCanvasTarget: (target) =>
target instanceof Element
&& !!target.closest('#annotation-canvas, .annotation-canvas'),
});
const onOutsidePointerDown = (event) =>
outsideController.pointerDown(event, rootRef);
const onOutsideClick = (event) =>
outsideController.click(event, rootRef);
document.addEventListener('pointerdown', onOutsidePointerDown, true);
window.addEventListener('click', onOutsideClick);

let raf = 0;
const tick = () => {
if (!active()) return;
const locate = locator();
if (typeof locate === 'function') {
const pos = locate();
if (!pos) {
cancel();
return;
}
const current = anchor();
if (pos.left !== current.left || pos.top !== current.top) setAnchor(pos);
}
raf = requestAnimationFrame(tick);
};
raf = requestAnimationFrame(tick);

onCleanup(() => {
outsideController.reset();
document.removeEventListener('pointerdown', onOutsidePointerDown, true);
window.removeEventListener('click', onOutsideClick);
if (raf) cancelAnimationFrame(raf);
scheduleFocusRestore();
});
});

return (
<Show when={active()}>
<div
ref={rootRef}
class="parametric-label-inline-editor"
style={{
position: 'fixed',
left: `${anchor().left}px`,
top: `${anchor().top}px`,
'z-index': '1200',
}}
onKeyDown={handleKeyDown}
>
<For each={fields()}>{(field, index) => (
<label class="parametric-label-inline-field">
<span>{field.label}</span>
<input
ref={(element) => { if (index() === 0) firstInputRef = element; }}
type={field.type === 'number' ? 'number' : 'text'}
min={field.min}
max={field.max}
step={field.step}
value={values()[field.key] ?? ''}
onInput={(event) => setFieldValue(field.key, event.currentTarget.value)}
/>
</label>
)}</For>
</div>
</Show>
);
}
7 changes: 3 additions & 4 deletions open-pdf-studio/js/solid/components/SymbolPalette.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import { registerPaletteDock, unregisterPaletteDock } from '../stores/paletteOrder.js';
import { ifcCategoryForSymbol } from '../data/ifcCategoryMap.js';
import { SYMBOL_STAMP_DEFAULT_SIZE } from '../../annotations/stamp-defaults.js';
import { setPendingSymbolId } from '../stores/parametricSymbolStore.js';

const DOCK_SNAP = 60;

Expand Down Expand Up @@ -89,10 +90,8 @@ function selectSymbol(symbol) {
// click on the page goes through the parametricSymbol tool and the params
// stay editable in the properties panel.
if (symbol.parametricId) {
import('../stores/parametricSymbolStore.js').then(m => {
m.setPendingSymbolId(symbol.parametricId);
setTool('parametricSymbol');
});
setPendingSymbolId(symbol.parametricId);
setTool('parametricSymbol');
return;
}
// Generic tool entries: palette items that simply activate a drawing tool
Expand Down
46 changes: 46 additions & 0 deletions open-pdf-studio/js/solid/components/parametric-label-focus.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
function makeProgrammaticallyFocusable(target) {
if (
target
&& typeof target.hasAttribute === 'function'
&& typeof target.setAttribute === 'function'
&& !target.hasAttribute('tabindex')
) {
target.setAttribute('tabindex', '-1');
}
return target;
}

export function captureParametricLabelReturnFocus(
documentRef = document,
preferredCanvas = null,
) {
const activeElement = documentRef?.activeElement;
if (
activeElement
&& activeElement !== documentRef?.body
&& activeElement !== documentRef?.documentElement
&& typeof activeElement.focus === 'function'
) {
return activeElement;
}
const canvas = preferredCanvas?.isConnected === false
? null
: preferredCanvas;
return makeProgrammaticallyFocusable(
canvas || documentRef?.querySelector?.('#annotation-canvas, .annotation-canvas') || null,
);
}

export function restoreParametricLabelFocus(target) {
if (!target || target.isConnected === false || typeof target.focus !== 'function') {
return false;
}
try {
target.focus({ preventScroll: true });
} catch (_) {
target.focus();
}
const documentRef = target.ownerDocument
|| (typeof document !== 'undefined' ? document : null);
return !documentRef?.activeElement || documentRef.activeElement === target;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
export function createOutsideCommitController({
isActive,
commit,
isCanvasTarget,
}) {
let pendingOutsideClick = false;

const isInside = (target, root) =>
!!(root && target != null && root.contains(target));

return {
pointerDown(event, root) {
pendingOutsideClick = false;
if (isInside(event.target, root)) return;
if (isCanvasTarget(event.target)) {
if (isActive()) commit();
return;
}
pendingOutsideClick = true;
},

click(event, root) {
if (!pendingOutsideClick) return;
pendingOutsideClick = false;
if (isInside(event.target, root) || !isActive()) return;
commit();
},

reset() {
pendingOutsideClick = false;
},
};
}
42 changes: 42 additions & 0 deletions open-pdf-studio/js/solid/stores/parametricLabelInputStore.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createSignal } from 'solid-js';

const [active, setActive] = createSignal(false);
const [anchor, setAnchor] = createSignal({ left: 0, top: 0 });
const [fields, setFields] = createSignal([]);
const [values, setValues] = createSignal({});
const [onCommit, setOnCommit] = createSignal(null);
const [onCancel, setOnCancel] = createSignal(null);
const [locator, setLocator] = createSignal(null);
const [returnFocusTarget, setReturnFocusTarget] = createSignal(null);

export function showParametricLabelInput(options = {}) {
setAnchor(options.anchor || { left: 0, top: 0 });
setFields(Array.isArray(options.fields) ? options.fields : []);
setValues({ ...(options.values || {}) });
setOnCommit(() => (typeof options.commit === 'function' ? options.commit : null));
setOnCancel(() => (typeof options.cancel === 'function' ? options.cancel : null));
setLocator(() => (typeof options.locate === 'function' ? options.locate : null));
setReturnFocusTarget(options.returnFocusTarget || null);
setActive(true);
}

export function hideParametricLabelInput() {
setActive(false);
setLocator(() => null);
setOnCommit(() => null);
setOnCancel(() => null);
setReturnFocusTarget(null);
}

export function setFieldValue(key, value) {
setValues((current) => ({ ...current, [key]: value }));
}

export function parametricLabelInputActive() {
return active();
}

export {
active, anchor, setAnchor,
fields, values, onCommit, onCancel, locator, returnFocusTarget,
};
Loading
Loading