UNPKG

ducjs

Version:

The duc 2D CAD file format is a cornerstone of our advanced design system, conceived to cater to professionals seeking precision and efficiency in their design work.

269 lines (268 loc) 19.6 kB
var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; import { BLOCK_ATTACHMENT, COLUMN_TYPE, DATUM_BRACKET_STYLE, IMAGE_STATUS, LINE_SPACING_TYPE, PARAMETRIC_SOURCE_TYPE, STACKED_TEXT_ALIGN, TEXT_FLOW_DIRECTION, VERTICAL_ALIGN, VIEWPORT_SHADE_PLOT, } from "../../flatbuffers/duc"; import { getUpdatedTimestamp, getZoom } from ".."; import { DEFAULT_ELEMENT_PROPS, DEFAULT_ELLIPSE_ELEMENT, DEFAULT_FONT_FAMILY, DEFAULT_FONT_SIZE, DEFAULT_FREEDRAW_ELEMENT, DEFAULT_POLYGON_SIDES, DEFAULT_TEXT_ALIGN, DEFAULT_VERTICAL_ALIGN, } from "../constants"; import { getDefaultStackProperties, getDefaultTableData, getDefaultTextStyle } from "./"; import { getFontString, getTextElementPositionOffsets, measureText, } from "./textElement"; import { randomId, randomInteger } from "../math/random"; import { normalizeText } from "../normalize"; import { getPrecisionValueFromRaw } from "../../technical/scopes"; export const newElementWith = (element, updates, /** pass `true` to always regenerate */ force = false) => { let didChange = false; for (const key in updates) { const value = updates[key]; if (typeof value !== "undefined") { if (element[key] === value && // if object, always update because its attrs could have changed (typeof value !== "object" || value === null)) { continue; } didChange = true; } } if (!didChange && !force) { return element; } return Object.assign(Object.assign(Object.assign({}, element), updates), { updated: getUpdatedTimestamp(), version: element.version + 1, versionNonce: randomInteger() }); }; const _newElementBase = (type, currentScope, _a) => { var _b, _c; var { x, y, scope = currentScope, zIndex, index = DEFAULT_ELEMENT_PROPS.index, label, isVisible = DEFAULT_ELEMENT_PROPS.isVisible, isPlot = DEFAULT_ELEMENT_PROPS.isPlot, isAnnotative = DEFAULT_ELEMENT_PROPS.isAnnotative, stroke = [DEFAULT_ELEMENT_PROPS.stroke], background = [DEFAULT_ELEMENT_PROPS.background], opacity = DEFAULT_ELEMENT_PROPS.opacity, width = DEFAULT_ELEMENT_PROPS.width, height = DEFAULT_ELEMENT_PROPS.height, angle = DEFAULT_ELEMENT_PROPS.angle, groupIds = DEFAULT_ELEMENT_PROPS.groupIds, regionIds = [], frameId = DEFAULT_ELEMENT_PROPS.frameId, layerId = null, roundness = DEFAULT_ELEMENT_PROPS.roundness, boundElements = DEFAULT_ELEMENT_PROPS.boundElements, link = DEFAULT_ELEMENT_PROPS.link, locked = DEFAULT_ELEMENT_PROPS.locked, description = null } = _a, rest = __rest(_a, ["x", "y", "scope", "zIndex", "index", "label", "isVisible", "isPlot", "isAnnotative", "stroke", "background", "opacity", "width", "height", "angle", "groupIds", "regionIds", "frameId", "layerId", "roundness", "boundElements", "link", "locked", "description"]); // assign type to guard against excess properties const element = { id: rest.id || randomId(), type, x, y, width, height, index, isVisible, angle, stroke, background, opacity, groupIds, frameId, roundness, label, scope, seed: (_b = rest.seed) !== null && _b !== void 0 ? _b : randomInteger(), version: rest.version || 1, versionNonce: (_c = rest.versionNonce) !== null && _c !== void 0 ? _c : 0, isDeleted: false, boundElements, updated: getUpdatedTimestamp(), link, locked, zIndex, description, customData: rest.customData, isPlot, isAnnotative, regionIds, layerId, }; return element; }; export const newElement = (currentScope, opts) => _newElementBase(opts.type, currentScope, opts); export const newEmbeddableElement = (currentScope, opts) => { return Object.assign(Object.assign({}, opts), _newElementBase(opts.type, currentScope, opts)); }; export const newFrameElement = (currentScope, opts) => (Object.assign(Object.assign(Object.assign({}, _newElementBase("frame", currentScope, opts)), getDefaultStackProperties()), { type: "frame", clip: false, labelVisible: true, standardOverride: null })); export const newPlotElement = (currentScope, opts) => (Object.assign(Object.assign(Object.assign({}, _newElementBase("plot", currentScope, opts)), getDefaultStackProperties()), { type: "plot", clip: false, labelVisible: true, standardOverride: null, layout: { margins: { top: getPrecisionValueFromRaw(25, currentScope, currentScope), right: getPrecisionValueFromRaw(25, currentScope, currentScope), bottom: getPrecisionValueFromRaw(25, currentScope, currentScope), left: getPrecisionValueFromRaw(25, currentScope, currentScope), } } })); export const newViewportElement = (currentScope, opts) => { var _a, _b, _c; return (Object.assign(Object.assign(Object.assign({}, _newElementBase("viewport", currentScope, opts)), getDefaultStackProperties()), { type: "viewport", points: [], lines: [], pathOverrides: [], lastCommittedPoint: null, startBinding: null, endBinding: null, standardOverride: null, view: { scrollX: getPrecisionValueFromRaw(0, currentScope, currentScope), scrollY: getPrecisionValueFromRaw(0, currentScope, currentScope), zoom: getZoom((_a = opts.zoom) !== null && _a !== void 0 ? _a : 1, (_b = opts.mainScope) !== null && _b !== void 0 ? _b : currentScope, (_c = opts.scopeExponentThreshold) !== null && _c !== void 0 ? _c : 2), twistAngle: 0, centerPoint: { x: getPrecisionValueFromRaw(0, currentScope, currentScope), y: getPrecisionValueFromRaw(0, currentScope, currentScope), }, scope: currentScope, }, scale: 1, shadePlot: VIEWPORT_SHADE_PLOT.AS_DISPLAYED, frozenGroupIds: [], scaleIndicatorVisible: true })); }; export const newEllipseElement = (currentScope, opts) => { return Object.assign(Object.assign({}, _newElementBase(opts.type, currentScope, opts)), { ratio: opts.ratio || DEFAULT_ELLIPSE_ELEMENT.ratio, startAngle: opts.startAngle || DEFAULT_ELLIPSE_ELEMENT.startAngle, endAngle: opts.endAngle || DEFAULT_ELLIPSE_ELEMENT.endAngle, showAuxCrosshair: opts.showAuxCrosshair || DEFAULT_ELLIPSE_ELEMENT.showAuxCrosshair }); }; export const newPolygonElement = (currentScope, opts) => { return Object.assign(Object.assign({}, _newElementBase(opts.type, currentScope, opts)), { sides: opts.sides || DEFAULT_POLYGON_SIDES }); }; export const newTextElement = (currentScope, opts) => { var _a, _b, _c, _d, _e, _f; const scope = (_a = opts.scope) !== null && _a !== void 0 ? _a : currentScope; const fontFamily = opts.fontFamily || DEFAULT_FONT_FAMILY; const fontSize = opts.fontSize || getPrecisionValueFromRaw(DEFAULT_FONT_SIZE, scope, currentScope); const lineHeight = opts.lineHeight || 1.2; const text = normalizeText(opts.text); const metrics = measureText(text, getFontString({ fontFamily, fontSize }), lineHeight, currentScope); const textAlign = opts.textAlign || DEFAULT_TEXT_ALIGN; const verticalAlign = opts.verticalAlign || DEFAULT_VERTICAL_ALIGN; const offsets = getTextElementPositionOffsets({ textAlign, verticalAlign }, metrics); const x = getPrecisionValueFromRaw(opts.x.value - offsets.x, scope, currentScope); const y = getPrecisionValueFromRaw(opts.y.value - offsets.y, scope, currentScope); return Object.assign(Object.assign({}, _newElementBase("text", currentScope, Object.assign(Object.assign({}, opts), { x, y }))), { type: "text", text, fontSize, fontFamily, textAlign, verticalAlign, width: getPrecisionValueFromRaw(metrics.width, scope, currentScope), height: getPrecisionValueFromRaw(metrics.height, scope, currentScope), containerId: opts.containerId || null, originalText: (_b = opts.originalText) !== null && _b !== void 0 ? _b : text, autoResize: (_c = opts.autoResize) !== null && _c !== void 0 ? _c : true, lineHeight, // DucTextStyle properties isLtr: (_d = opts.isLtr) !== null && _d !== void 0 ? _d : true, bigFontFamily: opts.bigFontFamily || "sans-serif", lineSpacing: opts.lineSpacing || { type: LINE_SPACING_TYPE.MULTIPLE, value: lineHeight }, obliqueAngle: opts.obliqueAngle || 0, paperTextHeight: opts.paperTextHeight, widthFactor: opts.widthFactor || 1, isUpsideDown: (_e = opts.isUpsideDown) !== null && _e !== void 0 ? _e : false, isBackwards: (_f = opts.isBackwards) !== null && _f !== void 0 ? _f : false, dynamic: opts.dynamic || [] }); }; export const newFreeDrawElement = (currentScope, opts) => { var _a, _b, _c, _d; const scope = (_a = opts.scope) !== null && _a !== void 0 ? _a : currentScope; return Object.assign(Object.assign({}, _newElementBase("freedraw", currentScope, opts)), { type: "freedraw", points: opts.points || [], size: opts.size || getPrecisionValueFromRaw(DEFAULT_FREEDRAW_ELEMENT.size, scope, currentScope), pressures: opts.pressures || [], simulatePressure: opts.simulatePressure, thinning: (_b = opts.thinning) !== null && _b !== void 0 ? _b : DEFAULT_FREEDRAW_ELEMENT.thinning, smoothing: (_c = opts.smoothing) !== null && _c !== void 0 ? _c : DEFAULT_FREEDRAW_ELEMENT.smoothing, streamline: (_d = opts.streamline) !== null && _d !== void 0 ? _d : DEFAULT_FREEDRAW_ELEMENT.streamline, easing: opts.easing || DEFAULT_FREEDRAW_ELEMENT.easing, lastCommittedPoint: null, start: opts.start || null, end: opts.end || null, svgPath: null }); }; export const newLinearElement = (currentScope, opts) => { var _a; return (Object.assign(Object.assign({}, _newElementBase("line", currentScope, opts)), { type: "line", points: opts.points || [], lines: opts.lines || [], pathOverrides: opts.pathOverrides || [], lastCommittedPoint: null, startBinding: null, endBinding: null, wipeoutBelow: (_a = opts.wipeoutBelow) !== null && _a !== void 0 ? _a : false })); }; export const newArrowElement = (currentScope, opts) => { var _a; return (Object.assign(Object.assign({}, _newElementBase("arrow", currentScope, opts)), { type: "arrow", points: opts.points || [], lines: opts.lines || [], pathOverrides: opts.pathOverrides || [], lastCommittedPoint: null, startBinding: null, endBinding: null, elbowed: (_a = opts.elbowed) !== null && _a !== void 0 ? _a : true })); }; export const newImageElement = (currentScope, opts) => { var _a, _b, _c; return (Object.assign(Object.assign({}, _newElementBase("image", currentScope, opts)), { type: "image", status: (_a = opts.status) !== null && _a !== void 0 ? _a : IMAGE_STATUS.PENDING, fileId: (_b = opts.fileId) !== null && _b !== void 0 ? _b : null, scaleFlip: (_c = opts.scaleFlip) !== null && _c !== void 0 ? _c : [1, 1], crop: null, filter: null })); }; export const newTableElement = (currentScope, opts) => (Object.assign(Object.assign(Object.assign({}, _newElementBase("table", currentScope, opts)), getDefaultTableData(currentScope)), { type: "table" })); export const newDocElement = (currentScope, opts) => { var _a, _b, _c, _d; return (Object.assign(Object.assign({}, _newElementBase("doc", currentScope, opts)), { type: "doc", text: opts.text || "", dynamic: opts.dynamic || [], flowDirection: opts.flowDirection || TEXT_FLOW_DIRECTION.TOP_TO_BOTTOM, columns: opts.columns || { type: COLUMN_TYPE.NO_COLUMNS, definitions: [], autoHeight: true }, autoResize: (_a = opts.autoResize) !== null && _a !== void 0 ? _a : true, // DucDocStyle properties isLtr: (_b = opts.isLtr) !== null && _b !== void 0 ? _b : true, fontFamily: opts.fontFamily || DEFAULT_FONT_FAMILY, bigFontFamily: opts.bigFontFamily || "sans-serif", textAlign: opts.textAlign || DEFAULT_TEXT_ALIGN, verticalAlign: opts.verticalAlign || DEFAULT_VERTICAL_ALIGN, lineHeight: opts.lineHeight || 1.2, lineSpacing: opts.lineSpacing || { type: LINE_SPACING_TYPE.MULTIPLE, value: 1.2 }, obliqueAngle: opts.obliqueAngle || 0, fontSize: opts.fontSize || getPrecisionValueFromRaw(DEFAULT_FONT_SIZE, currentScope, currentScope), paperTextHeight: opts.paperTextHeight, widthFactor: opts.widthFactor || 1, isUpsideDown: (_c = opts.isUpsideDown) !== null && _c !== void 0 ? _c : false, isBackwards: (_d = opts.isBackwards) !== null && _d !== void 0 ? _d : false, paragraph: opts.paragraph || { firstLineIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), hangingIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), leftIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), rightIndent: getPrecisionValueFromRaw(0, currentScope, currentScope), spaceBefore: getPrecisionValueFromRaw(0, currentScope, currentScope), spaceAfter: getPrecisionValueFromRaw(0, currentScope, currentScope), tabStops: [] }, stackFormat: opts.stackFormat || { autoStack: false, stackChars: [], properties: { upperScale: 0.7, lowerScale: 0.7, alignment: STACKED_TEXT_ALIGN.CENTER } } })); }; export const newPdfElement = (currentScope, opts) => (Object.assign(Object.assign({}, _newElementBase("pdf", currentScope, opts)), { type: "pdf", fileId: null })); export const newMermaidElement = (currentScope, opts) => (Object.assign(Object.assign({}, _newElementBase("mermaid", currentScope, opts)), { type: "mermaid", source: "", theme: undefined, svgPath: null })); export const newXRayElement = (currentScope, opts) => (Object.assign(Object.assign({}, _newElementBase("xray", currentScope, opts)), { type: "xray", origin: { x: getPrecisionValueFromRaw(0, currentScope, currentScope), y: getPrecisionValueFromRaw(0, currentScope, currentScope) }, direction: { x: getPrecisionValueFromRaw(1, currentScope, currentScope), y: getPrecisionValueFromRaw(0, currentScope, currentScope) }, startFromOrigin: false, color: '#FF00FF' })); export const newLeaderElement = (currentScope, opts) => { var _a, _b; return Object.assign(Object.assign({}, _newElementBase("leader", currentScope, opts)), { type: "leader", points: [], lines: [], pathOverrides: [], lastCommittedPoint: null, startBinding: null, endBinding: null, headsOverride: undefined, dogleg: getPrecisionValueFromRaw(10, currentScope, currentScope), textStyle: opts.textStyle || getDefaultTextStyle(currentScope), textAttachment: opts.textAttachment || VERTICAL_ALIGN.TOP, blockAttachment: opts.blockAttachment || BLOCK_ATTACHMENT.CENTER_EXTENTS, leaderContent: (_a = opts.leaderContent) !== null && _a !== void 0 ? _a : null, contentAnchor: (_b = opts.contentAnchor) !== null && _b !== void 0 ? _b : { x: 0, y: 0, } }); }; export const newDimensionElement = (currentScope, opts) => (Object.assign(Object.assign({}, _newElementBase("dimension", currentScope, opts)), { type: 'dimension' })); export const newFeatureControlFrameElement = (currentScope, opts) => { return Object.assign(Object.assign({}, _newElementBase("featurecontrolframe", currentScope, opts)), { type: "featurecontrolframe", rows: [], leaderElementId: null, textStyle: getDefaultTextStyle(currentScope), layout: { padding: getPrecisionValueFromRaw(4, currentScope, currentScope), segmentSpacing: getPrecisionValueFromRaw(4, currentScope, currentScope), rowSpacing: getPrecisionValueFromRaw(2, currentScope, currentScope), }, symbols: { scale: 1, }, datumStyle: { bracketStyle: DATUM_BRACKET_STYLE.SQUARE } }); }; export const newBlockInstanceElement = (currentScope, opts) => { var _a, _b, _c; return (Object.assign(Object.assign({}, _newElementBase("blockinstance", currentScope, opts)), { type: "blockinstance", blockId: opts.blockId, elementOverrides: (_a = opts.elementOverrides) !== null && _a !== void 0 ? _a : {}, attributeValues: (_b = opts.attributeValues) !== null && _b !== void 0 ? _b : {}, duplicationArray: (_c = opts.duplicationArray) !== null && _c !== void 0 ? _c : null })); }; export const newParametricElement = (currentScope, opts) => (Object.assign(Object.assign({}, _newElementBase("parametric", currentScope, opts)), { type: 'parametric', source: { type: PARAMETRIC_SOURCE_TYPE.CODE, code: "" } })); // Simplified deep clone for the purpose of cloning DucElement. // // Only clones plain objects and arrays. Doesn't clone Date, RegExp, Map, Set, // Typed arrays and other non-null objects. // // Adapted from https://github.com/lukeed/klona // // The reason for `deepCopyElement()` wrapper is type safety (only allow // passing DucElement as the top-level argument). const _deepCopyElement = (val, depth = 0) => { // only clone non-primitives if (val == null || typeof val !== "object") { return val; } const objectType = Object.prototype.toString.call(val); if (objectType === "[object Object]") { const tmp = typeof val.constructor === "function" ? Object.create(Object.getPrototypeOf(val)) : {}; for (const key in val) { if (val.hasOwnProperty(key)) { // don't copy non-serializable objects like these caches. They'll be // populated when the element is rendered. if (depth === 0 && (key === "shape" || key === "canvas")) { continue; } tmp[key] = _deepCopyElement(val[key], depth + 1); } } return tmp; } if (Array.isArray(val)) { let k = val.length; const arr = new Array(k); while (k--) { arr[k] = _deepCopyElement(val[k], depth + 1); } return arr; } // we're not cloning non-array & non-plain-object objects because we // don't support them on excalidraw elements yet. If we do, we need to make // sure we start cloning them, so let's warn about it. if (import.meta.env.DEV) { if (objectType !== "[object Object]" && objectType !== "[object Array]" && objectType.startsWith("[object ")) { console.warn(`_deepCloneElement: unexpected object type ${objectType}. This value will not be cloned!`); } } return val; }; /** * Clones DucElement data structure. Does not regenerate id, nonce, or * any value. The purpose is to to break object references for immutability * reasons, whenever we want to keep the original element, but ensure it's not * mutated. * * Only clones plain objects and arrays. Doesn't clone Date, RegExp, Map, Set, * Typed arrays and other non-null objects. */ export const deepCopyElement = (val) => { return _deepCopyElement(val); }; /** * utility wrapper to generate new id. In test env it reuses the old + postfix * for test assertions. */ export const regenerateId = ( /** supply null if no previous id exists */ previousId) => { // if (isTestEnv() && previousId) { // let nextId = `${previousId}_copy`; // // `window.h` may not be defined in some unit tests // if ( // window.h?.app // ?.getSceneElementsIncludingDeleted() // .find((el: DucElement) => el.id === nextId) // ) { // nextId += "_copy"; // } // return nextId; // } return randomId(); };