Merge branch 'main' into feature-27-round-arrow-angle
This commit is contained in:
commit
b9eed30518
11 changed files with 164 additions and 108 deletions
|
@ -1,9 +1,14 @@
|
|||
# tldraw
|
||||
|
||||
A tiny little drawing app by [steveruizok](https://twitter.com/steveruizok).
|
||||
A tiny little drawing app.
|
||||
|
||||
Visit [tldraw.com](https://tldraw.com/).
|
||||
|
||||
## Author
|
||||
|
||||
- [steveruizok](https://twitter.com/steveruizok)
|
||||
- ...and more!
|
||||
|
||||
## Support
|
||||
|
||||
To support this project (and gain access to the project while it is in development) you can [sponsor the author](https://github.com/sponsors/steveruizok) on GitHub. Thanks!
|
||||
|
|
|
@ -19,13 +19,13 @@
|
|||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "lint-staged",
|
||||
"pre-push": "yarn lint && yarn type-check"
|
||||
"pre-push": "yarn lint && yarn format && yarn type-check"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.@(ts|tsx)": [
|
||||
"yarn lint",
|
||||
"yarn format"
|
||||
"yarn format",
|
||||
"yarn lint"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -95,4 +95,4 @@
|
|||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
||||
}
|
||||
}
|
|
@ -14,6 +14,7 @@ import {
|
|||
clampToRotationToSegments,
|
||||
lerpAngles,
|
||||
clamp,
|
||||
getFromCache,
|
||||
} from 'utils'
|
||||
import {
|
||||
ArrowShape,
|
||||
|
@ -31,6 +32,8 @@ import getStroke from 'perfect-freehand'
|
|||
import React from 'react'
|
||||
import { registerShapeUtils } from './register'
|
||||
|
||||
const pathCache = new WeakMap<ArrowShape['handles'], string>([])
|
||||
|
||||
// A cache for semi-expensive circles calculated from three points
|
||||
function getCtp(shape: ArrowShape) {
|
||||
const { start, end, bend } = shape.handles
|
||||
|
@ -126,7 +129,9 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
const sw = strokeWidth * (isDraw ? 0.618 : 1.618)
|
||||
|
||||
const path = isDraw
|
||||
? renderFreehandArrowShaft(shape)
|
||||
? getFromCache(pathCache, shape.handles, (cache) =>
|
||||
cache.set(shape.handles, renderFreehandArrowShaft(shape))
|
||||
)
|
||||
: 'M' + vec.round(start.point) + 'L' + vec.round(end.point)
|
||||
|
||||
const { strokeDasharray, strokeDashoffset } = getPerfectDashProps(
|
||||
|
@ -168,7 +173,12 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
const sw = strokeWidth * (isDraw ? 0.618 : 1.618)
|
||||
|
||||
const path = isDraw
|
||||
? renderCurvedFreehandArrowShaft(shape, circle)
|
||||
? getFromCache(pathCache, shape.handles, (cache) =>
|
||||
cache.set(
|
||||
shape.handles,
|
||||
renderCurvedFreehandArrowShaft(shape, circle)
|
||||
)
|
||||
)
|
||||
: getArrowArcPath(start, end, circle, bend)
|
||||
|
||||
const arcLength = getArcLength(
|
||||
|
@ -277,15 +287,15 @@ const arrow = registerShapeUtils<ArrowShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
const { start, bend, end } = shape.handles
|
||||
this.boundsCache.set(
|
||||
cache.set(
|
||||
shape,
|
||||
getBoundsFromPoints([start.point, bend.point, end.point])
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
|
@ -597,8 +607,8 @@ function renderCurvedFreehandArrowShaft(shape: ArrowShape, circle: number[]) {
|
|||
size: strokeWidth / 2,
|
||||
thinning: 0.5 + getRandom() * 0.3,
|
||||
easing: (t) => t * t,
|
||||
end: { taper: 0 },
|
||||
start: { taper: 1 + 32 * (st * st * st) },
|
||||
end: { taper: shape.decorations.end ? 1 : 1 + 32 * (st * st * st) },
|
||||
start: { taper: shape.decorations.start ? 1 : 1 + 32 * (st * st * st) },
|
||||
simulatePressure: true,
|
||||
streamline: 0.01,
|
||||
last: true,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import { getFromCache, uniqueId } from 'utils/utils'
|
||||
import { DotShape, ShapeType } from 'types'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
import { boundsContained, translateBounds } from 'utils'
|
||||
|
@ -30,20 +30,18 @@ const dot = registerShapeUtils<DotShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = {
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
cache.set(shape, {
|
||||
minX: 0,
|
||||
maxX: 1,
|
||||
minY: 0,
|
||||
maxY: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.boundsCache.set(shape, bounds)
|
||||
}
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import { getFromCache, uniqueId } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import { DashStyle, DrawShape, ShapeType } from 'types'
|
||||
import { intersectPolylineBounds } from 'utils/intersections'
|
||||
|
@ -69,26 +69,26 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
// For drawn lines, draw a line from the path cache
|
||||
|
||||
if (shape.style.dash === DashStyle.Draw) {
|
||||
if (!drawPathCache.has(points)) {
|
||||
drawPathCache.set(shape.points, getDrawStrokePath(shape))
|
||||
}
|
||||
const polygonPathData = getFromCache(polygonCache, points, (cache) => {
|
||||
cache.set(shape.points, getFillPath(shape))
|
||||
})
|
||||
|
||||
if (shouldFill && !polygonCache.has(points)) {
|
||||
polygonCache.set(shape.points, getFillPath(shape))
|
||||
}
|
||||
const drawPathData = getFromCache(drawPathCache, points, (cache) => {
|
||||
cache.set(shape.points, getDrawStrokePath(shape))
|
||||
})
|
||||
|
||||
return (
|
||||
<g id={id}>
|
||||
{shouldFill && (
|
||||
<path
|
||||
d={polygonCache.get(points)}
|
||||
d={polygonPathData}
|
||||
strokeWidth="0"
|
||||
stroke="none"
|
||||
fill={styles.fill}
|
||||
/>
|
||||
)}
|
||||
<path
|
||||
d={drawPathCache.get(points)}
|
||||
d={drawPathData}
|
||||
fill={styles.stroke}
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={strokeWidth}
|
||||
|
@ -100,15 +100,17 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
// For solid, dash and dotted lines, draw a regular stroke path
|
||||
|
||||
const strokeDasharray = {
|
||||
[DashStyle.Draw]: 'none',
|
||||
[DashStyle.Solid]: `none`,
|
||||
[DashStyle.Dotted]: `${strokeWidth / 10} ${strokeWidth * 3}`,
|
||||
[DashStyle.Dashed]: `${strokeWidth * 3} ${strokeWidth * 3}`,
|
||||
[DashStyle.Solid]: `none`,
|
||||
}[style.dash]
|
||||
|
||||
const strokeDashoffset = {
|
||||
[DashStyle.Draw]: 'none',
|
||||
[DashStyle.Solid]: `none`,
|
||||
[DashStyle.Dotted]: `-${strokeWidth / 20}`,
|
||||
[DashStyle.Dashed]: `-${strokeWidth}`,
|
||||
[DashStyle.Solid]: `none`,
|
||||
}[style.dash]
|
||||
|
||||
if (!simplePathCache.has(points)) {
|
||||
|
@ -140,12 +142,11 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = getBoundsFromPoints(shape.points)
|
||||
this.boundsCache.set(shape, bounds)
|
||||
}
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
cache.set(shape, getBoundsFromPoints(shape.points))
|
||||
})
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
|
@ -183,25 +184,32 @@ const draw = registerShapeUtils<DrawShape>({
|
|||
// Test rotated shape
|
||||
const rBounds = this.getRotatedBounds(shape)
|
||||
|
||||
if (!rotatedCache.has(shape)) {
|
||||
const rotatedBounds = getFromCache(rotatedCache, shape, (cache) => {
|
||||
const c = getBoundsCenter(getBoundsFromPoints(shape.points))
|
||||
rotatedCache.set(
|
||||
cache.set(
|
||||
shape,
|
||||
shape.points.map((pt) => vec.rotWith(pt, c, shape.rotation))
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
boundsContain(brushBounds, rBounds) ||
|
||||
intersectPolylineBounds(
|
||||
rotatedCache.get(shape),
|
||||
rotatedBounds,
|
||||
translateBounds(brushBounds, vec.neg(shape.point))
|
||||
).length > 0
|
||||
)
|
||||
},
|
||||
|
||||
transform(shape, bounds, { initialShape, scaleX, scaleY }) {
|
||||
const initialShapeBounds = this.boundsCache.get(initialShape)
|
||||
const initialShapeBounds = getFromCache(
|
||||
this.boundsCache,
|
||||
initialShape,
|
||||
(cache) => {
|
||||
cache.set(shape, getBoundsFromPoints(initialShape.points))
|
||||
}
|
||||
)
|
||||
|
||||
shape.points = initialShape.points.map(([x, y, r]) => {
|
||||
return [
|
||||
bounds.width *
|
||||
|
@ -314,15 +322,6 @@ function getDrawStrokePath(shape: DrawShape) {
|
|||
return getSvgPathFromStroke(stroke)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path data for a solid draw stroke.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
*```ts
|
||||
* getSolidStrokePath(shape)
|
||||
*```
|
||||
*/
|
||||
function getSolidStrokePath(shape: DrawShape) {
|
||||
let { points } = shape
|
||||
|
||||
|
@ -331,13 +330,7 @@ function getSolidStrokePath(shape: DrawShape) {
|
|||
if (len === 0) return 'M 0 0 L 0 0'
|
||||
if (len < 3) return `M ${points[0][0]} ${points[0][1]}`
|
||||
|
||||
// Remove duplicates from points
|
||||
points = points.reduce<number[][]>((acc, pt, i) => {
|
||||
if (i === 0 || !vec.isEqual(pt, acc[i - 1])) {
|
||||
acc.push(pt)
|
||||
}
|
||||
return acc
|
||||
}, [])
|
||||
points = getStrokePoints(points).map((pt) => pt.point)
|
||||
|
||||
len = points.length
|
||||
|
||||
|
@ -364,3 +357,33 @@ function getSolidStrokePath(shape: DrawShape) {
|
|||
|
||||
return path
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get the path data for a solid draw stroke.
|
||||
// *
|
||||
// * ### Example
|
||||
// *
|
||||
// *```ts
|
||||
// * getSolidStrokePath(shape)
|
||||
// *```
|
||||
// */
|
||||
// function getSolidDrawStrokePath(shape: DrawShape) {
|
||||
// const styles = getShapeStyle(shape.style)
|
||||
|
||||
// if (shape.points.length < 2) {
|
||||
// return ''
|
||||
// }
|
||||
|
||||
// const options =
|
||||
// shape.points[1][2] === 0.5 ? simulatePressureSettings : realPressureSettings
|
||||
|
||||
// const stroke = getStroke(shape.points, {
|
||||
// size: 1 + +styles.strokeWidth * 2,
|
||||
// thinning: 0,
|
||||
// end: { taper: +styles.strokeWidth * 10 },
|
||||
// start: { taper: +styles.strokeWidth * 10 },
|
||||
// ...options,
|
||||
// })
|
||||
|
||||
// return getSvgPathFromStroke(stroke)
|
||||
// }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import { getFromCache, uniqueId } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import { LineShape, ShapeType } from 'types'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
|
@ -43,20 +43,18 @@ const line = registerShapeUtils<LineShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = {
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
cache.set(shape, {
|
||||
minX: 0,
|
||||
maxX: 1,
|
||||
minY: 0,
|
||||
maxY: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.boundsCache.set(shape, bounds)
|
||||
}
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import { getFromCache, uniqueId } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import { PolylineShape, ShapeType } from 'types'
|
||||
import { intersectPolylineBounds } from 'utils/intersections'
|
||||
|
@ -45,11 +45,11 @@ const polyline = registerShapeUtils<PolylineShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
this.boundsCache.set(shape, getBoundsFromPoints(shape.points))
|
||||
}
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
cache.set(shape, getBoundsFromPoints(shape.points))
|
||||
})
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getRotatedBounds(shape) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId } from 'utils/utils'
|
||||
import { getFromCache, uniqueId } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import { RayShape, ShapeType } from 'types'
|
||||
import { intersectCircleBounds } from 'utils/intersections'
|
||||
|
@ -46,20 +46,18 @@ const ray = registerShapeUtils<RayShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = {
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
cache.set(shape, {
|
||||
minX: 0,
|
||||
maxX: 1,
|
||||
minY: 0,
|
||||
maxY: 1,
|
||||
width: 1,
|
||||
height: 1,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.boundsCache.set(shape, bounds)
|
||||
}
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
getCenter(shape) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId, getPerfectDashProps } from 'utils/utils'
|
||||
import { uniqueId, getPerfectDashProps, getFromCache } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import { DashStyle, RectangleShape, ShapeType } from 'types'
|
||||
import { getSvgPathFromStroke, translateBounds, rng, shuffleArr } from 'utils'
|
||||
|
@ -34,11 +34,9 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
const strokeWidth = +styles.strokeWidth
|
||||
|
||||
if (style.dash === DashStyle.Draw) {
|
||||
if (!pathCache.has(shape.size)) {
|
||||
renderPath(shape)
|
||||
}
|
||||
|
||||
const path = pathCache.get(shape.size)
|
||||
const pathData = getFromCache(pathCache, shape.size, (cache) => {
|
||||
cache.set(shape.size, renderPath(shape))
|
||||
})
|
||||
|
||||
return (
|
||||
<g id={id}>
|
||||
|
@ -54,7 +52,7 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
stroke={styles.stroke}
|
||||
/>
|
||||
<path
|
||||
d={path}
|
||||
d={pathData}
|
||||
fill={styles.stroke}
|
||||
stroke={styles.stroke}
|
||||
strokeWidth={styles.strokeWidth}
|
||||
|
@ -114,21 +112,19 @@ const rectangle = registerShapeUtils<RectangleShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
const [width, height] = shape.size
|
||||
const bounds = {
|
||||
cache.set(shape, {
|
||||
minX: 0,
|
||||
maxX: width,
|
||||
minY: 0,
|
||||
maxY: height,
|
||||
width,
|
||||
height,
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
this.boundsCache.set(shape, bounds)
|
||||
}
|
||||
|
||||
return translateBounds(this.boundsCache.get(shape), shape.point)
|
||||
return translateBounds(bounds, shape.point)
|
||||
},
|
||||
|
||||
hitTest() {
|
||||
|
@ -218,5 +214,5 @@ function renderPath(shape: RectangleShape) {
|
|||
}
|
||||
)
|
||||
|
||||
pathCache.set(shape.size, getSvgPathFromStroke(stroke))
|
||||
return getSvgPathFromStroke(stroke)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { uniqueId, isMobile } from 'utils/utils'
|
||||
import { uniqueId, isMobile, getFromCache } from 'utils/utils'
|
||||
import vec from 'utils/vec'
|
||||
import TextAreaUtils from 'utils/text-area'
|
||||
import { TextShape, ShapeType } from 'types'
|
||||
|
@ -13,12 +13,8 @@ import state from 'state'
|
|||
import { registerShapeUtils } from './register'
|
||||
|
||||
// A div used for measurement
|
||||
document.getElementById('__textMeasure')?.remove()
|
||||
|
||||
if (document.getElementById('__textMeasure')) {
|
||||
document.getElementById('__textMeasure').remove()
|
||||
}
|
||||
|
||||
// A div used for measurement
|
||||
const mdiv = document.createElement('pre')
|
||||
mdiv.id = '__textMeasure'
|
||||
|
||||
|
@ -128,6 +124,10 @@ const text = registerShapeUtils<TextShape>({
|
|||
const fontSize = getFontSize(shape.style.size) * shape.scale
|
||||
const lineHeight = fontSize * 1.4
|
||||
|
||||
if (ref === undefined) {
|
||||
throw Error('This component should receive a ref.')
|
||||
}
|
||||
|
||||
if (!isEditing) {
|
||||
return (
|
||||
<g id={id} pointerEvents="none">
|
||||
|
@ -155,7 +155,6 @@ const text = registerShapeUtils<TextShape>({
|
|||
</g>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<foreignObject
|
||||
id={id}
|
||||
|
@ -164,7 +163,7 @@ const text = registerShapeUtils<TextShape>({
|
|||
pointerEvents="none"
|
||||
>
|
||||
<StyledTextArea
|
||||
ref={ref}
|
||||
ref={ref as React.RefObject<HTMLTextAreaElement>}
|
||||
style={{
|
||||
font,
|
||||
color: styles.stroke,
|
||||
|
@ -178,7 +177,7 @@ const text = registerShapeUtils<TextShape>({
|
|||
autoSave="false"
|
||||
placeholder=""
|
||||
color={styles.stroke}
|
||||
autoFocus={isMobile() ? true : false}
|
||||
autoFocus={!!isMobile()}
|
||||
onFocus={handleFocus}
|
||||
onBlur={handleBlur}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
@ -190,14 +189,14 @@ const text = registerShapeUtils<TextShape>({
|
|||
},
|
||||
|
||||
getBounds(shape) {
|
||||
if (!this.boundsCache.has(shape)) {
|
||||
mdiv.innerHTML = shape.text + '‍'
|
||||
const bounds = getFromCache(this.boundsCache, shape, (cache) => {
|
||||
mdiv.innerHTML = `${shape.text}‍`
|
||||
mdiv.style.font = getFontStyle(shape.scale, shape.style)
|
||||
|
||||
const [minX, minY] = shape.point
|
||||
const [width, height] = [mdiv.offsetWidth, mdiv.offsetHeight]
|
||||
|
||||
this.boundsCache.set(shape, {
|
||||
cache.set(shape, {
|
||||
minX,
|
||||
maxX: minX + width,
|
||||
minY,
|
||||
|
@ -205,9 +204,9 @@ const text = registerShapeUtils<TextShape>({
|
|||
width,
|
||||
height,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return this.boundsCache.get(shape)
|
||||
return bounds
|
||||
},
|
||||
|
||||
hitTest() {
|
||||
|
|
|
@ -1480,6 +1480,35 @@ export function getBoundsCenter(bounds: Bounds): number[] {
|
|||
/* Lists and Collections */
|
||||
/* -------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Get a value from a cache (a WeakMap), filling the value if it is not present.
|
||||
*
|
||||
* ### Example
|
||||
*
|
||||
*```ts
|
||||
* getFromCache(boundsCache, shape, (cache) => cache.set(shape, "value"))
|
||||
*```
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export function getFromCache<V, I extends object>(
|
||||
cache: WeakMap<I, V>,
|
||||
item: I,
|
||||
replace: (cache: WeakMap<I, V>) => void
|
||||
): V {
|
||||
let value = cache.get(item)
|
||||
|
||||
if (value === undefined) {
|
||||
replace(cache)
|
||||
value = cache.get(item)
|
||||
|
||||
if (value === undefined) {
|
||||
throw Error('Cache did not include item!')
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a unique string id.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue